win2003下PHP使用preg_match_all导致apache崩溃问题的解决方法
小编的平台是windowsserver2003(32位系统)+Apache/2.2.9(Win32)+PHP/5.2.17,在使用正则表达式preg_match_all(如preg_match_all("/ni(.*?)wo/",$html,$matches);)进行分析匹配比较长的字符串$html时(大于10万字节,一般用于分析采集回来的网页源码),Apache服务器会崩溃自动重启。
在Apache错误日志里有这样的提示:
[ThuApr1118:31:312013][notice]Parent:childprocessexitedwithstatus128--Restarting. [ThuApr1118:31:312013][notice]Apache/2.2.9(Win32)PHP/5.2.17configured--resumingnormaloperations [ThuApr1118:31:312013][notice]Serverbuilt:Jun13200804:04:59 [ThuApr1118:31:312013][notice]Parent:Createdchildprocess2964 [ThuApr1118:31:312013][notice]DisableduseofAcceptEx()WinSock2API [ThuApr1118:31:312013][notice]Child2964:Childprocessisrunning [ThuApr1118:31:312013][notice]Child2964:Acquiredthestartmutex. [ThuApr1118:31:312013][notice]Child2964:Starting350workerthreads. [ThuApr1118:31:312013][notice]Child2964:Listeningonport80.
经过查阅Apache官方以及论坛资料后,发现win平台下用正则preg_match_all或preg_match分析比较长的字符串时,导致apache崩溃重启的原因是windows平台下默认分配的线程堆栈空间ThreadStackSize太小导致的。win32默认只有256KB,而在linux下默认值是8M,这就是为什么同样的程序在linux平台下正常,而在win平台下不正常的原因。
根据PCRElibrary的官方说明:256KB的堆栈空间对应的pcre.recursion_limit大小应该不超过524。
Hereisatableofsafevaluesofpcre.recursion_limitforavarietyofexecutablestacksizes:
下面就是一张Stacksize和pcre.recursion_limit对应的建议安全值,超过这个数值就极有可能发生堆栈溢出,apachecrash:
Stacksize pcre.recursion_limit 64MB 134217 32MB 67108 16MB 33554 8MB 16777 4MB 8388 2MB 4194 1MB 2097 512KB 1048 256KB 524
如果你没有调整堆栈大小,就必须在使用正则的PHP页面最开头加入:
<?php ini_set("pcre.recursion_limit","524");//PHPdefaultis100,000. ?>
查看具体的错误可以使用下面的代码:
$resultsArray=preg_match_all("/table.*?<a>/isU",$html,$contents); if($resultsArray===0){ echoget_pcre_err(); } functionget_pcre_err(){ $pcre_err=preg_last_error(); //PHP5.2andabove. if($pcre_err===PREG_NO_ERROR){ $msg='Successfulnon-match.'; }else{ //preg_matcherror! switch($pcre_err){ casePREG_INTERNAL_ERROR: $msg='PREG_INTERNAL_ERROR'; break; casePREG_BACKTRACK_LIMIT_ERROR: $msg='PREG_BACKTRACK_LIMIT_ERROR'; break; casePREG_RECURSION_LIMIT_ERROR: $msg='PREG_RECURSION_LIMIT_ERROR'; break; casePREG_BAD_UTF8_ERROR: $msg='PREG_BAD_UTF8_ERROR'; break; casePREG_BAD_UTF8_OFFSET_ERROR: $msg='PREG_BAD_UTF8_OFFSET_ERROR'; break; default: $msg='UnrecognizedPREGerror'; break; } } return($msg); }
对于正则的修饰符isU说明:
i:表示in-casesensitive,即大小写不敏感 s:PCRE_DOTALL,表示点号可以匹配换行符。 U:表示PCRE_UNGREEDY,表示非贪婪,相当于perl/python语言的.*?,在匹配过程中,对于.*正则,一有匹配立即执行,而不是等.*搜索了所有字符再一一返回
在使用正则表达式时,我们应该尽量避免递归调用,递归容易导致堆栈溢出。比如:
/<table((?!<table).)*?<\/a>/isU就会发生错误,而使用/<table.*?<\/a>/i就正常。
那么如何增加win平台下ThreadStackSize的大小呢?在apache的配置文件httpd.conf里启用“Includeconf/extra/httpd-mpm.conf”(删除前面的注释#),然后在httpd-mpm.conf文件里的mpm_winnt_module配置模块里设置“ThreadStackSize8400000”即可(大约8M)。
<IfModulempm_winnt_module> ThreadStackSize8400000 ThreadsPerChild 200 MaxRequestsPerChild 10000 Win32DisableAcceptEx </IfModule>
这里需要注意的是,32位的Apache程序只能最多使用大约2GB内存空间!因此,ThreadStackSize和ThreadsPerChild的值相乘后(8M*200)不应该超过2G,否则无法启动apache,出现的错误日志如下:
[ThuApr1120:02:452013][crit](OS8)存储空间不足,无法处理此命令。 :Child4832:_beginthreadexfailed.Unabletocreateallworkerthreads.Created212ofthe220threadsrequestedwiththeThreadsPerChildconfigurationdirective.
通过上面的提示,小编可以告诉大家的是在我的这台服务器上,当线程堆栈大小设为8M时,我可以设置的线程数最多是212个。