当用户请求到达CDN边缘节点且缓存未命中时,节点必须回源获取内容。如果源站返回5xx系列状态码,例如500内部错误、502网关错误、503服务不可用或504网关超时,CDN不会立即将错误响应返回给用户,而是会启动回源重试机制。这个过程涉及对多个源站的健康探测和故障转移,直接影响业务可用性与故障恢复速度。理解回源重试的触发条件、探测逻辑和参数调优,对于搭建高可用源站架构至关重要。

并非所有5xx错误都会触发回源重试。不同的CDN厂商和反向代理软件对状态码的分类策略存在差异。例如,Nginx的proxy_next_upstream指令默认包含error、timeout和invalid_header,但并未包含所有5xx状态码,需要通过具体参数开启。理解这些差异是配置回源重试的第一步。
回源重试的触发条件与状态码分类
源站返回的5xx状态码可以粗略分为两类:临时性故障和持久性故障。502、503、504通常表示网关层或上游服务暂时不可用,例如后端进程正在重启、连接池耗尽、负载均衡器超时等。这类故障往往在极短时间内恢复,因此CDN节点进行重试有较高的成功率。而500内部错误一般表示源站应用代码抛出了未捕获异常,同一个请求再次发送时大概率仍然返回500,所以很多CDN默认不对500状态码执行重试,避免浪费源站资源。
例如,阿里云CDN允许用户通过控制台开启对5xx状态码的回源重试,但会提示对500状态码谨慎启用。Cloudflare则对502、503、504执行自动重试其他源站,但对500状态码直接返回给用户。Nginx中可以通过proxy_next_upstream指令精确控制哪些情况需要重试,常见的配置如下:
location / {
proxy_pass http://backend;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_connect_timeout 3s;
proxy_read_timeout 10s;
}
上面的配置表示:当源站出现连接错误、超时、或者返回502、503、504状态码时,Nginx会把请求转发给下一个上游服务器,最多尝试2次。如果不添加http_500,那么源站返回500时不会触发重试。这个配置粒度非常关键,因为错误地包含http_500可能会把应用级错误误判为可重试的瞬时故障,导致无效重试甚至放大源站压力。
另外需要注意,回源重试只发生在CDN节点与源站之间的请求上,用户浏览器端看到的仍然是同一个HTTP请求。因此重试过程对用户透明,用户感知到的只是响应延迟可能增加。如果所有源站都返回5xx,CDN最终会将最后一个源站的错误响应返回给用户,或者返回自定义的错误页面。
多源站探测机制与健康检查流程
当CDN配置了多个源站地址时,回源重试需要解决一个核心问题:如何知道哪些源站当前是健康的,哪些已经故障应该跳过。常见的健康检查方式分为被动检查和主动检查。被动检查不需要额外发送探测请求,而是根据真实回源请求的结果来标记源站状态。例如,在Nginx的upstream块中,使用max_fails和fail_timeout两个参数即可实现被动健康检查。
upstream backend {
server 192.168.0.1:8080 max_fails=2 fail_timeout=30s;
server 192.168.0.2:8080 max_fails=2 fail_timeout=30s;
server 192.168.0.3:8080 backup;
}
上述配置中,max_fails=2表示在fail_timeout指定的30秒内,如果源站192.168.0.1累计失败2次(包括连接超时、返回5xx等),该源站会被标记为不可用状态,30秒内CDN不再向它转发任何请求,这个过程也叫熔断。熔断期间,请求会分配给其他健康源站。30秒后,CDN会尝试放行少量请求对该源站进行探测,如果成功则恢复其健康状态。第三个源站192.168.0.3带有backup标记,表示它是备用源站,仅当所有主源站都不可用时才会启用。这种主备结合的方式非常适合多机房容灾场景。
主动健康检查则不同,CDN会定期向源站发送探测请求,即使没有用户流量也会执行。例如,部分CDN支持配置健康检查URL、检查间隔、超时时间和判定条件。探测请求通常使用HEAD方法请求一个轻量级路径,比如/healthcheck,源站返回200即认为健康。主动检查的优点是能够在故障发生后更快速地更新源站状态,减少请求被发往已故障源站的概率。但主动检查会增加少量的源站流量,并且如果检查URL本身依赖数据库或缓存,可能无法真实反映业务健康度,因此需要谨慎设计检查接口。
多源站探测的典型流程是:用户请求到达CDN节点,节点从健康源站列表中选择一个源站发起回源;如果该源站返回5xx,CDN根据重试策略标记本次失败,并尝试下一个健康源站;如果所有源站都失败,则返回最后一个错误响应。为了避免无限重试,必须设置proxy_next_upstream_tries或CDN控制台中的重试次数上限。在Nginx中,proxy_next_upstream_tries 2表示最多尝试2个源站。
如何调优回源重试并避免重试风暴
回源重试能够提升可用性,但配置不当会引发重试风暴。假设一次用户请求在源站超时前等待了10秒,CDN又向两个源站重试,那么单个用户请求可能占用源站长达20秒以上的处理时间。如果源站本身已经因为过载开始返回503,重试会把更多请求压向其他源站,导致雪崩。因此,限制重试次数、缩短连接和读取超时、合理设置fail_timeout是防止重试风暴的关键。
下面是一个比较完整的Nginx反向代理配置示例,演示了如何对多个源站进行回源重试调优:
upstream backend_pool {
server 192.168.0.1:8080 max_fails=3 fail_timeout=20s;
server 192.168.0.2:8080 max_fails=3 fail_timeout=20s;
server 192.168.0.3:8080 max_fails=3 fail_timeout=20s backup;
keepalive 32;
}
server {
listen 80;
server_name ippipp.com;
location / {
proxy_pass http://backend_pool;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_connect_timeout 2s;
proxy_read_timeout 8s;
proxy_send_timeout 8s;
}
}
在这个配置中,proxy_connect_timeout设置为2秒,避免连接源站耗时过长;proxy_read_timeout为8秒,控制等待源站响应的时间;proxy_next_upstream_tries 2确保一次回源最多尝试两个不同的源站;fail_timeout=20s在源站连续失败3次后熔断20秒。这些参数共同将最坏情况下的单次回源总耗时限制在约20秒以内,并且阻止了请求在多个源站之间无意义地跳跃。
排查5xx错误时,不能只依赖CDN层面的重试,必须深入源站日志。例如,使用curl -v http://192.168.0.1:8080/直接模拟回源请求,观察响应头和状态码;检查源站应用日志中5xx错误出现的时间分布,判断是偶发还是持续;对于Nginx源站,可以查看access.log中的upstream_status字段,了解源站自身返回的状态码是否与CDN观察到的一致。如果源站返回500,需要检查应用代码或PHP、Java等运行时的错误日志;如果源站返回502,常见原因是源站前面的Web服务器(如Nginx)与后端应用服务器(如PHP-FPM、Tomcat)之间的连接问题,需要查看php-fpm.log或Tomcat的catalina.out。
回源重试与多源站探测并不是替代源站自身高可用方案的手段。真正健壮的架构应当包含多机房部署、负载均衡、自动扩缩容以及完善的监控告警。CDN的回源重试更多是作为最后一道防线,在单个源站实例短暂抖动时提供无感切换。调试时,可以通过临时关闭回源重试来观察源站真实失败率,再逐步调整max_fails、fail_timeout和重试次数,找到适合业务特性的平衡点。