一、问题背景:为什么回源地址要用域名?

在用CDN加速网站的时候,我们经常会遇到一个决定:回源地址到底写IP还是写域名?很多人图省事直接写IP,觉得稳定。但实际工作中,大部分专业的CDN配置都推荐用域名。原因很简单:如果源站后端有多台服务器,用域名可以配合DNS轮询或者智能DNS做负载均衡;如果源站的IP地址哪天变更了,只要改一下DNS记录,CDN节点就会自动解析到新IP,不用手动去每台节点改配置。

听起来很好,对吧?但是这里藏着一个容易被忽视的风险:DNS解析本身也是会出问题的。比如你的回源域名指向的DNS服务器挂了、解析超时、或者被劫持了,CDN节点就找不到你的源站。更糟糕的是,如果源站后面还有一层代理(比如Nginx反向代理或者API网关),而这个代理宕机了,导致DNS解析返回的IP变成了一个不可达地址,整个回源链路就会断掉,用户访问直接报502。

二、DNS解析带来的风险

咱们把场景具体化:你有一个主站域名 origin.example.com,CDN回源配置就是填这个域名。CDN节点每到一个新请求,都会去解析这个域名,拿到IP后发起TCP连接。假设你的源站前端有一个Nginx反向代理,这个Nginx的IP是192.168.1.100,并且你把这个IP写到了origin.example.com的A记录里。

某一天,Nginx服务因为负载过高或者进程崩溃宕机了,而DNS解析依然正常返回192.168.1.100。CDN节点尝试连接这个IP,发现连不上,于是返回502。即使你在同一台机器上还有备用服务(比如监听另一个端口),或者你有另一台备用Nginx(IP 192.168.1.101),但因为DNS只返回了主IP,CDN根本不知道备用IP的存在。

这就是单点故障。要解决这个问题,我们得设计一套备份方案,让CDN在回源域名解析到的主IP不可用时,能自动切换到备用IP或者备用域名。

三、备份方案设计与实现

3.1 方案一:多域名回源与健康检查

最朴素的做法:在CDN配置里填多个回源域名,并启用健康检查。比如主域名 origin1.example.com,备用域名 origin2.example.com。CDN节点会定期向这两个域名发起探测,一旦主域名返回的IP不可达,就自动使用备用的。这也是很多商业CDN自带的功能。

但是缺点也很明显:你需要维护两套独立的源站基础设施(至少是两套IP/域名),成本翻倍。而且DNS解析本身可能同时出问题(比如公共DNS被污染),所以并不彻底。

3.2 方案二:本地DNS缓存与备用IP

另一种思路:在CDN节点或者代理层自己搞一个本地DNS缓存,同时配置一个“兜底”的备用IP列表。当DNS解析成功但连接失败时,尝试用硬编码的备用IP去连接。这样即使上游DNS挂掉,我们也有最后的保底手段。

3.3 方案三:使用动态DNS更新

针对代理宕机的情况,我们可以让代理服务自身在启动时通过API动态更新DNS记录。比如Nginx启动后,主动调用DNS服务商接口,将 origin.example.com 的A记录改到自己的IP。当它宕机后,监控系统发现健康检查失败,再调用接口把记录改到备用机器的IP。但这依赖外部监控机制,有一定的延迟。

四、实际配置示例(以Nginx为例)

我们用一个最实用的方案来演示:在Nginx里做反向代理,并配置多个上游服务器,其中一个通过域名解析,另一个直接用IP做备用。同时使用Nginx的 resolver 指令和 backup 标记,结合健康检查功能。

技术栈:Nginx + Linux Shell

先看完整配置:

# 这一段定义了一个upstream组,名为backend
# 它包含两个服务器:一个主服务器用域名,一个备用用IP
upstream backend {
    # 主服务器:使用域名,权重为5,并启用健康检查
    server origin.example.com:8080 weight=5 max_fails=3 fail_timeout=30s;

    # 备用服务器:直接写IP,并标记为backup
    # 注意:这里假设备用Nginx监听在192.168.1.101的8080端口
    server 192.168.1.101:8080 backup;
}

# 配置DNS解析器,这里指定一个可用的DNS服务器(例如使用Cloudflare的公共DNS)
# 并且设置缓存时间为300秒,同时开启解析请求的异步处理
resolver 1.1.1.1 8.8.8.8 valid=300s;

# 这里用map指令来演示如何根据情况动态选择服务器(非必需,仅作为扩展)
map $upstream_addr $upstream_host {
    default $upstream_addr;
}

server {
    listen 80;
    server_name example.com;

    # 通过proxy_pass将请求转发到上面定义的upstream组
    location / {
        proxy_pass http://backend;

        # 设置Host头为原始请求的Host
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # 开启Nginx的被动健康检查(默认就是开启的)
        # 如果上游服务器返回错误或超时,Nginx会自动将其标记为失败
        # 并尝试下一个服务器(包括backup)
    }
}

注意:这个配置中的 backup 关键字表示只有当主服务器全部不可用时,才会使用这个备用服务器。而且 max_failsfail_timeout 控制了故障转移的灵敏度。

但是有一个关键点:如果 origin.example.com 解析出来的IP本身就是 192.168.1.100(我们的主Nginx),而它宕机了,那么Nginx的upstream会检测到连接失败,自动切换到 192.168.1.101:8080。可问题在于:origin.example.com 解析出来的IP可能永远都是192.168.1.100,即使它宕机了,DNS解析也不会变。所以上面配置里的 backup 备用IP实际上是我们手动写死的,这才是真正的“备份”。

为了让备份更灵活,你可以配合一个脚本,定期检查主Nginx的健康状况,如果发现挂了,就动态修改本地的/etc/hosts文件,让 origin.example.com 强制指向备用IP。但那样做有点粗暴。

更优雅的做法是在Nginx中使用 resolver 并配合 server 指令的 resolve 参数,允许Nginx在运行中重新解析域名并自动更新上游服务器列表。

upstream backend {
    # 使用resolve参数,让Nginx在遇到DNS解析变更时自动更新
    # 同时指定一个备用服务器(backup),它不会被解析影响
    server origin.example.com:8080 resolve max_fails=3 fail_timeout=30s;
    server 192.168.1.101:8080 backup;
}

这里 resolve 参数要求Nginx在每次上游连接时重新解析域名(受 resolver 指令的缓存控制),但注意:如果 origin.example.com 解析结果一直没变,那还是只能连到主IP上。所以真正的备份还得靠 backup 服务器。

为了演示如何配合DNS缓存和备用IP,我们可以写一个简单的Shell脚本,在Nginx启动前检查主Nginx是否存活,如果挂了,就将域名解析强制指向备用IP。不过这个脚本需要作为systemd服务或者cron任务周期执行。

#!/bin/bash
# 文件名:check_origin.sh
# 用途:监控主Nginx(假设IP为192.168.1.100)的可用性
# 如果不可用,则修改/etc/hosts使origin.example.com指向备用IP

MAIN_IP="192.168.1.100"
BACKUP_IP="192.168.1.101"
DOMAIN="origin.example.com"

# 尝试连接主Nginx的80端口(或健康检查URL)
if curl -s -m 5 --head http://${MAIN_IP}:8080/health | grep -q "200 OK"; then
    echo "主Nginx健康,无需切换"
    # 确保hosts中域名指向主IP
    sed -i "/${DOMAIN}/d" /etc/hosts
    echo "${MAIN_IP} ${DOMAIN}" >> /etc/hosts
else
    echo "主Nginx故障,切换到备用IP"
    sed -i "/${DOMAIN}/d" /etc/hosts
    echo "${BACKUP_IP} ${DOMAIN}" >> /etc/hosts
fi

这个脚本需要以root权限运行,并且要保证 /etc/hosts 的修改不会和DNS冲突。你可以把它放到crontab里每30秒执行一次。这样当主Nginx宕机时,30秒内hosts文件就会被更新,CDN节点下次解析时就会拿到备用IP,回源链就接上了。

五、应用场景与优缺点分析

应用场景:任何使用CDN且回源采用域名的场景,特别适合源站后端有代理层(Nginx、HAProxy、API网关)并且对高可用要求较高的业务。比如电商官网、在线视频、金融交易系统等。如果只有一台源站服务器,用IP和域名区别不大;一旦有多台代理或需要动态扩缩容,就必须用域名,同时必须考虑备份。

优点

  • 避免因DNS故障或代理宕机导致整个回源链断掉。
  • 可以零代价地实现主备切换,不需要额外硬件。
  • 配合健康检查,切换速度快(Nginx被动检测可在毫秒内完成)。
  • 配置简单,易于维护。

缺点

  • 备用IP是硬编码的,如果备用IP本身也变化,需要手动更新,不够动态。
  • 修改hosts或手动配置backup服务器增加了运维成本。
  • 如果DNS解析本身被劫持返回错误的IP,备用IP可能也解决不了(因为Nginx还是先连主IP)。
  • 对于大规模CDN集群,每个节点都需要同样的配置,管理起来稍麻烦。

六、注意事项

  1. DNS缓存时间:CDN节点本身可能也会有DNS缓存,如果解析出来的IP变了,但节点缓存没刷新,依然会连到旧IP。所以要合理设置DNS记录的TTL,建议短一点(比如60秒),配合Nginx的 resolvervalid 参数。
  2. 健康检查机制:Nginx的被动健康检查只在上游返回错误或超时时才切换。如果上游服务假死(比如内存溢出不响应但TCP连接没断开),可能不会触发切换。建议配合主动健康检查(如 nginx_upstream_check_module 或第三方模块)。
  3. 优化脚本执行效率:修改 hosts 文件的脚本要避免频繁写入,最好结合状态锁。
  4. 多级缓存:如果CDN回源路径上有多个代理(比如CDN -> 源站Nginx -> 应用服务器),每一层都要考虑类似的备份方案。
  5. 安全性:备用IP如果暴露了源站内网地址,要通过防火墙限制只允许CDN节点访问。

七、总结

CDN回源用域名是提高运维灵活性的好习惯,但DNS解析带来的单点故障和代理宕机问题不能忽视。通过组合使用Nginx的upstream多服务器配置(主域名+备用IP)和外部监控脚本,可以在一个可控的成本内实现回源链的自动备份。实际生产中,建议同时利用CDN商用服务自带的健康检查和多源站功能,再配合自身代理层的backup机制,实现双重保障。不要只依赖一种方案,做最坏的打算,才能让用户访问不掉链子。