Apache作为反向代理时,mod_cache与mod_proxy的组合承担了绝大部分请求吞吐。一旦出现缓存命中但响应依然慢、或者CPU被某个不明函数吃掉一半的情况,单靠access log和server-status很难定位问题。火焰图可以把CPU采样结果按调用栈聚合,直观展示Apache内部哪个函数、哪个模块消耗了时间,是代理缓存链路调优里性价比最高的分析手段。

一、代理缓存的工作链路与常见瓶颈
反向代理场景下,一个请求进入Apache后会经历连接接收、URL映射、缓存查找、缓存写入、后端转发等阶段。mod_cache默认挂在两个钩子上:post_read_request阶段做快速命中判断,quick_handler里真正执行缓存读取;未命中则走正常处理流程,由mod_proxy把请求转发给后端Tomcat、uWSGI或另一台HTTP服务器。
瓶颈通常藏在三个地方。第一是缓存查找本身的开销,当CacheEnable指定的存储目录下文件数量达到数十万级别,某些文件系统的目录查找会成为热点。第二是缓存失效风暴,CacheDefaultExpire设置过短导致大量请求同时穿透到后端,mod_cache在回源时会加一把缓存锁防止惊群,锁竞争会让CPU在原子操作和互斥上空转。第三是磁盘写入,disk类型缓存默认不做tmpfs,大量小文件的写入在高并发下非常昂贵。
这些问题在监控上的表现往往很模糊:CPU使用率60%却找不到是哪个模块的锅,或者QPS不高但延迟P99抖动严重。这时候就需要火焰图登场了。
二、用perf和FlameGraph生成Apache火焰图
Apache本身没有内置火焰图导出功能,最常用的方案是用Linux的perf对httpd进程做CPU采样,再用Brendan Gregg的FlameGraph脚本渲染成SVG。首先确认内核允许采样,如果提示权限问题,可以临时调整:
# 允许读取内核采样信息(重启失效) sudo sysctl kernel.perf_event_paranoid=1 sudo sysctl kernel.kptr_restrict=0 # 对所有httpd进程采样30秒,频率99Hz避免与周期性任务共振 sudo perf record -F 99 -a -g -p $(pgrep -d ',' httpd) -- sleep 30 # 生成折叠调用栈并渲染火焰图 sudo perf script > out.perf FlameGraph/stackcollapse-perf.pl out.perf > out.folded FlameGraph/flamegraph.pl out.folded > cache.svg
打开生成的SVG,横轴宽度代表该函数占用CPU的比例,纵轴是调用栈深度。定位代理缓存问题时重点看三段:如果cache_storage或ap_cache开头的函数栈很宽,说明缓存存储层是热点;如果apr_file_open、openat等系统调用占比高,多半是文件句柄和目录查找问题;如果看到ap_proxy_string_read或者proxy相关的栈很宽,瓶颈可能在后端转发或响应回读上。
生产环境无法安装perf时,可以考虑换成eBPF工具做采样,或者在低峰期抓core dump离线分析。另外注意采样时尽量让系统跑在真实流量或压测流量下,空载时抓的火焰图基本没有分析价值。
三、根据火焰图结果做模块级调优
假设火焰图显示热点集中在缓存存储层,第一步是整理存储目录。disk缓存用两级哈希目录存放缓存文件,目录文件数过多时,把存储挪到tmpfs上能直接消掉大部分open与unlink的开销:
# 在 fstab 中挂载tmpfs作为缓存目录 tmpfs /var/cache/httpd/proxy tmpfs size=4g,mode=700,uid=apache 0 0
第二步是压制回源风暴和锁竞争。mod_cache默认对同一个URL的并发回源会加锁,其他请求阻塞等待,火焰图上表现为mutex等待很宽。可以适度调整锁参数,同时调大过期时间减少穿透:
CacheEnable disk / CacheRoot /var/cache/httpd/proxy CacheDirLevels 3 CacheDirLength 2 CacheMaxFileSize 10000000 CacheMinFileSize 512 CacheDefaultExpire 3600 # 回源锁:命中失败时后续请求最多等待3秒 CacheLock on CacheLockMaxAge 3 # 避免误缓存带会话的响应 CacheIgnoreHeaders Set-Cookie
第三步看连接层。如果火焰图宽的部分落在ap_get_brigade和socket read上,说明后端响应回读是瓶颈,这时应该启用mod_proxy的连接池复用,减少每次请求的TCP握手开销:
<Proxy http://127.0.0.1:8080/>
ProxySet disablereuse=off max=200 ttl=120 retry=30
</Proxy>
ProxyPass /app http://127.0.0.1:8080/app四、调优前后的压测验证
调优不能凭感觉,改完配置后用ab或wrk做对比压测,重点看三个指标:缓存命中率(通过日志里的cache-status字段统计)、P99延迟和每千请求CPU成本。压测建议分冷缓存和热缓存两轮,冷缓存轮观察回源行为是否正常,热缓存轮观察稳定态吞吐。
# wrk压测,注意加Host头否则虚拟主机匹配不上
wrk -t8 -c200 -d60s -H "Host: www.ipipp.com" http://10.0.0.5/static/index.html
# 日志格式中加入 %{cache-status}e 字段统计命中率
# 输出示例形如 cache hit、cache miss、cache hit(stale)理想的结果是:热缓存命中率稳定在95%以上,火焰图里openat和mutex等待明显变窄,CPU大头转移到正常的网络读写上。如果命中率上去了但延迟依然抖动,回头再看火焰图,检查是否还有磁盘写入残留,必要时给缓存盘换SSD或继续增大tmpfs容量。
最后提醒一点,火焰图是快照不是真相本身,同一个系统在不同流量模型下热点会漂移。建议把火焰图采样纳入常规巡检,每次大版本配置变更前后各抓一次,对比SVG的差异,这种前后对照的方式比单次分析要可靠得多。调优本质上是一个测量、修改、再测量的循环,火焰图只是把这个循环里的测量环节做得足够直观。
Apache代理缓存火焰图性能调优修改时间:2026-09-04 11:41:15