正确配置 Nginx Upstream Retry

Things fail

面向网络的服务,不可避免要遇到一些自己无法控制的问题,所以很多时候,重试是一个很好的保证服务可用的方案。文件下载失败了,重试;表单无法提交,重试。等等等,但是在 HTTP 的语义里面,像 POSTPATCH 等是不允许重试的,重试有可能造成数据冗余甚至一致性无法保证。

Nginx 在 1.9.13 之前就没有很好的处理对非幂等操作的重试。在那个版本之前,Nginx 都是直接根据错误情况,无论任何 HTTP method 都直接重试,结果可想而知,如果业务上没有处理这些情况,就会有糟糕的事情发生。

但是,其实就算你配置了 proxy_next_upstream,你也不一定能得到你想要的结果。

Upstream Retry 的陷阱

默认情况下,Nginx Upstream 重试相关的配置如下:

proxy_next_upstream on;
proxy_next_upstream error timeout;
proxy_next_upstream_tries 0;

表示在出现与上游连接失败或者超时的时候,进行重试,次数不限。

这个次数不限其实很容易混淆使用者的理解。比如下面一段配置:

upstream bk {
    server 127.0.0.1:9000;  # 假设 9000 端口没人监听,所以 Nginx 会重试
}

server {
    listen 80;
    location / {
        proxy_pass http://bk;
    }
}

连接 9000 端口失败了,但是没有重试发生。为什么呢,因为 upstream 的重试不会选择已经试过的 server。

可以简单使用 gdb 去查看这个情况:

$ gdb `which nginx`
> b ngx_http_upstream_connect
> b ngx_http_upstream_next
> command 1-2
continue
end
> run -c nginx.conf -p .

每次请求你都能得到类似这样的输出:

Breakpoint 1, ngx_http_upstream_connect (r=0x7ffff7efb390, u=0x7ffff7ef0b20) at src/http/ngx_http_upstream.c:1348
1348        r->connection->log->action = "connecting to upstream";

Breakpoint 2, ngx_http_upstream_next (r=0x7ffff7efb390, u=0x7ffff7ef0b20, ft_type=2) at src/http/ngx_http_upstream.c:3944
3944        if (u->peer.sockaddr) {

如果说有重试发生,那 ngx_http_upstream_connect 函数会不只被调用一次。没有重试的原因很简单,因为上面的配置中,只有一个 server 所以就没有可用的 server 供选择了。为了避免这种情况发生,你可以简单的把 upstream 的配置改成:

upstream bk {
    server 127.0.0.1:9000;
    server 127.0.0.1:9000;
    server 127.0.0.1:9000;
}

是的!拷贝相同的 server 就可以!哈哈哈,确实有点怪异,但是至少现在,你可以让你的请求重试三次了,不信你可以继续用 gdb 跟踪看看。这次你会看到一次请求调用了三次 ngx_upstream_connect,你成功地完成了重试的配置。

References