CDN优化实战:从命中率30%到95%的血泪史,AWS CloudFront踩坑全记录
最近客户cdn命中率异常,让我优化下缓存策略。经过一番折腾,总算把命中率提上来了!!!今天就把这些年踩过的坑和一些实用的经验分享给大家。
其实CDN优化这事儿,说简单也简单,说复杂也复杂。很多人以为接入个CDN就万事大吉了,结果发现命中率低得可怜,钱花了不少效果却不明显。我之前就遇到过这种情况,CDN命中率只有30%多,简直是在烧钱。
先说说CDN的那些关键指标
做CDN优化,你得先知道怎么看数据。就像开车要看仪表盘一样,不然怎么知道车子跑得怎么样?
命中率(Hit Ratio)
这个是最重要的指标,没有之一。命中率就是CDN直接返回缓存内容的请求占总请求的比例。一般来说,静态资源的命中率应该在90%以上,如果你的命中率只有50-60%,那肯定有问题。
在AWS CloudFront里查看命中率很简单,进入CloudFront控制台,选择你的分发,然后点击"Monitoring"标签页。这里会显示Cache hit rate的图表,可以看到不同时间段的命中率变化。
回源率(Origin Request Rate)
这个和命中率是相对的,回源率高说明CDN经常要去源站拿数据,这样就失去了CDN的意义。正常情况下回源率应该控制在10%以下。
响应时间
包括CDN响应时间和回源响应时间。CDN响应时间一般应该在100ms以内,如果超过这个数值就要检查是不是节点选择有问题。
我记得有次优化一个电商网站,发现图片加载特别慢。一查监控发现CDN响应时间居然有2秒多,后来才发现是缓存策略配置错误,每次都要回源验证。
带宽使用情况
这个主要是看成本,CDN的费用很大一部分就是带宽费用。通过监控带宽使用情况,可以了解流量分布,优化成本。
在AWS CloudFront的监控面板里,你还能看到:
- Origin latency(回源延迟)
- Error rate(错误率)
- Requests(请求数)
- Bytes downloaded(下载字节数)
这些数据都很有用,特别是错误率,如果突然飙升可能是源站出问题了。
命中率低的常见原因和解决方案
说到命中率低,我遇到过各种奇葩的情况。
缓存策略配置不当
这是最常见的问题。很多人图省事,直接用默认配置,结果发现很多本该缓存的内容都没有缓存。
比如说,有些动态参数会导致URL不同,CDN就认为是不同的资源。像这种情况:
https://example.com/image.jpg?timestamp=1234567890
https://example.com/image.jpg?timestamp=1234567891
CDN会认为这是两个不同的文件,实际上内容是一样的。解决办法是在CloudFront的Behavior设置里配置Query String参数处理,可以选择忽略某些参数或者只转发特定参数。
TTL设置太短
Time To Live设置太短会导致缓存频繁过期。我见过有人把图片的TTL设置成5分钟,这不是瞎搞吗?静态资源的TTL至少要设置几个小时,甚至几天。
在CloudFront里可以设置三种TTL:
- Minimum TTL:最小缓存时间
- Maximum TTL:最大缓存时间
- Default TTL:默认缓存时间
一般我会这样设置:
- 图片、CSS、JS等静态资源:24小时到7天
- HTML页面:1-6小时
- API接口:根据业务需求,可能几分钟到几小时
源站响应头配置问题
有时候源站返回了不合适的Cache-Control头,比如no-cache或者max-age=0,这会导致CDN不缓存内容。
我之前遇到过一个案例,开发在Nginx配置里给所有静态资源都加了no-cache,结果CDN命中率惨不忍睹。后来改成这样:
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
expires 7d;
add_header Cache-Control "public, immutable";
}
预热不充分
新上线的CDN或者清除缓存后,需要一段时间来"预热"。这期间命中率会比较低,这是正常现象。但如果长时间命中率都上不去,就要检查其他配置了。
CloudFront支持手动预热,可以通过Invalidation功能清除缓存,然后用脚本批量访问重要资源来预热缓存。
CDN优化策略详解
现在说说aws托管的缓存策略
AWS CDN托管的缓存策略详细说明
策略名称 | 缓存内容 | 不缓存内容 | 使用场景 |
---|---|---|---|
Amplify | 缓存大多数静态资源和部分动态内容 | 不缓存包含特定身份验证头的请求 | Amplify应用程序的通用缓存策略 |
Amplify-Default | 缓存静态资源和基于常用头部的内容,保留某些Cookie | 不缓存带有特定认证头的请求,某些动态API响应 | Amplify应用的标准配置,平衡缓存与个性化 |
Amplify-Default-V2 | 与Amplify-Default类似,但有优化的TTL设置 | 与Amplify-Default类似 | 需要改进的缓存性能的Amplify应用 |
Amplify-DefaultNoCookies | 缓存静态资源和页面内容 | 不缓存任何Cookie信息,不缓存认证请求 | 不需要基于Cookie区分用户的Amplify应用 |
Amplify-DefaultNoCookies-V2 | 与DefaultNoCookies类似,但有更长的TTL | 与DefaultNoCookies类似 | 追求更高缓存命中率的静态Amplify应用 |
Amplify-ImageOptimization | 缓存图像文件(jpg, png, gif等),支持不同的图像格式 | 不缓存非图像内容,不保留大多数请求头 | 图像密集型网站,图片库,电商产品展示 |
Amplify-ImageOptimization-V2 | 与ImageOptimization类似,增加了WebP等现代格式支持 | 与ImageOptimization类似 | 需要现代图像格式支持的应用 |
Amplify-StaticContent | 缓存JS, CSS, 字体, 图像等静态资源,长TTL | 不缓存HTML或动态内容,不保留Cookie | 静态资源的长期缓存,提高重复访问性能 |
Amplify-StaticContent-V2 | 与StaticContent类似,但有更优化的压缩设置 | 与StaticContent类似 | 需要最佳静态资源性能的应用 |
CachingDisabled | 不缓存任何内容 | 所有内容都直接从源站获取 | 实时数据,个性化内容,需要实时源站响应的场景 |
CachingOptimized | 缓存大多数内容,启用压缩,优化TTL | 不缓存POST/PUT请求,特定动态API路径 | 通用Web应用,需要平衡缓存与新鲜度的场景 |
CachingOptimizedForUncompressedObjects | 缓存未压缩的对象,自动应用适当的TTL | 不缓存已经压缩的内容,不处理某些动态请求 | 源站不提供压缩的情况,CDN负责压缩 |
Elemental-MediaPackage | 缓存视频分段,流媒体清单文件 | 不缓存用户特定的媒体请求,认证令牌 | 视频流媒体,直播,点播视频服务 |
UseOriginCacheControlHeaders | 根据源站的Cache-Control头决定缓存行为 | 如果源站指示不缓存,则不缓存;忽略查询参数 | 源站已有完善缓存策略的情况 |
UseOriginCacheControlHeaders-QueryStrings | 根据源站Cache-Control头和URL查询参数缓存 | 如源站指示不缓存则不缓存 | 基于查询参数提供不同内容且源站控制缓存的场景 |
AWS CloudFront 源请求策略详细说明
策略名称 | 转发内容 | 不转发内容 | 使用场景 |
---|---|---|---|
AllViewer | 转发所有查看者请求参数,包括查询字符串、标头和Cookie | 不限制任何参数的转发 | 需要将所有客户端请求信息传递给源站的场景,适用于高度依赖客户端信息的动态内容 |
AllViewerAndCloudFrontHeaders-2022-06 | 转发所有查看者请求参数和截至2022年6月的所有CloudFront标头 | 不限制任何参数或CloudFront标头的转发 | 需要完整客户端信息以及CloudFront添加的信息(如设备类型、地理位置)的场景 |
AllViewerExceptHostHeader | 转发除Host标头外的所有查看者请求参数 | 不转发Host标头 | 当源站需要使用不同的主机名处理请求,但仍需其他所有客户端信息的场景 |
CORS-CustomOrigin | 转发与CORS(跨源资源共享)相关的标头到自定义源 | 不转发与CORS无关的其他标头 | 需要支持跨域请求的自定义源站(非S3)场景,如API或Web应用 |
CORS-S3Origin | 转发与CORS相关的标头到S3源 | 不转发与CORS无关的其他标头 | 需要支持跨域请求的S3存储桶场景,如从浏览器直接访问S3资源 |
Elemental-MediaTailor-PersonalizedManifests | 转发Elemental MediaTailor个性化清单所需的标头 | 不转发与媒体个性化无关的标头 | 视频流媒体场景,特别是需要个性化内容或广告插入的流媒体服务 |
HostHeaderOnly | 仅转发Host标头到源站 | 不转发任何其他查看者请求参数 | 简单的静态内容分发场景,源站仅需要知道请求的主机名 |
UserAgentReferrerHeaders | 转发User-Agent(用户代理)和Referer(引用页)标头到源站 | 不转发其他查看者请求参数 | 源站需要了解访问者浏览器类型和来源网站的场景,如内容适配或引用统计 |
这些源请求策略控制CloudFront将哪些信息从查看者(客户端)请求中转发到源站。选择合适的策略可以优化源站请求处理,减少不必要的信息传递,同时确保源站获得所需的上下文信息来正确响应请求。
AWS CloudFront 响应标头策略详细说明
策略名称 | 添加/修改内容 | 不添加/不修改内容 | 使用场景 |
---|---|---|---|
CORS-and-SecurityHeadersPolicy | 添加CORS相关标头和安全标头到响应中 | 不修改源站提供的其他标头 | 需要同时支持跨域请求和增强安全性的Web应用,如需要跨域访问的API且需要防XSS、点击劫持等安全保护 |
CORS-With-Preflight | 添加CORS相关标头并支持预检(OPTIONS)请求 | 不添加安全标头,不修改源站提供的其他标头 | 需要支持复杂跨域请求的场景,特别是涉及非简单请求(如带自定义标头或使用PUT/DELETE方法)的API |
CORS-with-preflight-and-SecurityHeadersPolicy | 添加CORS相关标头(含预检支持)和安全标头 | 不修改源站提供的其他标头 | 需要全面CORS支持(包括复杂请求)和安全增强的Web应用,如SPA(单页应用)与后端API交互 |
SecurityHeadersPolicy | 仅添加安全相关标头到每个响应 | 不添加CORS标头,不修改源站提供的其他标头 | 注重安全性但不需要跨域支持的网站,如企业内部应用或对安全有高要求的公共网站 |
SimpleCORS | 仅添加基本CORS标头,支持简单跨域请求 | 不添加安全标头,不支持预检请求,不修改源站提供的其他标头 | 只需基本跨域支持的简单场景,如只允许GET请求的公共API或静态资源 |
这些响应标头策略控制CloudFront如何修改从源站返回给客户端的HTTP响应标头。适当的策略可以增强Web应用的安全性,启用跨域资源共享功能,而无需修改源站配置。安全标头通常包括内容安全策略(CSP)、X-XSS-Protection、X-Frame-Options等,用于防止常见的Web安全威胁。
当然要是这些不满足还可以自定义!!
其他一些缓存策略
分层缓存策略
不同类型的资源用不同的缓存策略。我一般会创建多个Behavior,针对不同的路径模式设置不同的缓存规则:
/api/* - TTL: 5分钟,转发所有参数
/images/* - TTL: 7天,忽略查询参数
/css/* - TTL: 1天,启用压缩
/js/* - TTL: 1天,启用压缩
/* - TTL: 1小时,默认行为
启用压缩
CloudFront支持自动压缩,可以显著减少传输数据量。在Behavior设置里开启"Compress Objects Automatically"就行了。不过要注意,压缩会消耗一些CPU资源,对于已经压缩过的文件(如图片)就没必要再压缩了。
合理使用Origin Shield
这是CloudFront的一个高级功能,相当于在CDN和源站之间加了一层缓存。对于回源比较频繁的场景很有用,可以减少源站压力。
我在一个视频网站项目中用过Origin Shield,效果很明显。因为视频文件比较大,用户分布又比较集中,开启Origin Shield后回源请求减少了60%多。
HTTP/2和HTTP/3支持
现在的CDN基本都支持HTTP/2了,CloudFront也不例外。HTTP/2的多路复用特性可以显著提升页面加载速度,特别是对于有很多小文件的网站。
智能路由
CloudFront会自动选择最优的边缘节点,但有时候你可能需要手动干预。比如某些地区的节点质量不好,可以通过地理位置限制来避免使用这些节点。
AWS CloudFront的一些实用技巧
用了这么久CloudFront,发现了一些比较实用的技巧。
Lambda@Edge
这个功能很强大,可以在CDN边缘节点运行代码。我用过几个场景:
- 动态修改响应头
- 根据用户地理位置返回不同内容
- 简单的A/B测试
- 图片格式转换(WebP支持检测)
比如这个检测WebP支持的例子:
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
const accept = headers.accept && headers.accept[0] && headers.accept[0].value;
if (accept && accept.includes('image/webp')) {
request.uri = request.uri.replace(/\.(jpg|jpeg|png)$/i, '.webp');
}
callback(null, request);
};
实时日志分析
CloudFront可以把访问日志实时推送到Kinesis Data Streams,然后用Lambda处理。我用这个功能做过实时监控,当错误率超过阈值时自动发送告警。
多源站配置
CloudFront支持配置多个源站,可以实现负载均衡和故障转移。我一般会配置主源站和备用源站,当主源站出问题时自动切换到备用源站。
自定义错误页面
通过配置Custom Error Pages,可以为不同的HTTP错误码返回自定义页面。这样用户体验会好很多,而且可以减少无效的回源请求。
监控和告警设置
CDN优化不是一次性的工作,需要持续监控和调优。
CloudWatch监控
CloudFront的所有指标都会推送到CloudWatch,可以设置各种告警:
- 命中率低于阈值告警
- 错误率超过阈值告警
- 回源延迟过高告警
- 带宽使用异常告警
我一般会设置这些告警规则:
命中率 < 85% 持续10分钟 -> 发送告警
4xx错误率 > 5% 持续5分钟 -> 发送告警
5xx错误率 > 1% 持续5分钟 -> 发送告警
回源延迟 > 2秒 持续5分钟 -> 发送告警
第三方监控工具
除了CloudWatch,我还会用一些第三方工具来监控CDN性能,比如Pingdom、GTmetrix等。这些工具可以从用户角度测试网站性能,发现一些CloudWatch监控不到的问题。
成本优化
CDN的费用不便宜,特别是流量大的网站。
Price Class选择
CloudFront有三个价格等级,包含的边缘节点数量不同。如果你的用户主要在北美和欧洲,选择Price Class 100就够了,没必要选择包含全球所有节点的Price Class All。
缓存策略优化
提高命中率不仅能改善性能,还能降低成本。因为CDN的费用主要是按流量计算的,命中率高意味着回源流量少,总体费用就低。如果命中率低就会出现流量放大效应:
• 正常命中率80%:1GB实际需求 = 1.25GB CDN流量
• 当前命中率可能<20%:1GB实际需求 = 5GB+ CDN流量
压缩和格式优化
启用压缩可以减少传输数据量,WebP格式的图片比JPEG小30-50%。这些优化都能直接降低CDN费用。
我之前优化过一个图片网站,通过启用压缩和WebP转换,CDN费用降低了40%多。
常见问题排查
做CDN优化经常会遇到各种问题,分享几个排查思路。
缓存不生效
首先检查响应头,看看有没有Cache-Control、Expires等缓存相关的头。然后检查CloudFront的Behavior配置,确认TTL设置正确。
可以用curl命令测试:
curl -I https://your-domain.com/test.jpg
看响应头里的X-Cache字段,Hit表示命中缓存,Miss表示没有命中。
某些地区访问慢
可能是CDN节点选择有问题。用不同地区的VPS测试访问速度,确定是哪些地区有问题。然后检查是不是需要调整Price Class或者配置地理位置限制。
缓存穿透
如果某个资源一直无法缓存,可能是URL参数或者响应头有问题。检查是不是有随机参数,或者源站返回了no-cache头。
我遇到过一个奇葩问题,开发在图片URL后面加了随机数防止缓存,结果CDN命中率为0。后来改成用版本号,问题就解决了。
总结
CDN优化是个系统工程,需要从多个维度来考虑。命中率是最重要的指标,但不是唯一的指标。要结合业务特点,制定合适的缓存策略。
AWS CloudFront功能很强大,但配置也比较复杂。建议先从基础配置开始,逐步优化。不要一开始就搞得很复杂,容易出问题。
最重要的是要持续监控和优化,CDN不是配置好就不管了。用户行为会变化,业务需求也会变化,CDN策略也要跟着调整。
我现在维护的几个网站,CDN命中率都在95%以上,用户访问速度提升了3-5倍。虽然前期折腾了不少时间,但效果还是很明显的。
如果你也在做CDN优化,遇到问题可以多交流。这块水还是挺深的,大家一起学习进步。
如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!
公众号:运维躬行录
个人博客:躬行笔记