CDN 的智能调度并不是简单地把请求平均分配到各个节点,而是要先判断用户所在的网络环境。电信、联通、移动三张网之间的互联互通一直存在瓶颈,跨网流量容易在交换点排队。利用 ASN 来识别用户接入线路,是相对准确且维护成本可控的方案。本文会从 ASN 的概念讲起,再结合 DNS 调度和 OpenResty 实现一个按运营商分流的例子,最后讨论误判、回退和监控等落地细节。

一、ASN 如何区分运营商线路
要理解 ASN 分流,先要清楚自治系统号在 IP 路由中的位置。互联网并不是一张扁平网络,而是由大量自治域(AS)通过 BGP 协议互相连接组成。每个自治域有一个或多个 ASN,向互联网宣告自己拥有的 IP 前缀。用户通过运营商接入互联网时,获得的公网 IP 通常属于该运营商的某个 AS。因此只要用用户 IP 去查 ASN 库,就能大概率判断出用户走的是电信、联通还是移动线路。这和传统 GeoIP 依赖地理坐标不同,ASN 直接反映网络归属,更适合做线路调度。
三大运营商的实际 AS 分布并不是只有一个号。电信骨干网络常见 AS4134,部分地区或 CN2 网络使用其他号码;联通骨干常看到 AS4837,精品网或产业互联网可能对应不同 AS;移动骨干常出现 AS9808,部分省市公司也会独立宣告 IP 段。要实现准确分流,不能只写死几个 AS 号,需要维护一张运营商到 ASN 的映射表,把各省公司、子公司甚至历史上合并进来的 AS 都归类到对应运营商。MaxMind、IPIP 等 ASN 库通常会提供 AS 号和所属组织名称,可以按关键词匹配后人工校准。
ASN 判断还应该与地域维度结合。知道用户是电信用户还不够,如果电信用户在广东,而调度系统把请求丢给北京的电信节点,虽然同网但距离太远,延迟仍然不低。常见的做法是先用 IP 地理库拿省市信息,再用 ASN 拿运营商信息,两者组合成“华南电信”“华北联通”这样的线路标签。对于教育网、广电、大型云服务商的 IP,ASN 可能属于教育网或云厂商,此时应归入“其他”线路或者直接走多线 BGP 节点,否则容易误分流。
二、DNS 调度与 ECS 的作用
CDN 实现运营商分流最直接的方式是 DNS 智能解析。用户浏览器发起域名解析时,请求先到递归 DNS 服务器,再由递归 DNS 向权威 DNS 查询。权威 DNS 如果能看到请求来源 IP,就可以用 ASN 判断该递归 DNS 或者用户所在网络属于哪家运营商,然后返回对应的 A 记录。对于电信用户返回电信节点的 IP,对于联通用户返回联通节点 IP。这种方案的好处是用户零感知,不会增加额外 HTTP 请求。但对公共 DNS 场景存在先天缺陷:用户配置了 114.114.114.114 或 8.8.8.8 后,权威 DNS 看到的来源是公共 DNS 的地址,这些地址通常位于 BGP 多线机房,ASN 并不代表真实用户接入网。
为了解决公共 DNS 的误判,权威 DNS 需要支持 ECS(EDNS Client Subnet)。递归 DNS 在转发查询时可以携带用户 IP 的一段子网信息,例如用户 IP 的前 24 位加上源前缀长度。权威 DNS 收到 ECS 后,优先根据这段子网做 ASN 和地域判断,而不是只看递归 DNS 的 IP。目前主流公共 DNS 基本支持 ECS,但部分海外 DNS 因隐私政策会关闭这个选项。ECS 的 scope prefix 也会影响缓存命中,源前缀越长,判断越精确,但缓存复用率越低。CDN 权威 DNS 通常在成本和精度之间选择 /24 或者 /16 前缀长度作为折中。
另一种方案是 HTTP 302 调度,也就是把分发动作放到应用层。用户先访问一个中心节点,中心节点读取 TCP 连接的真实源 IP,再根据 ASN 判断是否需要跳转到更合适的线路节点。如果用户已经在最佳线路,就直接返回内容,否则返回 302 到目标节点域名。302 方案能看到真实用户 IP,不受递归 DNS 影响,非常适合视频大文件下载、大流量预热等场景。缺点是每次新会话都可能多一次 302 往返,对小文件访问不友好;而且需要防止目标域名再次解析时被 DNS 错误调度,形成跳转循环。实践中很多 CDN 采用 DNS 粗分加 302 纠偏的混合策略。
三、OpenResty 实现按 ASN 分流
在边缘网关层做 ASN 分流比 DNS 更灵活,适合自建 CDN 或者内部调度系统。下面以 OpenResty 为例,说明如何通过 Lua 查询 MaxMind 的 GeoLite2 ASN 数据库来实现电信、联通、移动线路的划分。GeoLite2 数据库可以从 MaxMind 官方获取,文件格式为 MMDB,Lua 可以通过 lua-resty-maxminddb 库直接读取。和旧版 GeoIP.dat 相比,MMDB 的维护更活跃,也支持独立的 ASN 数据库,更新方便。
假设我们已经有三组边缘节点,分别部署在电信、联通和移动线路内:10.0.0.11 是电信入口,10.0.0.12 是联通入口,10.0.0.13 是移动入口。再准备一个默认 BGP 多线节点 10.0.0.1,当查询失败或者遇到未知 AS 时兜底。Nginx 配置如下。
server {
listen 80;
resolver 223.5.5.5 valid=30s ipv6=off;
set $backend_ip "10.0.0.1";
access_by_lua_file /etc/nginx/lua/asn_route.lua;
location / {
proxy_pass http://$backend_ip:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这里的核心是 access_by_lua_file 阶段。Nginx 在访问阶段执行 Lua 脚本,脚本根据 $remote_addr 查出 ASN 并把线路对应的节点 IP 写回 $backend_ip。因为 proxy_pass 使用了变量,Nginx 会在每次请求时动态取值,达到分流目的。Lua 脚本内容如下。
local geo = require "resty.maxminddb"
local db_path = "/usr/local/openresty/geolite2/GeoLite2-ASN.mmdb"
if not geo.initted() then
local ok, err = geo.init(db_path)
if not ok then
ngx.log(ngx.ERR, "failed to init maxminddb: ", err)
return
end
end
local res, err = geo.lookup(ngx.var.remote_addr)
if not res or not res.asn then
ngx.var.backend_ip = "10.0.0.1"
return
end
local asn = res.asn
-- 电信骨干/CN2 线路
if asn == 4134 or asn == 4809 then
ngx.var.backend_ip = "10.0.0.11"
-- 联通骨干/精品网
elseif asn == 4837 or asn == 9929 then
ngx.var.backend_ip = "10.0.0.12"
-- 移动骨干
elseif asn == 9808 or asn == 56040 then
ngx.var.backend_ip = "10.0.0.13"
end
这段逻辑只展示了最小可用的按 ASN 分流。生产环境还需要把 ASN 映射表放到共享字典或外部配置里,避免每次修改映射都改 Lua 代码;同时要对 geo.lookup 的异常做降级处理。像 geo.init 只需要初始化一次,后续请求直接复用文件句柄,性能开销很低。GeoLite2-ASN.mmdb 文件建议通过定时任务每周更新,并做好文件替换后的 reload 或重新 init。
四、线路探活、回退与误判兜底
ASN 分流并不是判断完就结束了,线路节点的可用性直接决定调度效果。如果电信节点宕机,脚本仍然把电信用户往 10.0.0.11 转发,就会造成大量失败。可以在 Nginx 上启用主动健康检查模块,或者用外部探测程序定时请求各线路节点,把探活结果写入 Redis 或共享内存。Lua 在选择线路前先读取对应节点的健康状态,只有健康时才写入 $backend_ip,否则回退到默认多线节点。这样即使某条线路整体故障,也不会影响用户访问。
DNS 级分流还要注意 TTL 和缓存带来的滞后。当用户从移动数据网络切换到电信 Wi-Fi,本地 DNS 可能还缓存着之前移动线路的解析结果,导致用户继续访问移动节点,体验反而变差。适当缩短 TTL 可以缓解,但会增加 DNS 查询量。更好的做法是让 App 端在切换网络后主动清理 DNS 缓存,或者依靠 ECS 让递归 DNS 按用户子网重新缓存。302 分流则要避免无意义的跳转,例如已经命中最佳线路时直接返回内容;跳转地址建议使用 IP 而不是域名,减少二次解析的不可控性。
误判来源也需要关注。云厂商、IDC 和教育网的 IP 可能被 ASN 库标成非三大运营商,这时强制划分到某一个运营商反而不如走 BGP 多线。还有些用户出口使用企业专线或小型运营商的网络,ASN 数据库更新不及时会导致查询结果为空,所以默认线路必须保留。监控方面建议按 ASN 聚合统计每个线路节点的响应时间、回源率、缓存命中率,并周期性导出各线路流量占比。如果发现某个运营商流量异常偏低,可能意味着 ASN 库映射错误或权威 DNS 没有正确识别 ECS,需要及时校正。