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——避免把缓存策略越改越乱:

  1. 动态接口混入统计/api/ 这类接口通常不应被缓存,先把它们从统计里剔除,或者给动态请求打上 no-store
  2. URL 带查询参数/product/a?from=wechat/product/a?from=seo 会生成不同的缓存键,导致同一份内容被缓存多份。此时可以借助缓存键策略合并参数。
  3. 缓存过期时间太短。HTML 页面 TTL 设置过短,热门页面反复过期回源,适当拉长 TTL 并开启源站盾能明显降低回源量。
  4. 未设置缓存规则。图片、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 加速分类下的其他文章。