上个月公司核心的支付接口遭遇了连续断网的惨剧。由于主数据中心的光纤被挖断,下游业务服务在呼叫支付网关时,由于网关彻底失联,导致所有的 API 请求全部堵死在主通道上。最离谱的是,我们虽然部署了备用灾备网关,但由于 Nginx 配置得极其简陋,根本没有配置健康检查和自动转移机制,结果下游服务眼睁睁看着主网关挂掉而无法自动切换,直接全瞎。

很多工程师在配置 Nginx 反向代理时,往往只写个简单的 proxy_pass http://backend; 就完事了。平日里大家都高高兴兴,可一旦某台后端服务器 CPU 爆表、JVM 发生 Full GC 卡死或者网络抖动,Nginx 依然会傻傻地把一部分流量分发过去。这就会导致前端间歇性地报错 502 或 504,严重影响用户体验。

其实,Nginx 开源版自带的 ngx_http_upstream_module 就提供了非常实用的被动式健康检查和容灾机制。我们只需要合理配置 max_failsfail_timeout,再配合 proxy_next_upstream,就能实现秒级的平滑故障转移。

这里分享我们线上网关优化后的 Nginx 配置文件片段,可以直接拿去生产环境落地:

# nginx.conf 示例配置
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # 配置上游服务器组
    upstream api_backend_servers {
        # 默认轮询,这里配置了健康检查参数
        # max_fails=3 表示在 fail_timeout 时间内如果失败了3次,就认为该服务器挂了
        # fail_timeout=15s 表示检测失败的窗口时间,以及标记失败后暂时停用该服务器的时间
        server 10.0.1.10:8080 max_fails=3 fail_timeout=15s weight=5;
        server 10.0.1.11:8080 max_fails=3 fail_timeout=15s weight=5;
        
        # backup 标记为备用服务器。只有当上面的主服务器全部挂掉时,才会把流量转发到这里
        server 10.0.2.20:8080 max_fails=2 fail_timeout=10s backup;
        
        # down 标记服务器永久不可用,通常用于平滑下线维护
        # server 10.0.1.12:8080 down;
    }

    server {
        listen 80;
        server_name api.yourcompany.com;

        # 开启连接超时优化
        keepalive_timeout 65;

        location /api/v1/ {
            proxy_pass http://api_backend_servers;
            
            # 设置一些必要的请求头,透传真实 IP 和 Host
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            # 关键配置:定义哪些情况视为后端服务失败,需要尝试下一个节点
            # error: 连接后端出错
            # timeout: 后端响应超时
            # invalid_header: 后端返回的 header 格式不对
            # http_502 / http_503 / http_504: 后端返回特定错误码
            proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;

            # 限制将请求传给下一个服务器的尝试次数,防止请求在所有后端节点中无限循环
            proxy_next_upstream_tries 3;

            # 限制将请求传给下一个服务器的超时时间
            proxy_next_upstream_timeout 10s;

            # 连接超时设置
            proxy_connect_timeout 3s;   # 与后端服务器建立连接的超时时间,不宜设得太长
            proxy_read_timeout 10s;     # 接收后端服务器响应的超时时间
            proxy_send_timeout 5s;      # 发送请求给后端服务器的超时时间
        }
    }
}

这里需要避开一个大坑:proxy_next_upstream 默认会对 non_idempotent(非幂等)请求(比如 POST、PATCH 等)进行故障转移,但这在涉及金融支付等交易场景时是极其危险的。因为这可能会导致用户发起一次扣款,主网关已经执行了部分逻辑但返回超时,Nginx 接着把请求转给了备网关,从而造成二次扣款。所以如果是严格的非幂等接口,我们在使用时应当非常谨慎,甚至考虑在支付接口中禁用非幂等请求的自动 Failover。

另外,Nginx 开源版这种健康检查是被动的。也就是说,只有当真实的客户端请求过去失败了,Nginx 才会把它标记为失效。如果希望主动向后端发送心跳包,就需要引入阿里巴巴开源的 nginx_upstream_check_module 模块,或者干脆花钱升级到商业版的 Nginx Plus。

大家公司的反向代理网关是用什么机制做健康检测的?有没有因为配置了不合理的 Failover 导致线上请求被多次执行的血泪教训?