CDN 日志命中率分析脚本
CDN 日志里最值得关注的数字就是命中率。如果命中率长期低于 70%,意味着每三次请求里就有一次要回源,带宽成本和源站压力都会明显上升。以一个月回源 500GB、按流量计费的站点为例,把命中率从 60% 提升到 90%,每月大约能省下 300GB 回源流量,换算成成本是一笔不小的开支。本文提供一套可以直接落地的 Python 脚本,用来统计 HIT/MISS 比例、按 URL 定位高回源请求,并找出拖累命中率的页面。还要提醒一句:命中率不是越大越好——如果动态接口也被缓存,用户看到的是过期数据,指标要结合业务一起看。
日志里的缓存状态字段
大多数 CDN 的访问日志都会带缓存状态字段。以 Cloudflare 为例,每条请求日志会记录 CacheStatus,常见取值包括 HIT、MISS、DYNAMIC、EXPIRED、REVALIDATED、STALE 等;阿里云 CDN 与腾讯云 EdgeOne 也有类似字段,只是命名略有差异。命中率的算法很直接:
$$命中率 = \frac{HIT 请求数}{HIT 请求数 + MISS 请求数} \times 100%$$
| 缓存状态 | 含义 | 对命中率的影响 |
|---|---|---|
| HIT | 请求命中边缘节点缓存 | 提升命中率 |
| MISS | 未命中,需要回源 | 拉低命中率 |
| EXPIRED | 缓存过期,重新回源校验 | 拉低命中率 |
| DYNAMIC | 动态内容默认不缓存 | 统计时应剔除 |
| STALE | 源站异常时返回过期内容 | 不产生回源,可忽略 |
参考:Cloudflare 日志字段说明 https://developers.cloudflare.com/logs/reference/log-fields/
日志格式与字段解析
上面的脚本假设日志是标准 combined 格式,字段用空格分隔、请求行用双引号包裹。不同 CDN 的日志字段顺序可能不一样:有的把缓存状态放在请求行末尾,有的放在独立字段里,还有的直接输出 JSON。写脚本前先用 head -5 cdn.log 看两行样本,确认 HIT/MISS 标记出现的位置,再决定用 ' HIT ' 还是 '\" HIT ' 这类匹配串。如果是 JSON 格式日志,直接用 json.loads 解析字段会更稳,例如 Cloudflare Logpush 里的 CacheStatus 字段。
命中率统计脚本
下面的脚本读取标准 Nginx/CDN 格式的访问日志,逐行判断缓存状态,输出整体命中率,并统计每个 URL 的回源次数,方便快速定位"最耗回源"的页面:
from collections import Counter
def analyze_cdn_log(path: str):
total = Counter()
origin_fetch = Counter() # 每个 URL 的回源次数
with open(path, 'r', encoding='utf-8') as f:
for line in f:
if '" HIT ' in line:
total['HIT'] += 1
elif '" MISS ' in line:
total['MISS'] += 1
url = line.split('"')[1].split(' ')[1]
origin_fetch[url] += 1
hit = total['HIT']
miss = total['MISS']
all_requests = hit + miss
ratio = hit / all_requests * 100 if all_requests else 0.0
print(f'总请求: {all_requests}, HIT: {hit}, MISS: {miss}')
print(f'命中率: {ratio:.2f}%')
print('Top 10 高回源 URL:')
for url, cnt in origin_fetch.most_common(10):
print(f' {cnt:>6} {url}')
analyze_cdn_log('cdn.log')
假设日志长这样:
127.0.0.1 - - [01/Jul/2026:10:15:22 +0800] "GET /product/a HTTP/1.1" 200 1024 "MISS"
127.0.0.1 - - [01/Jul/2026:10:15:23 +0800] "GET /product/a HTTP/1.1" 200 1024 "HIT"
运行后会输出类似结果:
总请求: 18423, HIT: 15123, MISS: 3300
命中率: 82.10%
Top 10 高回源 URL:
1204 /api/list?page=2
986 /product/a
命中率低的原因与排查顺序
结合上面脚本的输出,可以按以下顺序排查。这个顺序也意味着:先排除统计口径问题,再处理缓存键,最后才动 TTL——避免把缓存策略越改越乱:
- 动态接口混入统计。
/api/这类接口通常不应被缓存,先把它们从统计里剔除,或者给动态请求打上no-store。 - URL 带查询参数。
/product/a?from=wechat和/product/a?from=seo会生成不同的缓存键,导致同一份内容被缓存多份。此时可以借助缓存键策略合并参数。 - 缓存过期时间太短。HTML 页面 TTL 设置过短,热门页面反复过期回源,适当拉长 TTL 并开启源站盾能明显降低回源量。
- 未设置缓存规则。图片、CSS、JS 这些静态资源如果没走缓存规则,命中率很难上来。
把分析变成例行任务
上面这个脚本适合"出了问题再查",但更推荐把它变成例行任务:每天凌晨用 cron 跑一次,把命中率结果追加到一份日志里,低于阈值时通过邮件或企业微信通知。做法很简单,把 analyze_cdn_log 的返回值整理成一行文本,然后用 cron 定时执行:
# 每天 8 点跑一次,输出追加到结果文件
0 8 * * * cd /opt/cdn-stats && python3 analyze.py >> hit-rate.log
配合监控告警工具,命中率连续三天低于 80% 就自动提醒,回源量上涨的问题往往在演变成带宽账单之前就被发现了。
常见问题
为什么脚本统计的命中率和 CDN 后台对不上? 后台一般把 DYNAMIC、STALE 等状态单独归类,而脚本只数 HIT 和 MISS;想严格对齐,需要把 EXPIRED、REVALIDATED 也纳入分母。命中率 100% 就一定好吗? 不一定。如果动态接口也被缓存,命中率虽然漂亮,用户看到的却是旧数据。日志很大怎么办? 可以只用按天切分后的最近 24 小时日志,或者在脚本里通过 sys.argv 传入时间范围过滤。
最佳实践
- 把命中率指标接入日志分析监控,设置 70% 的告警阈值,低于阈值时自动提醒。
- 优化时先看 Top 回源 URL,优先处理贡献回源量最大的 20% 页面,往往就能把命中率拉回 90% 以上。
- 定期复核 CDN 的缓存策略,与业务改版同步更新。
- 回源流量本身也有成本,命中率优化直接关系到CDN 成本控制。
如果你想进一步了解 CDN 的整体选型与配置,可以参考CDN 加速分类下的其他文章。