Apache作为反向代理使用时,开启mod_cache可以显著降低后端服务压力,缩短响应时间。但缓存配好之后,命中率是一个必须持续关注的指标:如果命中率只有百分之十几,说明大部分请求还是穿透到了后端,缓存几乎没有发挥作用,反而多了一层转发开销。本文将介绍如何在Apache中记录缓存命中情况、如何从日志中统计命中率,以及提升命中率的实用技巧。

开启缓存命中日志记录
Apache从2.4版本开始,在%{cache-status}e这个日志变量中提供了非常直观的缓存状态信息。只要请求经过mod_cache处理,这个变量就会记录下该请求的缓存判定结果,常见的取值包括HIT、MISS、EXPIRED、REVALIDATED和SKIPPED等。HIT表示直接命中缓存返回,MISS表示缓存中没有可用数据需要回源,EXPIRED表示缓存存在但已过期,REVALIDATED表示通过与后端协商验证后复用了旧缓存,SKIPPED则表示mod_cache主动跳过了缓存处理。
要在访问日志中输出这个字段,需要修改日志格式定义。下面是一个完整的配置示例,假设我们已经用mod_cache搭建了一个磁盘缓存型的反向代理:
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
<VirtualHost *:80>
ServerName proxy.ipipp.com
ProxyPreserveHost On
ProxyPass "/" "http://backend.ipipp.com/"
ProxyPassReverse "/" "http://backend.ipipp.com/"
CacheEnable disk "/"
CacheRoot "/var/cache/apache/proxy"
CacheDirLevels 2
CacheDirLength 1
CacheMaxFileSize 5000000
CacheIgnoreNoLastMod On
# 关键:自定义日志格式,加入缓存状态字段
LogFormat "%h %t \"%r\" %>s %b cache=%{cache-status}e %D" cachelog
CustomLog "logs/proxy_cache.log" cachelog
</VirtualHost>
配置生效后,日志中每一行都会带上cache=HIT或cache=MISS这样的标记。注意%{cache-status}e是一个环境变量形式的日志字段,它只在mod_cache介入请求处理时才会有值,因此看到cache字段为空的行,说明该请求根本没有进入缓存流程,这本身就是一个需要排查的问题。
用脚本统计命中率
有了带缓存状态的日志,统计命中率就是纯粹的文本处理工作了。最简单的方式是用grep配合awk,一行命令就能算出结果:
#!/bin/bash LOG=/var/log/httpd/proxy_cache.log # 统计各类缓存状态的数量 total=$(wc -l < $LOG) hit=$(grep -c 'cache=HIT ' $LOG) miss=$(grep -c 'cache=MISS ' $LOG) expired=$(grep -c 'cache=EXPIRED ' $LOG) echo "总请求数: $total" echo "命中数: $hit" echo "未命中数: $miss" echo "命中率: $(echo "scale=2; $hit * 100 / $total" | bc)%"
这个脚本适合快速检查,但生产环境更推荐按时间维度统计,比如观察每小时的命中率变化趋势。可以利用cron定时任务,每小时执行一次统计并把结果写入独立的文件,再配合监控系统的自定义指标上报。如果团队使用Prometheus,也可以直接部署一个日志解析的exporter,把命中率做成可视化曲线,异常波动一目了然。
另外需要注意EXPIRED和REVALIDATED的处理方式。有些团队只把HIT算作命中,这样统计出来的命中率会偏低。实际上REVALIDATED虽然是回源校验了,但后端返回304时传输成本极低,可以视为半个命中。建议在统计时把HIT和REVALIDATED都算作有效命中,EXPIRED单独归类观察,这样能更准确地评估缓存的真正价值。
分析命中率低的原因并优化
如果统计结果显示命中率长期偏低,通常有以下几个方向需要排查。第一是后端响应头的问题。如果后端返回的响应带有Cache-Control: no-store或Set-Cookie,mod_cache默认会拒绝缓存这类响应,大量动态接口就会全部变成MISS。可以通过CacheIgnoreHeaders Set-Cookie指令忽略特定头,但要谨慎使用,避免把用户个性化内容错误地缓存下来返回给其他人。
第二是缓存键的区分度问题。如果站点对同一资源存在多种URL形式,比如带查询参数和不带参数、大小写不一致,缓存键就会碎片化,明明内容相同却无法复用缓存条目。这时可以结合CacheKeyBaseURL以及URL重写规则做归一化处理。第三是缓存容量和过期策略,缓存目录磁盘写满后旧条目会被清理,如果热点内容在过期前就被挤出,命中率自然上不去,适当调大CacheMaxFileSize并配合htcacheclean工具管理缓存空间是常见做法:
# 使用 htcacheclean 控制缓存目录大小,保留1GB,后台运行 htcacheclean -d 30 -p /var/cache/apache/proxy -l 1024M -n -t # 查看缓存目录的实际占用 du -sh /var/cache/apache/proxy
第四是请求方法与状态码过滤。mod_cache默认只缓存GET和HEAD请求,如果业务中大量使用POST查询接口,这些请求永远无法命中缓存,需要考虑在应用层或网关层做改造。同样,5xx响应也不会被缓存,后端不稳定也会间接拉低命中率。
最后提醒一点,命中率统计应该长期坚持而不是一次性检查。建议把命中率、回源请求数、缓存空间占用三个指标纳入日常监控,并设置告警阈值,比如命中率低于60%就告警。只有持续观察,才能及时发现后端响应头变更、缓存目录被清空这类导致命中率骤降的问题,让代理缓存真正稳定地发挥作用。