WAMP下Apache高并发卡顿排查与性能调优实战指南

WAMP下Apache高并发卡顿排查与性能调优实战指南 WAMP环境里的Apache最典型的一个毛病就是“平时好好的人一多就卡成PPT”。我接手过好几台跑在Windows上的WAMP服务器用户反馈几乎一模一样上午十点一过访问量稍微上来点页面就开始转圈数据库连接报错也跟着冒出来。第一反应当然是查带宽、查服务器配置但绝大多数情况下硬件根本没到瓶颈真正拖后腿的是Apache在Windows下的并发处理模型以及WAMP这套环境里几处默认配置互相打架。这篇文章就围绕这个场景把我排查和处理WAMP下Apache高并发卡顿的完整思路拆开讲。从怎么确认瓶颈、改Apache的并发参数到PHP、MySQL两边怎么配合调优再到Windows系统层面的几个隐藏坑每一步都会给出能直接照做的配置和命令。不管你用的是WAMP全家桶还是自己拼的Windows Apache MySQL PHP组合这套思路基本都能用得上。1. 先分诊卡顿到底是哪个环节在堵1.1 别急着改配置先把“卡”量化出来“访问卡”是个特别模糊的描述可能是Apache进程把CPU打满可能是MySQL的查询堆积也可能是PHP执行时卡在某个外部请求上。要是上来就乱调参数大概率越调越乱。我习惯用三个步骤做分诊打开任务管理器看CPU、内存、磁盘三个指标谁先顶不住。用压力测试工具模拟并发请求观察Apache进程数和响应时间的变化。翻Apache的error_log和PHP的慢日志看报错集中在哪一类。任务管理器是最快的一步。如果CPU占到90%以上说明是计算密集型的瓶颈通常出在Apache的并发线程数太多、PHP代码太耗CPU或者MySQL查询没有索引如果内存占满导致频繁读写磁盘那多半是Apache的线程栈分配过多或者MySQL的缓存设置不合身如果CPU和内存都还有余量但页面就是打不开那就要怀疑是不是连接数耗尽、端口耗尽、或者防火墙和杀毒软件在实时扫描拖慢了请求。这里有个很常见的误区很多人一看人多了卡直接去升级服务器CPU和内存。但Windows下的Apache对硬件配置的敏感度远不如Linux真正的瓶颈往往在软件配置层面。就说一个最简单的现象CPU四核八线程的机器Apache默认配置下可能只用了不到两核的算力这就是并发模型没调好的锅。1.2 用ab压测模拟“人多”的场景为了不等到用户把服务器卡死才去解决我习惯本地或者内网先压一轮。Apache自带的ab工具就是干这个的WAMP的bin目录下就有路径一般是C:\wamp64\bin\apache\apache2.4.x\bin\ab.exe压测命令很简单ab -n 1000 -c 50 http://127.0.0.1/-n表示总请求数-c表示并发数也就是同时保持50个连接。压完之后重点看两个指标Requests per second每秒请求数和Time per request每个请求平均耗时。如果每秒请求数很低比如只有几十而Time per request很高那就说明Apache的处理能力确实有问题。压测时一定要盯着任务管理器看。如果CPU没有打满但RPS上不去基本都是Apache的配置限制住了并发能力如果CPU打满但RPS还是低那就去查PHP代码和数据库查询的效率。这一步做扎实了后面调优才有的放矢。1.3 检查error_log确定有没有硬性报错Apache的error_log是个好东西默认路径在WAMP的logs目录下C:\wamp64\logs\apache_error.log打开之后先搜几个关键词Timeout、resource temporarily unavailable、MaxRequestWorkers、Out of memory。出现这些就说明Apache确实在并发上被卡死了。我在一次排障中就见过连续几十行“server reached MaxRequestWorkers setting”这就是典型的并发数已经被榨干新请求进不来只能排队等。除了这些还要看有没有PHP的Fatal error。WAMP默认的PHP错误可能是显示在页面上的但生产环境里应该把display_errors关掉把错误写到日志里。这一步能帮你区分到底是Apache层面进不来还是PHP执行到一半挂掉了。2. Apache并发模型才是卡顿的命门2.1 Windows下Apache为什么只能选mpm_winntApache在Linux下常见的有prefork多进程模型和worker多进程多线程模型两种MPM但在Windows下官方只推荐且默认使用mpm_winnt。这个模型是单一父进程加多线程的结构所有请求都由一个进程内的多个线程处理。为什么要单独说这个因为很多WAMP使用者会搜到Linux的优化教程照着把prefork或worker的配置写进去结果Apache直接启动不了或者启动之后更卡。Windows没有fork()函数prefork天然没法用worker虽然理论上能编译但官方在Windows上的支持一直不算完备。老老实实用mpm_winnt别瞎折腾。mpm_winnt的核心参数就两个一个是ThreadsPerChild控制进程内开多少个线程另一个是MaxConnectionsPerChild控制单个子进程处理多少请求后自动回收重建线程。Windows下Apache没有多个子进程的概念所以把这两个参数调好基本就决定了它能扛多少并发。2.2 关键参数怎么算、怎么调WAMP的httpd.conf里默认是不带mpm配置的默认值相当于IfModule mpm_winnt_module ThreadsPerChild 150 MaxConnectionsPerChild 0 /IfModuleThreadsPerChild150意味着最多150个并发线程。注意这150个线程是共享同一个进程的如果每个PHP请求都占着内存不释放150个线程同时跑起来内存会非常难看。MaxConnectionsPerChild0表示不限制每个子进程处理多少请求这会导致线程长期不回收内存碎片越积越多跑几天之后Apache进程的内存占用能翻好几倍然后开始各种莫名卡顿甚至崩溃。我的建议是根据服务器内存量来做初始配置服务器内存ThreadsPerChild推荐值MaxConnectionsPerChild适用场景4GB1001000小网站在线人数50以内8GB2002000中型网站在线人数100~20016GB及以上3003000并发要求更高的场景这里MaxConnectionsPerChild设置为1000~3000是合理的经验值。这个参数的意思是每个线程处理完这么多个请求之后父进程会把整个子进程重启一遍。这样能有效回收内存碎片代价是重启瞬间会有几十毫秒的停顿但相比内存泄漏导致的卡顿这点停顿完全可以接受。网上有些教程说把这个参数设置为0可以避免重启开销在Windows上我强烈不建议这么做。改完之后在httpd.conf里加上一行IncludeInclude c:/wamp64/bin/apache/apache2.4.x/conf/extra/httpd-mpm.conf或者在httpd.conf里直接写IfModule mpm_winnt_module ThreadsPerChild 200 MaxConnectionsPerChild 2000 /IfModule然后重启Apache。WAMP下可以直接左键托盘图标选Restart All Services。重启后用ab再压一遍你会发现RPS有明显提升而且内存曲线比之前稳定很多。2.3 KeepAlive这个隐形杀手KeepAlive的初衷是让同一个客户端连接可以复用省去重复建立TCP连接的开销。但对WAMP这种单人开发的服务器来说默认的KeepAlive On会带来一个麻烦每一个空闲连接都会占着一个线程而Apache线程总数是有限的。人稍微一多一堆浏览器开着长连接不关线程就被白白占完了后面的用户只能等。处理方式很简单调短KeepAliveTimeoutKeepAlive On MaxKeepAliveRequests 100 KeepAliveTimeout 2KeepAliveTimeout默认是5秒调到2秒后浏览器如果在2秒内有新请求还能复用连接超过2秒连接就被释放用户下个请求重新建连。对于绝大多数WAMP场景这个值已经够用了。另外如果网站里图片、CSS、JS比较多可以考虑把KeepAlive完全关掉改成让浏览器端做强缓存减少服务器端连接占用的压力。对于小程序后端或者纯API接口KeepAlive On反而能减少握手延迟所以这个参数要根据业务类型来定不是一刀切的。3. PHP和MySQL真正消耗线程的两大块3.1 PHP-FPM不可用但OpCache必须开WAMP里的PHP默认走的是Apache的mod_php也就是每个Apache线程都内嵌了一个PHP解释器。这意味着Apache每开一个线程就要为PHP分配一份内存150个线程就是150份PHP运行时。这个设计本身就吃内存所以ThreadsPerChild不能盲目调太大不然内存先爆。PHP层面有几个必调的点。首先是开启OpCache这玩意儿能缓存PHP编译后的字节码省去每次请求都重新解析PHP文件的开销。在php.ini里找到[opcache] zend_extensionE:/wamp64/bin/php/php7.x/ext/php_opcache.dll opcache.enable1 opcache.memory_consumption128 opcache.max_accelerated_files4000 opcache.validate_timestamps0注意如果是开发环境validate_timestamps不要设成0不然你改PHP文件后要手动清缓存如果是生产环境设成0能省掉每次检查文件修改时间的开销。然后是PHP的max_execution_time默认是30秒。如果某个PHP脚本执行了30秒还没结束Apache线程就被这个请求白白占住30秒。并发一多线程池很快就满了。建议调成10秒左右max_execution_time 10同时把慢日志打开方便定位到底哪些脚本在耗时slowlog C:/wamp64/logs/php_slow.log request_slowlog_timeout 3这个配置不是所有PHP版本默认都开需要确认你的PHP是线程安全版TS并且确认配置文件里有这些项。没有的话就手动加。3.2 MySQL连接数限制和慢查询是两座大山WAMP自带的MySQL/ MariaDB默认max_connections一般是100到150。这个数乍看不小但要命的是如果Apache的ThreadsPerChild被改大到了200而每个PHP请求都新建数据库连接那数据库连接数可能瞬间被打爆。MySQL连接池在PHP里不是标配每个请求默认都要建立一次TCP连接到MySQL用完了再断开。高并发下这种做法非常浪费但改动代码成本很高。有一个折中方案在phpMyAdmin或者MySQL自己的配置里把max_connections适当调大同时把wait_timeout调短让空闲连接快点释放[mysqld] max_connections 200 wait_timeout 60 max_allowed_packet 16Mwait_timeout从默认的28800秒8小时改成60秒能有效防止一堆失联的空闲连接把MySQL拖垮。不过如果你代码里有长连接的需求这个值要相应调大不能照抄。MySQL的慢查询日志一定要开。在高并发场景下一条慢查询就能拖慢整个页面如果页面里有好几个慢查询数据库直接成为瓶颈。开启方式在my.ini里slow_query_log 1 slow_query_log_file C:/wamp64/logs/mysql-slow.log long_query_time 1long_query_time设为1秒也就是说超过1秒的SQL会被记录下来。然后定期检查这个日志把频繁出现的慢SQL交给开发去优化。在实际排障中我见过太多“Apache卡”最后定位到MySQL慢查询上的案例。一个没索引的联表查询在数据量到几十万条之后每查一次就是几秒钟这比Apache自身配置的影响还要大。3.3 Session存储也是个被忽视的点默认情况下PHP的Session是以文件形式存在临时目录里的。高并发时同一时刻大量并发请求都要读写Session文件文件锁的竞争就会变得非常激烈。表现为CPU不高、内存不高但请求就是慢尤其是那些频繁使用Session的页面。临时解决办法是把session的存储目录改成内存盘RAMDisk或者用Redis/Memcached做Session存储。WAMP环境下最简单的做法是给PHP加一个Memcached扩展或者是用Windows版的Redis。如果不想引入外部组件至少要保证session.save_path指向的目录有足够的IO性能并且定期清理过期Session文件。4. Windows系统层面一堆容易忽略的隐藏坑4.1 杀毒软件实时扫描正在拖慢每一个请求Windows服务器上基本都装了杀毒软件这玩意儿对Apache的影响大得离谱。Apache的每个进程、每个PHP脚本都要被实时扫描一遍本来几十毫秒就能完成的请求硬生生被拖到几百毫秒。人少的时候感觉不明显并发一上来扫描队列排着队整个服务器就卡住了。处理方法是在杀毒软件的把Apache安装目录、PHP安装目录、网站代码目录、MySQL的data目录全部加入排除名单。注意不仅仅是Apache的目录还有Session临时目录和日志目录也要加进去。如果你用的是Windows Defender可以在“病毒和威胁防护”里设置“排除项”。这一步做完很多卡顿问题能立竿见影地缓解。4.2 Windows的TCP动态端口耗尽Apache在Windows下短时间内接受大量连接会消耗大量TCP临时端口。Windows默认的动态端口范围是49152到65535差不多16384个端口。如果Apache长时间运行每个TCP连接结束之后端口并不会立刻释放而是进入TIME_WAIT状态需要等待大约120秒才能被复用。高并发下一旦端口耗尽新连接就建立不起来表现就是网站时不时打不开过一会儿又能打开。可以用这条命令查看和调整端口范围netsh int ipv4 show dynamicport tcp如果显示的范围很小可以扩大netsh int ipv4 set dynamicport tcp start20000 num40000另外可以调小TIME_WAIT的等待时间需要打开注册表在HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters下新建一个DWORD值。4.3 日志太多、模块太全都在吃IOWAMP默认开着很多用不上的Apache模块每个模块都会在请求生命周期里执行额外的逻辑。不需要的模块建议直接注掉尤其是那些不用的认证模块、代理模块和CGI模块。模块数量少了Apache的内存占用会明显下降。还有Apache的访问日志人一多的时候每个请求都要往access.log里写一行记录。如果服务器IO性能一般日志写入就成了一块巨大的开销。建议日志级别调整为warnLogLevel warn访问日志如果不需要太细的话可以关闭或者改成按天轮转。WAMP自带的Apache默认是直接把日志写到一个文件里文件越来越大查找问题也越来越麻烦。4.4 用输出压缩和静态资源缓存给Apache减负对于以网页为主的站点开启mod_deflate效果非常明显。它能在Apache层面把HTML、CSS、JS压缩后再传给浏览器大幅减少网络传输量。Apache默认带这个模块在httpd.conf里打开注释即可LoadModule deflate_module modules/mod_deflate.so IfModule mod_deflate.c AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript DeflateCompressionLevel 6 /IfModule压缩级别不建议开到9CPU开销上升但传输体积并没有减少多少性价比很低。同时给静态资源设置较长的浏览器缓存时间IfModule mod_expires.c ExpiresActive On ExpiresByType image/jpg access plus 7 days ExpiresByType image/png access plus 7 days ExpiresByType text/css access plus 7 days ExpiresByType application/javascript access plus 7 days /IfModule4.5 高并发场景下反向代理往往是更优解WAMP本身的定位是开发环境不是高性能生产服务器。如果业务同时在线人数真的大几百了硬靠调优Apache在Windows上的表现天花板是很低的。这时候我一般会推荐一个折中方案服务器上保留WAMP不动在前面加一层Nginx或者用Apache自带的反向代理模块配合缓存。Apache的mod_proxy和mod_cache可以组合成一个简单的反向代理缓存层静态文件请求直接由缓存返回动态请求才落到后端的Apache后端的压力能少掉一大半。具体配置LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule cache_module modules/mod_cache.so LoadModule cache_disk_module modules/mod_cache_disk.so IfModule mod_cache.c CacheRoot c:/wamp64/tmp/apache_cache CacheEnable disk / CacheDefaultExpire 300 CacheIgnoreNoLastMod On /IfModule不过说实话Windows上跑两层Web服务器资源开销和管理复杂度都是双份的。真要长期扛高并发还是建议把应用迁移到Linux服务器上Apache或者Nginx在Linux上的性能和稳定性都会好很多。WAMP更适合做开发、测试、小规模上线验证。5. 常见问题与排查技巧实录5.1 一张表解决大多数WAMP卡顿定位问题我自己长期排查WAMP问题下来整理了一张速查表。遇到卡顿先对照这张表排查能省掉大量时间现象可能原因排查命令/日志解决办法CPU打满但RPS很低PHP效率低、OpCache没开看php_slow.log开启OpCache优化慢SQL内存持续暴涨Apache线程内存泄漏观察任务管理器Apache进程设置MaxConnectionsPerChild定期回收网页时好时坏间隔性打不开TCP端口耗尽netsh int ipv4 show dynamicport tcp扩大动态端口范围首次请求慢之后正常Session文件竞争看PHP临时目录大小换Redis/Memcached存储Session页面能打开但数据库报连接错误MySQL连接数耗尽show processlist;调max_connections和wait_timeout全部请求都慢且CPU不高杀毒软件实时扫描临时关闭杀软测试加排除名单访问量上来后Apache进程崩溃MaxRequestWorkers设置过小看apache_error.log调大并发线程数这张表的思路是先看现象再定位到具体的软件层面最后做对应的配置调整。大多数WAMP卡顿问题的根因其实都在这几个范围内。5.2 我踩过的几个坑第一次处理类似问题的时候我一上来就把ThreadsPerChild调到了500。结果服务器内存占用直接飙到90%页面反而更卡。后来才明白线程数不是越大越好它受限于内存和每个PHP请求的内存占用。调这个参数前先算一下假设每个PHP请求平均占用20MB内存200个线程同时跑就是4GB的内存消耗加上MySQL和其他进程8GB的服务器很容易被吃满。还有一次我改了php.ini之后重启Apache却发现配置没生效。最后发现WAMP的PHP加载的php.ini路径不是我以为的那个而是PHP安装目录下的php.ini。在命令行执行php --ini可以查到实际加载的配置文件路径。这个坑特别隐蔽很多人调了半天发现改的是个假配置文件。另外一个经验是每次修改配置后一定要小流量验证不要直接全量生效。WAMP下可以先停掉Apache用命令行模式手动启动一次看有没有语法错误。用httpd -t可以快速检查配置语法httpd -t报错的话它会直接告诉你哪一行有问题比重启服务后才发现启动失败要高效得多。5.3 从运维角度做好监控才是一劳永逸调优只是第一步后续的监控和基线对比更重要。我习惯把每次修改前后的ab压测结果记录下来包括RPS、平均响应时间、Apache内存占用、MySQL连接数这样能客观看到调优效果。Windows自带的性能监视器可以监控Apache进程的CPU、内存、IO指标。再加上定期查看Apache的error_log和MySQL的慢查询日志基本能覆盖日常运维的核心需求。越是“人一多就卡”的服务器越要提前做压力测试和数据记录而不是等卡了再去找原因。另外用Windows的计划任务定期重启Apache也是一个应急手段。比如每天凌晨低峰期重启一次服务能把长期运行导致的内存碎片和线程泄漏问题控制在可控范围内。虽然这不能根治问题但作为一道兜底防线实际操作起来非常有效。最后说点实在的WAMP下的Apache在高并发场景里卡顿核心原因往往不是单个配置而是并发模型、PHP资源消耗、MySQL连接、Windows系统限制这几层因素叠加出来的结果。先把瓶颈定位清楚再一层层去调才不会白费力气。我个人在实际操作中最深的体会是线程数和连接数这两个参数一定要联动调整改Apache的同时必须考虑MySQL能不能接住否则Web层和数据库层的瓶颈只是被转移了位置。如果你现在正被“人一多就卡”折磨建议从ab压测开始记录好数据再按文章里的顺序逐项排查。多数情况下不花一分钱硬件升级费光靠配置调整就能把承载能力翻一两倍。