ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CDN缓存策略实战:从命中率优化到资源更新避坑指南

CDN缓存策略实战:从命中率优化到资源更新避坑指南 接手过不少站点之后我有个越来越深的体会CDN这东西很多人把它当成“买了就能快”的加速器实际上它更像一个需要你反复调教的缓存系统。真正拉开体验差距的往往不是节点多少、带宽多大而是缓存策略怎么设计。这篇文章不聊PPT层面的概念就围绕CDN内容分发网络里最核心的缓存策略与优化实践把我实际配置过的参数、踩过的坑、调优的思路一次说清楚。尤其是那些“为什么这么配”的理由我会尽量讲透因为只有理解了背后的逻辑你才能举一反三而不是抄完配置走人。1. 缓存策略的整体设计思路1.1 缓存到底在解决什么问题CDN说白了就是把你的内容分发到离用户更近的节点上让用户不用千里迢迢跑到源站拉数据。但这里有个前提如果每个请求来了边缘节点都得回源站取一次数据那CDN跟普通转发没区别速度上不去源站压力也一点没减。所以缓存才是CDN的命根子。你可以把CDN节点想象成社区门口的小卖部。源站是城市中心的大仓库用户是楼上的住户。没有小卖部的时候每个人买东西都得跑一趟仓库路上花时间仓库也忙不过来。有了小卖部常见商品直接摆货架上住户下楼就买到只有货架缺货了店主才去仓库补货。CDN节点里的缓存就是小卖部的货架。但这里有个天然矛盾货架上的商品不能永远不变你得考虑商品会不会过期、会不会被下架。对应到CDN上就是缓存内容的新鲜度与命中率之间的平衡——缓存时间短了用户容易拿到新内容但回源频率高性能和成本都会变差缓存时间长了命中率上去了可一旦源站更新了内容用户还在看旧版本。很多团队的缓存问题根源就是没想清楚这个矛盾一刀切地设置缓存时间结果要么内容长期不更新被投诉要么命中率低到CDN白买。1.2 一条请求在CDN上的全流程要把缓存策略做好先得知道一个请求是怎么走完的。简单概括一下用户发起请求浏览器先做DNS解析。DNS调度系统根据用户的地理位置、运营商、节点负载返回一个最优的边缘节点IP。请求到达边缘节点后CDN会检查这个URL对应的内容是否已经在缓存中。如果命中缓存直接返回给用户整个过程完全不经过源站。如果没有命中边缘节点会回源站请求内容拿到之后存入本地缓存并返回给用户。后续同样的请求就能直接命中。这里面有几个关键概念你需要烂熟于心命中率命中的请求数占总请求数的百分比。这是衡量CDN缓存效果的核心指标一般静态资源命中率要做到95%以上才算正常。回源率回源请求占总请求的比例命中率越高回源率越低。TTL缓存有效期也就是内容在节点上允许被缓存多久到时间后节点会主动向源站确认内容是否变化。缓存KeyCDN判断请求是否命中时用的唯一标识通常是URL本身。这里面的坑非常大后面单独讲。你可以用一条命令快速看一个资源有没有被CDN缓存curl -I https://你的域名/static/js/app.js看响应头里有没有Age字段以及Via、X-Cache这样的CDN标记。有Age说明是从CDN缓存返回的Age的值就是从写入缓存到当前经过了多久。1.3 设计缓存策略时的三个核心权衡做缓存策略本质上是在三个维度之间做取舍新鲜度、性能、成本。新鲜度要求内容及时更新性能要求用户访问快成本要求回源带宽和源站压力小。这三个目标天然冲突你不可能同时做到极致。我的经验是先给内容分类再对每一类分别定策略。比如静态资源图片、CSS、JS、字体等这类内容基本不变或者文件名里带版本号可以给很长的缓存时间甚至365天。页面HTML内容会频繁更新但也不能完全不缓存通常会设置一个较短的缓存周期同时配合条件请求做校验。API接口如果要缓存得按接口的业务特性区分有些只读接口可以缓存几秒钟到几分钟涉及用户隐私的接口则绝不能缓存。没有一种通用配置能适配所有业务设计缓存策略的第一步永远是盘点你的资源类型而不是急着去控制台点按钮。2. 核心缓存机制的细节与实操要点2.1 HTTP缓存头体系是怎么运作的CDN节点的缓存设置最终都要落到HTTP协议上。理解这套机制你才真正掌握主动权。Cache-Control是现在的主宰。它有几个关键指令max-age3600表示这个响应可以被缓存3600秒。s-maxage3600专门给共享缓存用的优先级高于max-age。CDN节点属于共享缓存浏览器缓存属于私有缓存这个区分非常重要。public/privatepublic表示任何缓存都可以存private表示只能存到浏览器CDN节点不能缓存适合带用户信息的响应。no-cache名字容易误导人它不是说不能缓存而是说每次使用前必须去源站验证是否过期。no-store这才是真正禁止所有缓存响应不落盘一点都不能存。must-revalidate缓存过期后必须回源验证不能使用过期内容。还有一个有点年头的Expires头它给的是一个绝对过期时间。现在主要看Cache-Control如果两个都设置Cache-Control优先。只设置了Expires的CDN控制台老配置劝你尽早迭代掉因为客户端时钟不准会导致缓存时间不可控。ETag和Last-Modified的作用是让CDN节点做条件回源。节点上的缓存过期后不会傻乎乎地把内容整个拉回来而是带着If-None-Match或If-Modified-Since去问源站我的缓存版本还在吗如果源站返回304 Not ModifiedCDN节点就可以继续用旧缓存同时刷新TTL。这比重新拉取全部内容省很多流量。一个典型的响应头组合长这样HTTP/1.1 200 OK Cache-Control: public, max-age31536000, immutable ETag: 5d8c4a4e2f1a6b8c9d0e1f2a Last-Modified: Tue, 15 Nov 2024 08:00:00 GMT这里加了immutable属于给浏览器看的提示告诉它这个文件在过期之前绝对不会变可以放心大胆地用强缓存。2.2 CDN控制台上的缓存规则配置明白了HTTP头体系还要会跟CDN平台的规则配置对齐。主流CDN平台的缓存配置方式大同小异无非是按目录、按文件后缀、按全路径匹配来设置规则规则之间一般有优先级越具体的匹配优先级越高。我建议你在控制台里至少配三到五条规则覆盖你站点的核心路径/static/目录缓存365天强制覆盖源站Header这点很关键后面说。*.js、*.css缓存30天配合文件名版本号使用。*.jpg、*.png、*.webp缓存30天。/api/、/user/开头的接口缓存0秒或极短时间。首页HTML缓存60秒开启协商缓存。这里注意一个操作习惯在CDN控制台设置缓存规则时有个“是否遵循源站Header”的选项。如果你源站配的Cache-Control有冲突比如页面程序默认输出了no-cacheCDN就不会缓存。我的建议是静态资源路径果断开启“覆盖源站Header”用CDN规则强制指定缓存时长但HTML和动态接口最好遵循源站输出不要把事做绝。缓存Key的设置值得单独花时间。默认情况下CDN把整个URL作为缓存Key包括查询参数。也就是说?id1和?id2是两个完全不同的缓存。如果你的URL带了随机参数比如时间戳、统计参数缓存命中率会非常难看。很多CDN平台支持“忽略查询参数”或者“按指定参数生成缓存Key”。我用过的实践是这样的静态资源直接忽略全部Query参数app.js?v1和app.js?v2都能命中同一个缓存。前提是你的更新靠文件名而不是靠Query参数。动态页面保留关键参数ignore掉无用参数需要你在控制台上把“缓存Key需要保留的Query参数”列出来。带分页、筛选的列表接口这类最好把参数完整保留否则不同用户看到的数据会串。2.3 缓存穿透、雪崩与失效风暴缓存不是万能的设计不当会引入更多问题。我实际遇到最多的是这三个缓存穿透指请求的URL始终不在缓存里每次都回源。常见场景是有人恶意刷不存在的URL或者你的URL带了随机参数且没忽略。穿透会导致源站被打爆CDN形同虚设。对策就是配置“404状态码缓存”让不存在的资源也能被缓存一小段时间同时配合CDN的频控规则拦截异常请求。缓存雪崩大量缓存在同一时间点过期回源请求瞬间飙升源站扛不住。我记得最惨的一次是把一批图片全设成了24小时缓存业务方凌晨统一更新了一批资源CDN节点同一秒全部失效源站带宽直接跑满整个业务抖动了好几分钟。对策有三板斧第一把缓存过期时间打散给TTL加个随机偏移量比如3600秒基础上加0到300秒的随机值第二对核心资源做预热在更新前手动把资源推到节点上第三源站加回源限流超限直接拒绝并返回旧缓存。失效风暴跟前两者类似本质是热点资源失效导致的大量回源。说个真实案例某个电商活动页挂了一张主图CDN缓存设了10分钟结果活动一开始所有城市的节点差不多同时过期回源源站一瞬间涌入几千个请求。解决办法是把活动页主图缓存拉长到活动结束或者提前做全网预热。3. 按内容类型的缓存配置方案3.1 静态资源带指纹的长缓存是标准答案图片、CSS、JS、字体这类资源优化空间最大也最容易出效果。核心思路就一句话给文件名打上版本指纹然后放心大胆地设置长缓存。所谓指纹就是在文件名里加内容哈希比如app.e3b0c442.js。内容变了文件名就变了浏览器和CDN会把它当成一个全新的文件来请求。内容没变文件名就一直不变缓存可以放得很长。我通常的配置是这样的# 源站Nginx配置为指纹资源设置长缓存 location /static/ { add_header Cache-Control public, max-age31536000, immutable; add_header ETag ; try_files $uri $uri/ 404; }这里注意我把ETag去掉了因为文件名指纹本身就是版本标识ETag的协商验证毫无意义反而多一次304请求的开销。如果你用的是构建工具Webpack、Vite这些默认就会生成带哈希的文件名天然适合这套策略。大图片、音视频这类体积大的文件除了长缓存还要注意开启动态压缩和Range回源支持。Range是HTTP的一个能力允许客户端只请求文件的一部分视频拖动进度条靠的就是它。如果你的CDN节点缓存了整段视频回源时也支持Range转发用户体验会顺畅很多。3.2 HTML页面短缓存加协商缓存HTML是所有资源的入口用户一进来先加载的就是它。HTML缓存时间不能太长因为页面内容更新是常态但你也不能完全不缓存那样首屏速度会受影响。我的通用做法是HTML设30到120秒的短缓存同时开启协商缓存能力。源站输出Cache-Control: public, max-age60。同时保留ETag或Last-Modified。CDN节点缓存过期后会带着条件请求头回源源站返回304就会复用缓存。这样做的效果是页面更新后最多延迟一两分钟全网生效日常访问大部分能命中缓存或走304验证回源流量控制得住。如果你用的是服务端渲染框架建议在应用层设置响应头而不是统一在反向代理上粗暴配置。因为不同页面的敏感程度可能不一样比如搜索结果页和落地页的缓存策略就不能一样。3.3 API接口与动态内容怎么处理接口能不能缓存这个问题经常有人问我的答案是有条件地缓存但需要想清楚边界。适合缓存的接口特征是数据变化频率低、不涉及用户隐私、对实时性要求不高。比如配置信息、类目树、城市列表、公告内容这些完全可以缓存几分钟。不适合缓存的接口特征是涉及登录状态、用户个性化数据、库存价格、操作类接口。这类如果被CDN缓存就会出现用户A的数据返回给用户B的严重事故。如果你实在想让某些动态接口“快起来”思路不是死磕缓存而是让请求离源站更近、让源站处理更快。比如开启CDN的TCP优化和HTTP/2/3支持。源站启用动态回源加速走专线回源而不是公网。接口本身做数据压缩响应体小一圈传输时间能省很多。我见过一个团队为了“缓存接口”付出了惨痛代价一个查询用户订单详情的接口被误配了10分钟缓存CDN边缘节点上直接缓存了用户A的订单数据结果同IP下的用户B也能看到。排查了很久才定位到是缓存Key没区分用户。从那以后凡是带用户态的接口我都在源站强制设置Cache-Control: private, no-store从源头掐断缓存的可能性。3.4 缓存Key的分场景设计细说缓存Key是整个缓存架构里最容易被低估的一环。同一个URL如果内容会因某些因素变化而这些因素没有体现在缓存Key里就会出现串数据。拿我之前那个场景举例页面URL一样但用户角色不同看到的内容不同。CDN层面看到的是同一个URL只会缓存第一份内容后面所有角色的用户都拿到同一份。解决办法是要么这类页面不缓存要么把区分维度拼接到URL上让不同角色访问不同URL。还有一种常见坑同一资源同时有http和https两个入口部分CDN平台默认缓存Key区分协议还有example.com和www.example.com如果不做归一化也会产生两份缓存浪费空间还影响命中率。建议你在上线前专门做一次梳理哪些URL是同一份内容但可能被多种形式访问统一之后域名、协议、大小写、Query参数都尽量归一化。4. 命中率优化与全链路观测4.1 提升命中率的五个可落地手段配置做得没问题之后就是持续优化命中率的阶段了。说几个我验证过有效的手段第一统一URL规范。检查全站的URL生成逻辑把动态参数、统计参数从静态资源URL中移除不要用Query参数区分版本改用文件名版本号。这一步做好了命中率能提5到10个百分点。第二合理拉长TTL。很多命中率低的原因不是CDN不行而是缓存时间设太短。CSS设60秒剧本重演了。静态资源放心拉长时间带指纹的文件设一年都没问题。第三善用预热。大促、活动、新版本发布前把核心资源手动预热到CDN节点。控制台上一般都有“URL预热”功能输入URL系统会主动回源拉到节点上缓存好。这能规避发布瞬间的缓存穿透。第四配置分层缓存。部分CDN提供多级缓存父层节点可以缓存更多内容边缘层未命中还会去父层找一层再回源能显著降低回源量。如果你的CDN支持建议开启。第五定期刷新与清理策略搭配。刷新接口要慎用一键强制刷新全站对源站和CDN都是灾难。正确做法是定向刷新只清更新的那几路URL。4.2 怎么通过日志和数据发现缓存问题优化没有数据支撑就是盲人摸象。先学会看几个核心指标命中率越高越好静态资源95%以上及格98%以上健康。回源带宽回源流量占用户实际流量的比例比例越高说明CDN发挥的作用越小。回源QPS源站每秒承受的CDN回源请求数动态接口需要重点观察。命中QPS边缘节点直接响应了多少请求。大部分CDN控制台自带这些图表但如果你想自己分析日志常见的日志格式里会包含命中标记字段。比如腾讯云CDN的日志里每一条记录会有一个hit字段1代表命中0代表未命中。你可以写个简单脚本统计# 统计某一天的总请求数和命中率 awk -F {if($111) hit; total} END {print 命中率:, hit/total}注意不同平台的日志字段顺序不一样先打开一条日志确认字段含义。另外只盯着整体命中率是不够的要按URL维度拆把命中率低的Top 50 URL拉出来逐个看是缓存Key问题还是TTL问题。这一步是排查效率最高的动作。4.3 稳定性与安全兜底配置再好也得防着意外。说几个我踩过之后沉淀下来的底线操作源站必须配回源超时和重试限制比如源站5秒无响应CDN直接返回502而不是无限等同时重试次数控制在1次防止雪崩叠加。HTTPS一定全程开启源站要支持HTTP/2证书统一管理。HTTPS的握手开销在动态请求上感知很明显开启会话复用能缓解不少。对源站实施限流和黑白名单。CDN的IP段一般有公示源站只放行CDN回源IP其他直接拒绝能防掉大量恶意直连和扫描。开启CDN的CC防护、WAF这些增值功能。正常业务流量进来CDN已经帮忙挡了一层剩下打到你源站的请求都值得怀疑。5. 常见问题与排查技巧实录5.1 缓存错乱用户看到别人的数据这类事故最吓人但根源通常不复杂。按照严重程度排查优先级是这样先看接口是否被设置了缓存。如果你在CDN控制台或源站把某个带用户态的接口配了缓存赶紧关掉。在源站响应里加Cache-Control: private, no-store是根治法。再看是否缺了Vary响应头。Vary字段的作用是告诉缓存这个URL的响应内容会根据某个请求头的变化而变化。比如页面有压缩版和未压缩版CDN需要根据Accept-Encoding区分缓存如果页面按不同语言输出需要根据Accept-Language区分。缺了Vary浏览器或CDN就可能把一种内容缓存去服务另一种内容的请求反应出来就是错乱。我建议在源站层面对动态页面统一处理# 动态页面默认输出不缓存 location ~ \.php$ { add_header Cache-Control private, no-store; }5.2 内容更新不生效清缓存根本没用“我明明刷新了CDN缓存为什么用户还是看到旧页面”这是出现频率最高的问题。大概率是下面三个原因之一刷新的是边缘节点的缓存但浏览器本地还有一层强缓存没过期。用户浏览器直接用了本地旧缓存根本没去CDN请求。这种情况需要把静态资源的缓存时间调成合理范围或者让前端提示强刷CtrlF5。刷新操作没覆盖到你实际访问的URL。比如你访问的是https://www.example.com/index.html但刷新的时候刷了https://example.com/index.html这俩在CDN看来是两个URL。CDN刷新是异步的节点逐级生效高峰期可能等几十秒甚至几分钟。刷新完不要立刻验证过一分钟再测。排查时可以分角色验证先用无痕模式看是否命中CDN再看源站直接访问是否正常。如果源站正常、CDN正常那就是浏览器缓存的问题。# 无痕模式 强制带时间戳绕过缓存查看回源情况 curl -H Cache-Control: no-cache -I https://你的域名/5.3 命中率低得离谱从哪里查如果日志显示命中率只有30%不要急着调TTL先按这个顺序排查检查是不是启用了“忽略查询参数”。如果没启用而你的系统在下发URL时自动加了时间戳那每一个请求都是一个新URL命中率必然低。检查缓存规则是否包含目标路径。很多控制台的规则是按目录匹配的你写的是/static/但资源实际在/assets/下面规则形同虚设。检查源站返回的响应头是否把缓存禁用了。源站程序里如果有header(Cache-Control: no-cache)这类逻辑CDN不管控制台设多久都没用。我遇到过一个隐蔽的情况源站Nginx对静态文件追加了Last-Modified但忽略了ETag而CDN恰好依赖ETag做缓存命中后的校验。设置不匹配导致每次都要回源命中率上不去。后来统一使用ETag问题才解决。5.4 回源超时与慢请求排查命中率正常但偶尔有几个接口特别慢观察日志发现慢在回源阶段。回源慢的原因分类排查源站带宽本身满了打开源站的监控面板看外网出带宽是不是持续打满。源站处理逻辑慢SQL慢查询、外部接口调用超时这类问题加再多的CDN也救不了。回源链路差CDN节点和源站之间的链路不稳定可以考虑用源站就近的节点池或开启CDN的动态回源加速。排查回源性能时有个小技巧直接从CDN日志里筛出回源时间超过500ms的请求按回源IP分组看是不是集中在某个区域的节点。如果是大概率是该区域节点到你源站的链路质量有问题。5.5 常见问题速查表症状可能原因第一排查动作命中率长期低于90%URL带随机参数、规则未覆盖检查缓存Key和规则匹配路径更新后用户看旧内容浏览器缓存未过期、刷新源URL不一致无痕模式复现检查缓存时间用户看到别人的数据动态接口被缓存、缺Vary头在源站设置private no-store大促瞬间源站被压垮缓存同时过期、未预热打散TTL活动前批量预热刷新缓存后很久才生效CDN刷新异步生效、多层级节点刷新后等1分钟再验证回源请求缓慢源站带宽打满、处理逻辑慢查看源站监控定位瓶颈写在后面的一些体会这几年的CDN调优做下来我最深的一个感受是缓存策略不是配置层面的问题而是业务建模的问题。你不能只问“CDN控制台怎么填”你得先问“我的内容多久变一次、谁能看、变了怎么通知用户”。把这三个问题想透了配置反而是水到渠成的事。另外强调一点每一个参数调整都要留痕、可回滚。我早期优化时习惯一次性改一堆规则结果出问题都不知道是哪一步引入的。后来规范了流程一次只改一个维度记录前后命中率、回源带宽、错误码变化。这样每次调整都是可验证的经验也才能真正沉淀下来。最后送一个实用小习惯给缓存相关的变更单独做一份变更清单包含时间、负责人、改动内容、前后指标截图。等你做久了回头翻会发现很多可复用的优化模式比任何文档都有价值。
返回列表