做CDN这一行快十年了,每次我给新同事讲调度系统,都喜欢打一个比方:你在市中心开车,导航告诉你前方拥堵,建议你绕行隔壁那条路,虽然路程远了2公里,但实际能早到10分钟。CDN调度系统干的就是这件事,只不过它指挥的不是汽车,是每秒几千万甚至上亿次的用户请求。每一个请求到达DNS那一层时,调度系统都要在几十毫秒内回答一个灵魂问题:这个用户,应该给他哪个节点?
今天的博文我就把CDN调度系统彻底拆开讲一遍,从一次用户访问的完整链路说起,讲到GSLB调度器、用户IP库、网络质量探测、ECS精准调度、HTTPDNS、Anycast,再到容灾降级和日常排查。内容尽量说人话,该给参数给参数,该给排查思路给排查思路,希望对做CDN运维、架构设计、边缘计算的朋友有帮助。
1. 一次访问背后的调度逻辑
很多人理解CDN,就停留在“把静态资源缓存到离用户近的节点”这个层面。真实情况远不止这么简单。CDN节点在全国可能分布几百个,每个节点还有不同的运营商、不同的出口带宽、不同的实时负载,用户从北京联通发起一个请求,理论上可以被调度到北京联通节点、北京电信节点、天津联通节点,甚至更远的华北节点。选哪个,就是调度系统的事。
1.1 用户请求的第一站:DNS递归解析
先还原一次完整的用户请求。用户在浏览器里输入www.example.com,浏览器首先问本地DNS(Local DNS)。这个Local DNS一般由运营商提供,比如北京联通的用户,Local DNS大概率是202.106.0.20。本地DNS自己没缓存,就会去问根DNS、顶级域DNS,最终找到example.com的权威DNS。
如果example.com接入了CDN,它的权威DNS上配置的就不是源站IP,而是CDN服务商分配的CNAME域名,比如www.example.com.cdn.cloudprovider.com。本地DNS接下来就要解析这个CNAME域名,而CDN服务商对这个域名做了NS委托,把解析权交给了自家的GSLB(Global Server Load Balancing,全局负载均衡)系统。
到这一步,调度系统才正式登场。关键点在于:权威DNS拿到的只是本地DNS的出口IP,而不是用户的真实IP,这就是后面所有“调度不够精准”问题的根源。
1.2 GSLB调度器做了什么
GSLB收到解析请求后,手里有几张底牌可以打:
- 请求来源IP,也就是Local DNS的IP;
- 这个IP对应的地理位置和运营商信息;
- 各CDN节点的实时状态,比如健康状态、负载情况、回源带宽;
- 各节点到用户方向的网络质量数据,这部分通常依赖探测系统。
GSLB根据这些输入算出一个最优节点IP,通过CNAME层层返回,最终浏览器拿到一个类似1.2.3.4的节点IP,然后从这个节点拉取资源。
用话概括:GSLB是CDN系统的“大脑”,所有节点是它的手足,用户请求是它要分流的一股股车流。它的决策质量和数据源的准确度直接挂钩,数据越细、越准,调度结果才越靠谱。
1.3 调度的本质是权衡而不是追求最优
很多刚接触调度系统的人会有一个误区:总觉得调度应该把每个用户都分到最快的节点。实际上生产环境里几乎做不到,也没必要。
首先,全网节点几百个,“最快”本身是动态变化的,可能这一秒快、下一秒就拥塞。其次,即使某个节点对某一小撮用户延迟最优,如果所有人都挤过去,这个节点立刻被打满,延迟飙升,反而更慢。所以调度系统做的其实是“权衡”——在延迟、成本、带宽、容量之间找一个当前条件下的平衡点。
比如某用户在上海,上海有两个节点,A节点延迟低1毫秒,但已经跑了70%的带宽,B节点延迟高3毫秒,但只跑了40%。有经验的调度策略通常会分一部分流量到B,宁可多花几毫秒,也要避免A被击穿。这一点也是后面所有策略设计的底层逻辑。
2. CDN调度系统的核心模块拆解
一个完善的调度系统,至少由四块组成:数据采集层、IP库、决策引擎、执行下发链路。每一块都有各自的难点,我在日常工作中踩过的坑大多集中在这几层。
2.1 用户IP库:准确的地图是调度的基础
调度系统要判断“用户在哪”,靠的是IP地理库。这个库不是简单的“IP段对应省份”,而是要精确到城市、运营商,甚至区县级。
IP库的数据来源主要有三块。第一是运营商自己公布的IP段数据,APNIC等机构可以下载,但更新比较慢,不可能覆盖所有小运营商和新增段。第二是和专业IP数据机构合作的商业库,覆盖面广、更新勤,但要花钱。第三是CDN自己的访问日志反哺,比如一个节点收到的请求里出现了陌生IP段,回源日志显示用户手机号归属地都在某个省份,就能推断这个IP段的归属。
实际运营中,IP库不准是最让人头疼的问题之一。我记得有一次线上事故,某省用户普遍卡顿,排查了一圈,最后发现是IP库把该省一个大段地址标错了运营商。用户明明是联通宽带,IP却识别成移动,调度系统就把他导去了移动节点,跨网访问延迟当然高。从那以后我养成一个习惯:每次大版本更新IP库,都要抽样对比线上访问日志,宁可多跑几次校验,也不能盲目相信库的“权威性”。
2.2 节点质量数据:没有监控就没有调度
调度系统做决策时,节点侧数据至少包括三项:健康状态、负载水位、网络质量。
健康状态靠主动探测。CDN每个节点会在边缘服务器上部署Agent,定期向中心上报心跳,内容包括CPU、内存、带宽使用率、磁盘IO等。控制面如果连续几个周期没收到某个节点的心跳,就会自动把这个节点在调度系统里标记为“不可用”。
网络质量数据更复杂一些。业界常见做法是部署一组探测节点,分布在主要城市和运营商网络里,定时向所有CDN节点发起探测请求,测RTT、丢包率。比如北京的探测节点测到北京联通节点的丢包率是0.5%,测到上海电信节点丢包率是2%,这些数据汇总到调度中心,作为决策参考。
还有一类被动数据也不容忽视。CDN节点本身会产生大量日志,里面记录了每个用户请求的来源IP、下载速度、首字节时间。将这些数据按时间段聚合,也能反映出某运营商、某地区的真实用户体验。主动探测加被动日志两者结合,才是一份能支撑调度决策的质量数据。
2.3 决策引擎:多因素打分怎么设计
决策引擎是调度的核心算法模块。虽然每家CDN的实现细节不同,但大体都逃不开多因素加权打分。
简单来说,每个候选节点都会有一个分数,分数由以下维度构成:
- 地理位置距离权重:用户IP归属地和节点的地理距离越近分越高;
- 运营商匹配权重:同运营商优先,避免跨网;
- 实时负载分:节点当前负载越低分越高;
- 网络质量分:RTT低、丢包率低的分越高;
- 带宽成本权重:某些区域带宽成本高,策略上可以适当降权。
最后按加权总分选出Top N节点,第一个给用户,后续几个作为Failover备选。
这里有一点要特别注意:权重参数不是一次性调好的,而是需要根据线上反馈持续迭代。比如某个时间节点开始,某地用户反馈变差,就要看看是不是该地区网络质量权重给得不够。建议调度参数平台做成可配置,最好能在后台动态下发,而不是改一行代码就发一次版本。我这边以前就因为参数写死在配置中心,改一次要跑完整个发布流程,碰上紧急事故急得跳脚。
3. 调度策略的落地:DNS、ECS、HTTPDNS和Anycast
调度决策算出来了,接下来是怎么把结果下发到用户侧。这一步同样有很多讲究,不同的下发方式直接决定了调度的精准度和生效速度。
3.1 传统DNS调度:简单但天生有缺陷
传统CNAME方式最大的问题在于:权威DNS看到的是Local DNS的IP,不是用户真实IP。这会导致几种典型偏差:
一是Local DNS跨地域。我现在拿着北京的手机开4G,但运营商如果分配了一个归属地在上海的外地DNS,我的域名解析请求就会带着上海Local DNS的IP去GSLB,调度系统根据这个IP判断用户在上海,于是返回上海节点。用户人在北京,却连上海节点,白白跨了将近1200公里。
二是Local DNS缓存问题。运营商的Local DNS经常无视DNS TTL,自己长时间缓存CDN返回的IP。明明调度系统已经切走了某个故障节点,用户侧还在通过缓存访问,导致故障节点迟迟摘不掉。
三是对加密DNS(DoH/DoT)支持不足。越来越多的浏览器和应用默认开启DoH,请求会发往公共DNS服务商,本地运营商彻底失去对DNS流量的观测能力,调度系统看见的IP就五花八门了。
3.2 ECS(EDNS Client Subnet)如何把调度精准度拉回来
ECS协议解决的核心问题就是前面提到的“看不到用户真实IP”。它允许递归DNS在向上游权威DNS发起请求时,附带一个用户的子网信息,比如/24,权威DNS就能根据这个子网位置返回更精准的节点。
我自己的经验是,开启ECS后,调度的城市级命中率能提升不少,尤其对使用公共DNS的用户效果显著。但ECS也不是银弹,它有几个坑需要注意。
第一,很多递归DNS实现的ECS只传/24,这个粒度在某些IP划分混乱的城市还是不够准。第二,ECS会带来响应量变大,DNS响应报文从几十字节膨胀到几百字节,对DNS基础设施的压力要保持观察。第三,某些老旧的Local DNS会忽略ECS甚至不支持ECS,你这边加了,它那边还是不传,效果就要打个折扣。
3.3 HTTPDNS与Anycast:两条进阶路线
HTTPDNS的思路是绕过传统DNS链路,App端直接向调度服务的HTTP接口发起请求,把本机的公网IP直接告诉调度系统,调度系统返回一个最优节点IP。这种方式精准度高,不受Local DNS干扰,也能拿到用户的真实公网IP,现在很多头部App、视频客户端都在用。
缺点是只能覆盖自有App,网页用户覆盖不到,而且App需要集成SDK,对非技术团队有一定接入门槛。我碰到过一些客户的API网关注入了HTTPDNS,但因为缓存策略没设置好,每天产生大量重复调度请求,调度服务压力陡增,还得单独做一层缓存。
Anycast则是另一种哲学。它不依赖DNS解析时动态选择,而是把同一个IP地址在多个地区同时宣告。用户访问时,路由器自己按BGP最优路径把请求送到最近的节点。这种方案的调度生效速度极快,天然具备容灾能力,适合用于DNS服务器本身、流量清洗服务等场景。但它的问题是粒度太粗,只能做到网络层就近,没法考虑节点负载,所以一般作为辅助手段,和DNS调度配合使用。
3.4 调度策略对比速查
| 方案 | 精准度 | 生效速度 | 覆盖范围 | 主要问题 |
|---|---|---|---|---|
| 传统DNS | 低,依赖Local DNS位置 | 慢,受TTL缓存影响 | 所有域名用户 | 看不到用户真实IP |
| DNS+ECS | 中高,可达城市级 | 中等 | 支持ECS的递归DNS | 依赖递归DNS配合 |
| HTTPDNS | 高,精确到用户出口IP | 快,秒级生效 | 仅App端 | 需要集成SDK |
| Anycast | 中,网络层就近 | 最快,路由实时收敛 | 全IP流量 | 不感知节点负载 |
4. 调度系统容灾与降级:别等到故障发生才想对策
调度系统好不好,日常看不出差距,大故障一来全暴露了。我见过不少调度系统,平时看着挺智能,真到节点宕机、流量突增时,策略反而不生效,用户全被挤到坏节点上,那感觉就像交通指挥系统停电了,十字路口乱成一锅粥。
4.1 健康检查与故障自动摘除
节点故障摘除,是所有容灾手段里最基础的一项。评判标准只有一个:快。
健康检查的频率一般控制在5到10秒一轮,摘除动作要自动触发。这里的关键在于“探活阈值”的设置。阈值设得太敏感,网络抖动一下就把正常节点摘了,导致流量无意义迁移,反而放大故障。阈值设得太迟钝,节点已经不可用还在对外调度,用户等待超时,体验一落千丈。我的建议是分层设置:内存、CPU类指标用短期尖峰判断,带宽超限用持续累计判断,全挂类故障直接用TCP连接失败快速识别。
摘除节点后还要联动刷新冗余配置,确保所有下发链路的缓存里不再出现坏节点IP。这块如果漏了,就会出现“调度CDN认为已经摘除,但用户侧还在访问”的尴尬局面。
4.2 过载保护与优雅降级
过载保护针对的是“节点还没挂,但已经快撑不住”的状态。没有限流的CDN节点,遇到热点事件爆发,带宽会在几分钟内打满。这时候继续把用户调度进来,只会让节点彻底崩溃。
比较实用的做法是分级过载保护。节点负载超过70%时,调度系统减少新流量导入;超过85%时,只保留VIP客户流量;超过95%时,该节点在调度层面直接摘除。每一级的触发和恢复都要有对应的窗口期,避免节点刚降级就被放流量,来回抖动。
降级还有一个兜底方案是回源。如果所有边缘节点都扛不住,就直接让请求回源站,宁可源站压力大一点,也不要让用户拿到超时错误。毕竟对用户来说,页面加载慢一点还能接受,打不开才是最大的流失。
4.3 被动过载场景:热点流量突袭
这里要特别提一类场景,就是热门事件导致的流量尖峰。比如某明星突然上热搜,一张图片瞬间被全网访问。正常情况下流量会先集中到几个核心IDC节点,如果调度系统把流量均匀撒到全网,看起来分摊了压力,但其实很多三四线节点的带宽资源并不像想象中那么大,反而可能拉爆小节点。
对热点流量,我在实操中一般会提前配置“热点缓存预分发”策略。通过历史数据识别高热度资源,提前把内容推送到各省骨干节点,这样用户请求一到就能命中本地缓存,不需要跨地域拉取。这个动作看着和调度无关,其实能显著减弱调度系统的压力——资源不用跨区传输,流量自然就留在了用户附近。
5. 常见问题与排查技巧实录
调度系统上线后,大量时间花在排查问题上。我把这些年遇到的高频问题整理成一份速查表,都是踩过的坑,建议大家直接收藏。
5.1 用户总被调度到远处节点,怎么排查
遇到这类反馈,先别急着怀疑调度算法。按照下面顺序排查,基本能定位90%的问题:
- 确认用户IP和实际归属地是否一致。本地DNS的归属地和用户真实所在地不一致时,调度结果自然会偏。
- 确认Local DNS是否开启了缓存并且忽略了TTL。调用
dig看返回结果,如果TTL还剩很大值,多半是缓存。 - 确认ECS是否生效。用openssl或者dig带ECS选项查询,看权威DNS返回的节点IP是否根据用户子网变化。
- 确认节点覆盖情况。有些城市的边缘节点确实没有部署,调度只能退到就近城市,这是覆盖规划问题,不是调度能解决的。
5.2 调度切换流量后半天不生效
经常有运维同学问我:已经摘除故障节点了,怎么用户还访问到那个坏节点上?
这个问题的罪魁祸首通常是Local DNS缓存。运营商Local DNS的缓存策略五花八门,有些甚至完全忽略TTL。我的经验是,CDN侧需要在摘除节点后做两层保障。第一层,将故障节点IP加入GSLB黑名单,确保新解析请求直接绕过;第二层,主动向主要运营商Local DNS推送一个更短的TTL响应,或者配置一个专门的白名单域名,让重点客户走HTTPDNS绕过缓存。
线上实操时还要注意,切流量不是一次性的。节点故障时不要让所有用户一次性迁走,最好是按运营商、按地区灰度切换,每次切一小批,观察服务质量变化,再决定要不要继续。我见过有同事图省事一次性全切,结果新迁入的节点扛不住压力也挂了,故障雪崩就是这么来的。
5.3 运营商数据不准导致的调度偏差
IP库运营商识别不准,会导致跨网调度,是用户体感最差的一类问题。但实际操作中,IP库很难做到100%准确,怎么办?
我的一般做法是“双保险”。调度决策时,不只依赖IP库中的运营商字段,还会参考历史请求样本。比如某IP段过去一周有大量请求都指向移动线路的用户,请求行为上更像移动,即使IP库标的是联通,系统也应该以实测数据为准。这个思路本质上就是“用真实流量校准地图”,效果比单纯更新IP库好得多。
另外要提一点,运营商网络本身就是动态变化的,同一个IP段可能过几个月就更换了归属。IP库的更新频率至少要一个月一次,重大带宽调整时期要加密更新,千万别一个季度都不升级一次。
5.4 调度参数调整时的灰度与复盘
调度参数的调整,是所有变更里风险最高的一种,因为它直接影响线上所有用户的访问路径。每次改参数,我都建议先小流量验证,再全量发布,并且必须有回滚预案。
比较稳妥的做法是:先在某个边缘区域或者某个客户维度做灰度,用一周左右的时间对比调度前后的首字节时间、可用率、带宽成本等指标,确认没有负面趋势后再全量。复盘时重点看三块数据:可用率是否下降、时延是否波动、流量分布是否合理。任何一项异常都要追到底,不能因为指标大体涨了就盖过去。
6. 一些实践体会与建议
做了这么久调度系统,我最大的体会是:调度系统的难点不在算法,而在数据质量。算法再聪明,给它的IP库不准、质量数据不实时、节点状态不完整,算出来的结果照样是错的。就像导航软件再先进,地图数据是旧的,一样会把你导进死胡同。
所以如果想优化自建CDN的调度能力,建议优先把精力花在下面三件事上。
第一,建立完整的数据闭环。调度的每次决策结果,都要有对应的指标反馈,比如这个调度节点给用户带来的首字节时间、下载速度、可用率。没有反馈的调度系统等于闭着眼睛开车。
第二,把调度决策和业务场景解耦。不同类型的业务对延迟的敏感度不一样,视频请求更看重带宽,网页请求更看重首字节时间,API请求更看重稳定性。调度策略不能一刀切,最好按业务维度配置不同的权重模板。
第三,定期做全网调度演练。故障不会提前通知你,唯一能保证故障时不出乱子的方式就是提前演练。我这边每个季度会做一次节点断网模拟,把某个机房的流量全切走,观察其他节点的承接能力,这个习惯救过我好几次。
做CDN调度系统,真不是搭一个平台就完事了,它更像是在运营一套持续演化的生态系统。希望这篇内容能帮大家少踩一些坑,如果你也在做类似系统,欢迎多交流,互相补补经验。