一句话:CDN 把网站或游戏的静态文件(图片、安装包、补丁、视频)提前复制到全国乃至全球各地的服务器上,玩家从离自己最近的那台下载,速度更快,源服务器的压力也更小。
一、生活里的例子:电商的"前置仓"
一家网店的总仓库在广州:
- 没有前置仓:哈尔滨的用户下单,货从广州发,要好几天;"双十一"时总仓库门口的车排成长龙
- 有前置仓:提前把热门商品运到各城市的小仓库,哈尔滨的用户下单,从本地仓发货,当天到
| 电商 | CDN |
|---|---|
| 广州总仓库 | 源站(Origin):你自己的服务器 |
| 各城市的前置仓 | 边缘节点(Edge) |
| 热门商品 | 被频繁访问的文件 |
| 本地仓有货,直接发 | 缓存命中(Hit) |
| 本地仓没货,从总仓调 | 回源(Miss) |
| 订单系统决定从哪个仓发货 | 调度系统(通常靠 DNS) |
二、没有 CDN 会遇到什么问题
假设游戏服务器在上海,发布了一个 2GB 的更新包:
| 问题 | 原因 |
|---|---|
| 远距离延迟高 | 光在光纤里每 1000 公里大约要 5ms(单程),国外玩家往返动辄 150ms+,TCP 下载要来回很多次 |
| 源站带宽被挤爆 | 10 万人同时下载 2GB,源站出口带宽根本不够 |
| 跨运营商慢 | 服务器在电信,联通、移动的玩家访问要绕路 |
| 单点故障 | 源站一挂,所有人都下载失败 |
| 容易被攻击 | DDoS 流量直接打到源站 |
带宽这个问题最致命:延迟可以忍,下载不动才是真的玩不了。
三、CDN 由哪几部分组成
┌──────────────┐ │ 源站 Origin │ ← 你自己的服务器 / 对象存储(OSS、S3) └──────┬───────┘ │ 回源 ┌────────┴────────┐ │ 中间层(回源保护)│ ← 汇总各地的回源请求,保护源站 └───┬────────┬────┘ ┌────────┘ └────────┐ ┌────┴────┐ ┌────────┐ ┌────┴────┐ │北京节点 │ │广州节点 │ │成都节点 │ ← 边缘节点:直接面对玩家 └────┬────┘ └───┬────┘ └────┬────┘ 玩家 玩家 玩家 调度系统(DNS / Anycast):决定每个玩家连哪个节点| 组成部分 | 作用 |
|---|---|
| 源站 | 存着文件的"原件",CDN 缓存没有时来这里取 |
| 边缘节点 | 分布在各城市、各运营商的缓存服务器,玩家直接从这里下载 |
| 中间层 | 多个边缘节点同时缺同一个文件时,只让一个请求去源站(也叫 Origin Shield) |
| 调度系统 | 根据玩家的位置、运营商、节点负载,选出最合适的节点 |
四、一次请求的完整流程
玩家要下载https://patch.mygame.com/v1.2/data.bundle:
1. 玩家电脑:patch.mygame.com 是哪个 IP? → 问本地 DNS 2. DNS:patch.mygame.com 是一个别名(CNAME) → 指向 patch.mygame.com.cdn-vendor.net 3. CDN 的调度 DNS: 查看"来问的人"在哪:广东、电信 → 返回广州电信节点的 IP:203.0.113.25 4. 玩家连接 203.0.113.25,发起 HTTP 请求 5. 广州节点查缓存: ├─ 有,且没过期 → 直接返回(命中,最快) └─ 没有 / 已过期 → 去中间层或源站取 → 存一份 → 返回给玩家(回源) 6. 下一个广东电信玩家来要同一个文件 → 直接命中用dig(或 Windows 上的nslookup)可以亲眼看到第 2、3 步:
$ dig patch.mygame.com patch.mygame.com. 300 IN CNAME patch.mygame.com.cdn-vendor.net. patch.mygame.com.cdn-vendor.net. 60 IN A 203.0.113.25接入 CDN 的关键动作,通常就是在 DNS 里加一条 CNAME。你的代码和 URL 都不用改,这就是"接口不变、实现替换"。
不同地区的人查同一个域名,得到的 IP 不一样 —— 这就是 CDN 的"就近"。
另一种调度方式:Anycast
| 方式 | 原理 | 特点 |
|---|---|---|
| DNS 调度(国内常见) | DNS 根据来源返回不同 IP | 灵活,能按运营商细分;但 DNS 有缓存,切换不够快 |
| Anycast(Cloudflare 等) | 全球很多节点共用同一个 IP,路由器自动把包送到"最近"的那个 | 切换快,天然抗 DDoS;对路由控制要求高 |
五、缓存规则:存多久?
CDN 节点判断"缓存还能不能用",主要看源站返回的HTTP 响应头。
最重要的头:Cache-Control
Cache-Control: public, max-age=31536000, immutable| 写法 | 意思 |
|---|---|
max-age=600 | 浏览器和 CDN 都可以缓存 600 秒 |
s-maxage=3600 | 只对 CDN 等共享缓存生效,优先于 max-age |
no-cache | 可以存,但每次用之前都要问源站"变了没有" |
no-store | 完全不许缓存 |
private | 只能浏览器存,CDN 不许存(个人数据) |
immutable | 这个文件永远不会变,过期前不用再问 |
"变了没有"怎么问:304
CDN → 源站: GET /config.json If-None-Match: "abc123" ← 我手上这份的指纹 源站 → CDN: 304 Not Modified ← 没变,继续用你那份(只回一个头,不传内容) 或 200 OK + 新内容 + ETag: "def456" ← 变了,给你新的不同文件,不同策略
| 文件类型 | 建议 TTL | 原因 |
|---|---|---|
文件名带哈希的资源包(data_8f3a2c.bundle) | 1 年 | 内容变了文件名就变,旧名字永远不会改 |
| 图片、音频、视频 | 几天~几个月 | 很少变化 |
版本清单 / 目录文件(version.json、catalog.json) | 很短或不缓存 | 它决定玩家"要不要更新",必须及时 |
| 公告、活动配置 | 1~5 分钟 | 要能较快生效 |
| 登录、支付、玩家数据接口 | 不缓存 | 每个人都不一样 |
六、最重要的工程技巧:用文件名做版本号
文件更新后,CDN 上还缓存着旧的,怎么办?
| 方法 | 做法 | 问题 |
|---|---|---|
| 刷新(Purge) | 在 CDN 控制台提交 URL,让各节点删掉缓存 | 全球节点生效要几分钟;有次数限制;浏览器、客户端本地的缓存删不掉 |
加参数data.bundle?v=2 | 改参数让 URL 变化 | 依赖 CDN 把参数算进缓存键,有的配置会忽略参数 |
✅文件名带内容哈希data_8f3a2c.bundle | 内容一变,文件名就变 | 几乎没有问题,业界标准做法 |
带哈希文件名的好处:
旧版本: version.json → 指向 data_8f3a2c.bundle (缓存 1 年) 新版本: version.json → 指向 data_c91e47.bundle (新文件,第一次访问时回源) ↑ 只有这一个小文件需要"不缓存/短缓存"原则:大文件"永不变、永久缓存",只让一个很小的入口文件保持新鲜。新旧版本的文件可以同时存在,玩家更新到一半也不会拿到混乱的组合。
还有一招:预热(Prefetch)
大版本发布前,提前让 CDN 把新文件拉到各节点。否则开服那一刻,几十万人同时触发回源,源站会被打垮 —— 这叫缓存击穿 / 回源风暴。
七、怎么衡量 CDN 用得好不好
| 指标 | 意思 | 目标 |
|---|---|---|
| 命中率 | 请求中直接从节点返回的比例 | 静态资源 > 95% |
| 回源率 / 回源带宽 | 有多少流量打到了源站 | 越低越好 |
| 首字节时间(TTFB) | 发出请求到收到第一个字节 | 命中时通常几十毫秒 |
| 下载速度 | 大文件的平均速度 | 按地区、运营商分别看 |
| 错误率 | 4xx / 5xx 比例 | 5xx 高通常是源站出问题 |
命中率低的常见原因:
| 原因 | 解决 |
|---|---|
URL 里带了随机参数(?t=时间戳) | 静态资源不要加随机参数;或配置 CDN 忽略参数 |
| TTL 太短 | 带哈希的文件放心设长 |
源站返回了no-store/private | 检查源站响应头 |
| 文件太冷门,很少人访问 | 正常现象,可以接受 |
八、CDN 附带的"额外福利"
| 功能 | 说明 |
|---|---|
| HTTPS | 证书部署在边缘节点,加密握手离玩家近,更快 |
| 抗 DDoS | 攻击流量被分散到大量节点吸收,源站 IP 可以隐藏 |
| 压缩 | 自动 gzip / brotli 压缩文本(json、js),注意已压缩的资源包别重复压 |
| 断点续传 | 支持Range请求,大文件下到一半断了可以接着下 |
| 防盗链 | 检查Referer,或使用签名 URL(带过期时间和签名,别人拿到链接也很快失效) |
| 回源 IP 白名单 | 源站只允许 CDN 的 IP 段访问 —— 上一篇子网掩码的/24写法在这里就派上用场 |
签名 URL 的大致样子:
https://cdn.mygame.com/vip/skin_01.bundle?expires=1735689600&sign=3f9a1c... sign = 哈希(密钥 + 文件路径 + expires)CDN 节点用同一个密钥算一遍,对得上、没过期才放行。密钥只在服务器端,客户端只拿到已签好的 URL。
九、在游戏里,CDN 用在哪
✅ 适合
| 内容 | 说明 |
|---|---|
| 安装包、大版本补丁 | 流量最大的部分,几乎必须用 CDN |
| 热更新资源(AssetBundle / Addressables) | 不发新版本也能更新美术、配置、关卡 |
| 版本清单、公告、活动配置 | 短 TTL |
| 头像、截图、回放文件 | 用户生成的内容 |
| 官网、活动页 | 传统 Web 加速 |
❌ 不适合
| 内容 | 原因 |
|---|---|
| 实时战斗同步 | CDN 缓存的是"大家都一样的文件",战斗数据每人每帧都不一样,而且多为 UDP |
| 登录、匹配、支付接口 | 动态请求,不能缓存 |
实时对战要的是另一类服务:游戏加速网络 / 全球加速(就近接入 + 专线回到游戏服务器)。它借用了 CDN"就近接入"的思路,但不做缓存,只负责走更稳的路线,降低延迟和抖动 —— 也就是减轻上一篇抖动缓冲区的压力。
十、实战:Unity 热更新的 CDN 流程
整体流程
游戏启动 │ ▼ 1. 下载 version.json(不缓存,或 TTL 很短) { "version": "1.2.3", "catalog": "catalog_c91e47.json" } │ ▼ 2. 和本地版本对比 → 一样?直接进游戏 │ 不一样 ▼ 3. 下载新的 catalog(文件名带哈希,可长期缓存) │ ▼ 4. 对比哪些资源包变了 → 只下载变化的(data_xxxx.bundle) │ ▼ 5. 校验文件哈希 → 全部通过 → 替换本地版本号 → 进游戏Addressables 的关键设置
| 设置 | 推荐 |
|---|---|
| Remote Load Path | https://cdn.mygame.com/[BuildTarget]/ |
| Bundle Naming Mode | Append Hash(文件名带哈希) |
| Catalog | 开启Build Remote Catalog;catalog 的.hash小文件配短 TTL |
| CDN 规则 | .bundle长缓存;.hash/version.json短缓存或不缓存 |
下载版本清单(简化示例)
usingSystem.Collections;usingUnityEngine;usingUnityEngine.Networking;publicclassVersionChecker:MonoBehaviour{conststringVersionUrl="https://cdn.mygame.com/android/version.json";[System.Serializable]classVersionInfo{publicstringversion;publicstringcatalog;}IEnumeratorStart(){usingvarreq=UnityWebRequest.Get(VersionUrl);// 告诉沿途缓存:要最新的(CDN 侧也应为这个文件配置短 TTL,双保险)req.SetRequestHeader("Cache-Control","no-cache");req.timeout=10;yieldreturnreq.SendWebRequest();if(req.result!=UnityWebRequest.Result.Success){Debug.LogWarning($"版本检查失败:{req.error},使用本地资源进入");yieldbreak;// 网络问题不应该让玩家完全进不了游戏}varinfo=JsonUtility.FromJson<VersionInfo>(req.downloadHandler.text);stringlocal=PlayerPrefs.GetString("res_version","0");if(info.version!=local){Debug.Log($"发现新版本{info.version},开始下载{info.catalog}");// 接着:下载 catalog → 计算差异 → 下载变化的 bundle → 校验哈希// Addressables 中可用 CheckForCatalogUpdates / UpdateCatalogs / DownloadDependenciesAsync}}}注意:这里没有在 URL 后面加
?t=时间戳来强制刷新。那样每次都会回源,CDN 命中率为零。正确做法是在 CDN 上给这个文件配短 TTL。
大版本日的流量规划
| 措施 | 作用 |
|---|---|
| 提前预热新资源 | 避免开服瞬间回源风暴 |
| 分批推送更新提示 | 不让所有人在同一分钟下载 |
| 多家 CDN 互备 | 一家出故障,客户端切到备用域名 |
| 客户端失败重试 + 断点续传 | 下到 90% 断网不用从头来 |
| 下载前检查剩余空间 | 避免下完才发现装不下 |
十一、和前面几篇的联系
| 前面讲过的 | 关系 |
|---|---|
| 网关 | 玩家的请求先经过家里网关出去;CDN 节点通常部署在运营商机房,离网关只有几跳 |
| 子网掩码 | CDN 按 IP 段判断用户在哪个地区、哪个运营商;源站白名单也按网段配置 |
| 抖动缓冲区 | 在线视频的"缓冲中"就是 CDN 下载 + 抖动缓冲共同作用;CDN 越近越稳,缓冲就可以越小 |
| 系统调用 | 高性能 CDN 节点用sendfile等零拷贝调用,文件直接从内核发到网卡,不进用户态 |
| 架构原则 | 静态(CDN)与动态(游戏服务器)分离,各自独立扩容 |
十二、常见的坑
| 坑 | 现象 | 解决 |
|---|---|---|
| 版本清单被长时间缓存 | 发了新版本,玩家一直检测不到更新 | 入口文件配短 TTL 或不缓存 |
| 资源包文件名不变,只改内容 | 有人拿到新的、有人拿到旧的,资源错乱 | 文件名带内容哈希 |
| 更新时先上传 catalog,后上传 bundle | 玩家读到新目录,却下载不到新文件,404 | 先传资源包,最后传入口文件 |
| 每次请求都加随机参数 | 命中率接近 0,回源费用暴涨 | 静态资源不加随机参数 |
| 把个人数据接口接入 CDN | A 玩家看到了 B 玩家的数据 | 动态接口返回private/no-store,或不走 CDN |
| 404 也被缓存了 | 文件补传后,仍然一直 404 | 缩短错误响应的缓存时间;先传文件再公开 |
| 只刷新了 CDN | 部分玩家仍是旧内容 | 客户端/浏览器本地也有缓存,靠文件名版本号解决 |
| 源站没限制访问 | 别人绕过 CDN 直接打源站 | 源站只放行 CDN 回源 IP 段,或校验回源密钥头 |
| 发布日没预热 | 开服瞬间源站被打挂 | 提前预热 + 中间层 + 分批推送 |
| 忘了海外玩家 | 国内飞快,海外极慢 | 选择覆盖目标地区的 CDN,或多 CDN 按地区调度 |
| 不看账单 | 月底流量费吓一跳 | 设置流量告警;压缩资源;提高命中率 |
一句话总结
CDN = 源站 + 遍布各地的缓存节点 + 就近调度。玩家通过 DNS(CNAME)或 Anycast 被分配到最近的节点:有缓存就直接返回,没有就回源取一份再返回。
用好 CDN 的关键只有一条:大文件用"内容哈希命名 + 长期缓存",只让很小的入口文件保持新鲜。
在游戏里,它负责安装包、补丁、热更新资源;而实时战斗流量需要的是加速网络,不是缓存。