共计 3992 个字符,预计需要花费 10 分钟才能阅读完成。
nginx 是优秀的反向代理服务器,这里主要讲它的健康检查和负载均衡机制,以及这种机制带来的问题。所谓健康检查,就是当后端出现问题(具体什么叫出现问题,依赖于具体实现,各个实现定义不一样),不再往这个后端分发请求,并且做后续的检查,直到这个后端恢复正常。所谓负载均衡,就是选择后端的方式,如何(根据后端的能力)将请求均衡的分发到后端。此外,当请求某个后端失败时,要将该请求分发到其它后端(Redispatch)。这里以 ngx_http_upstream_round_robin(简称 RR)做为负载均衡模块,以 ngx_http_proxy_module(检查 proxy)作为后端代理模块。
nginx 的健康检查和负载均衡是密切相关的,它没有独立的健康检查模块,而是使用业务请求作为健康检查,这省去了独立健康检查线程,这是好处。坏处是,当业务复杂时,可能出现误判,例如后端响应超时,这是可能是后端宕机,也可能是某个业务请求自身出现问题,跟后端无关。如果后端宕机,nginx 还要在将它标记为不可用之后,仍不时的将业务请求分发给它,以检查后端是否恢复。
nginx 完成客户端请求 Header 部分的解析,upstream 调用 RR 模块的 peer.get 选择具体的后端。当请求结束,upstream 调用 RR 模块的 peer.free,向 RR 反馈后端的健康情况。当 upstream 和后端通信时,出现错误会调用 ngx_http_upstream_next,
void ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t ft_type); 第三个参数指明错误类型,共有如下错误类型
#define NGX_HTTP_UPSTREAM_FT_ERROR 0x00000002
#define NGX_HTTP_UPSTREAM_FT_TIMEOUT 0x00000004
#define NGX_HTTP_UPSTREAM_FT_INVALID_HEADER 0x00000008
#define NGX_HTTP_UPSTREAM_FT_HTTP_500 0x00000010
#define NGX_HTTP_UPSTREAM_FT_HTTP_502 0x00000020
#define NGX_HTTP_UPSTREAM_FT_HTTP_503 0x00000040
#define NGX_HTTP_UPSTREAM_FT_HTTP_504 0x00000080
#define NGX_HTTP_UPSTREAM_FT_HTTP_404 0x00000100
#define NGX_HTTP_UPSTREAM_FT_UPDATING 0x00000200
#define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK 0x00000400
#define NGX_HTTP_UPSTREAM_FT_MAX_WAITING 0x00000800
#define NGX_HTTP_UPSTREAM_FT_NOLIVE 0x40000000
ngx_http_upstream_next,只要错误类型不是 NGX_HTTP_UPSTREAM_FT_HTTP_404,都认为后端有问题(NGX_PEER_FAILED)
if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) {
state = NGX_PEER_NEXT;
} else {
state = NGX_PEER_FAILED;
}
ngx_http_upstream_next 调用 RR 的 peer.free,RR 根据 state 判断刚才接受请求的后端是否健康。
if (ft_type != NGX_HTTP_UPSTREAM_FT_NOLIVE) {
u->peer.free(&u->peer, u->peer.data, state);
}
ngx_http_upstream_next 如果超过最大重试次数(默认为后端的个数,每试过一个,就减 1),或者 proxy 设置不允许 redispatch,则向客户端返回响应 status。
if (u->peer.tries == 0 || !(u->conf->next_upstream & ft_type)) {
ngx_http_upstream_finalize_request(r, u, status);
}
proxy 模块的 proxy_next_upstream 配置,在何种情况下将请求 redispatch 到下一个后端。
刚刚谈到,只要错误类型不是 NGX_HTTP_UPSTREAM_FT_HTTP_404,都认为后端有问题。这里的错误类型包括,连接后端失败,连接,读写后端超时,后端返回了 500,502,504 等。这个策略是有待商榷的,尤其是读写后端超时也判断为后端不可用。因为某个业务请求,可能因为自身的原因而导致读写超时。注意,在 proxy_next_upstream 中指定 timeout,http_504 是不同的,前者表示 upstream 连接,读写后端超时,后者表示后端返回的 http code 是 504。
实际上健康检查不是必须的,因为 redispatch 的存在保证了,就算有后端宕机,客户端仍将收到正确的响应。那么我们考虑关掉健康检查。通过 upstream 的 server 配置的 max_fails 参数
RR 的 peer.get,如果 max_fails 为 0,则该后端总是可用的(就算它真有问题)。
if (peer->max_fails == 0
|| peer->fails < peer->max_fails)
{
break;
}
因为 redispatch 的次数,取决于后端的个数,所以后端的个数稍微多一点是有好处的。
下面是一些佐证分析的测试。
upstream test {
server 127.0.0.1:8060 max_fails=0;
server 127.0.0.1:8070 max_fails=0;
server 127.0.0.1:8080 max_fails=0;
server 127.0.0.1:8090 max_fails=0;
}
只有 8060,8070 是存活的,8080,8090 处于不可用状态,这里 max_fails=0,关闭了健康检查。
proxy_read_timeout 2;
读超时设为 2S。
proxy_next_upstream error timeout;
默认当 error 和 timeout 发生时,redispatch。
测试请求的 sleep 参数指定后端的 sleep 时间,code 参数指定后端返回的 http code。根据 time 和 sleep 时间的对比,判断重试了几个后端。
time curl “http://127.0.0.1:8099/index.php?sleep=3” -vv
real 0m4.014s
sleep=3,读超时,重试了 2 个后端。
修改配置 proxy_next_upstream error;
time curl “http://127.0.0.1:8099/index.php?sleep=3” -vv
real 0m2.018s
读超时,不再 redispatch,重试了 1 个后端。
修改配置 proxy_next_upstream error http_504;
time curl “http://127.0.0.1:8099/index.php?sleep=1” -vv
real 0m1.022s
这个是正常请求。
time curl “http://127.0.0.1:8099/index.php?sleep=1&code=504” -vv
real 0m2.023s
让后端返回 504,此时 nginx 会做 redispatch,重试了 2 个后端
但是 nginx 返回给客户端的是 502,不是 504,因为所有的后端都返回 504,nginx 认为后端不可用,返回 502.
测试健康检查,关掉 redispatch。proxy_next_upstream off;
curl “http://127.0.0.1:8099/index.php?sleep=3” -vv
返回了两次 502,两次 504。存活的后端返回 504,有问题的返回 502。
修改 max_fails server 127.0.0.1:8060 max_fails=1; 对 8060 开启健康检查。
curl “http://127.0.0.1:8099/index.php?sleep=3” -vv
第一轮 4 次请求,返回两次 502,两次 504
8080 和 8090 有问题,返回 502,8060 和 8070 响应超时,返回 504,因为 8060 开启了健康检查,并且返回了 504,所以被标记为不可用。
第二轮 4 次请求,返回三次 502,一次 504。8070 没有开启健康检查,所以仍然返回 504。
根据测试分析,业务请求(sleep 3s,或者 输出 http 504)可以让 nginx 误以为后端宕了,而这时后端活得好好的。在私有云平台,这个通常不是问题,把超时设大点,不返回 5XX 错误,可以避免这个问题。但是在公有云平台,这是致命的,因为业务可以编程输出 5XX 错误。有两种方法应对,一种是关闭健康检查,一种是修改 nginx 的代码,仅对 NGX_HTTP_UPSTREAM_FT_ERROR 判定为后端有问题。
: