Nginx健康检查机制

Nginx/Web服务器
40
0
0
2024-11-10

目前,nginx对后端节点健康检查的方式主要有3种:

1、Nginx健康检查模块

目前,nginx对后端节点健康检查的方式主要有3种:

这里列出:

1、ngx_http_proxy_module 模块和ngx_http_upstream_module模块(自带)
    官网地址:http://nginx.org/cn/docs/http/ngx_http_proxy_module.html#proxy_next_upstream
2、nginx_upstream_check_module模块
    官网网址:https://github.com/yaoweibin/nginx_upstream_check_module
3、ngx_http_healthcheck_module模块
    官网网址:http://wiki.nginx.org/NginxHttpHealthcheckModule、ngx_http_proxy_module 模块和ngx_http_upstream_module模块(自带)
    官网地址:http://nginx.org/cn/docs/http/ngx_http_proxy_module.html#proxy_next_upstream
2、nginx_upstream_check_module模块
    官网网址:https://github.com/yaoweibin/nginx_upstream_check_module
3、ngx_http_healthcheck_module模块
    官网网址:http://wiki.nginx.org/NginxHttpHealthcheckModule

公司业务线上对后端节点的健康检查是通过nginx_upstream_check_module模块做的,这里我将分别介绍这三种实现方式以及之间的差异性。

一、 ngx_http_proxy_module 模块 和ngx_http_upstream_module模块 (自带)

严格来说,nginx自带是没有针对负载均衡后端节点的健康检查的,但是可以通过默认自带的 ngx_http_proxy_module 模块 和ngx_http_upstream_module模块中的相关指令来完成当后端节点出现故障时,自动切换到健康节点来提供访问。

这里列出这两个模块中相关的指令:

ngx_http_proxy_module 模块中的 proxy_connect_timeout 指令、 proxy_read_timeout指令和proxy_next_upstream指令

   语法:	proxy_connect_timeout time;
   默认值:	proxy_connect_timeout 60s;
   上下文:	http, server, locationtime;
   默认值:	proxy_connect_timeout 60s;
   上下文:	http, server, location

设置与后端服务器建立连接的超时时间。应该注意这个超时一般不可能大于75秒。

   语法:	proxy_read_timeout time;
   默认值:	proxy_read_timeout 60s;
   上下文:	http, server, locationtime;
   默认值:	proxy_read_timeout 60s;
   上下文:	http, server, location

定义从后端服务器读取响应的超时。此超时是指相邻两次读操作之间的最长时间间隔,而不是整个响应传输完成的最长时间。如果后端服务器在超时时间段内没有传输任何数据,连接将被关闭。

语法:	proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 |http_404 | off ...;
默认值:	proxy_next_upstream error timeout;
上下文:	http, server, location

指定在何种情况下一个失败的请求应该被发送到下一台后端服务器:

error      # 和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现错误
timeout    # 和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现超时
invalid_header  # 后端服务器返回空响应或者非法响应头
http_500   # 后端服务器返回的响应状态码为500
http_502   # 后端服务器返回的响应状态码为502
http_503   # 后端服务器返回的响应状态码为503
http_504   # 后端服务器返回的响应状态码为504
http_404   # 后端服务器返回的响应状态码为404
off        # 停止将请求发送给下一台后端服务器
error # 和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现错误
timeout    # 和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现超时
invalid_header  # 后端服务器返回空响应或者非法响应头
http_500   # 后端服务器返回的响应状态码为500
http_502   # 后端服务器返回的响应状态码为502
http_503   # 后端服务器返回的响应状态码为503
http_504   # 后端服务器返回的响应状态码为504
http_404   # 后端服务器返回的响应状态码为404
off        # 停止将请求发送给下一台后端服务器

需要理解一点的是,只有在没有向客户端发送任何数据以前,将请求转给下一台后端服务器才是可行的。也就是说,如果在传输响应到客户端时出现错误或者超时,这类错误是不可能恢复的。

范例如下:

http {
proxy_next_upstream http_502 http_504 http_404 error timeout invalid_header;
}

ngx_http_upstream_module模块中的server指令

语法:	server address [parameters];
默认值:	―
上下文:	upstreamserver address [parameters];
默认值:	―
上下文:	upstream

范例如下:

        upstream name {
                server 10.1.1.110:8080 max_fails=1 fail_timeout=10s;
                server 10.1.1.122:8080 max_fails=1 fail_timeout=10s;
        }

下面是每个指令的介绍:

max_fails=number # 设定Nginx与服务器通信的尝试失败的次数。在fail_timeout参数定义的时间段内,如果失败的次数达到此值,Nginx就认为服务器不可用。在下一个fail_timeout时间段,服务器不会再被尝试。 失败的尝试次数默认是1。设为0就会停止统计尝试次数,认为服务器是一直可用的。 你可以通过指令proxy_next_upstream、fastcgi_next_upstream和 memcached_next_upstream来配置什么是失败的尝试。 默认配置时,http_404状态不被认为是失败的尝试。

fail_timeout=time # 设定服务器被认为不可用的时间段以及统计失败尝试次数的时间段。在这段时间中,服务器失败次数达到指定的尝试次数,服务器就被认为不可用。默认情况下,该超时时间是10秒。 在实际应用当中,如果你后端应用是能够快速重启的应用,比如nginx的话,自带的模块是可以满足需求的。但是需要注意。如果后端有不健康节点,负载均衡器依然会先把该请求转发给该不健康节点,然后再转发给别的节点,这样就会浪费一次转发。 可是,如果当后端应用重启时,重启操作需要很久才能完成的时候就会有可能拖死整个负载均衡器。此时,由于无法准确判断节点健康状态,导致请求handle住,出现假死状态,最终整个负载均衡器上的所有节点都无法正常响应请求。由于公司的业务程序都是java开发的,因此后端主要是nginx集群和tomcat集群。由于tomcat重启应部署上面的业务不同,有些业务启动初始化时间过长,就会导致上述现象的发生,因此不是很建议使用该模式。 并且ngx_http_upstream_module模块中的server指令中的max_fails参数设置值,也会和ngx_http_proxy_module 模块中的的proxy_next_upstream指令设置起冲突。比如如果将max_fails设置为0,则代表不对后端服务器进行健康检查,这样还会使fail_timeout参数失效(即不起作用)。此时,其实我们可以通过调节ngx_http_proxy_module 模块中的 proxy_connect_timeout 指令、proxy_read_timeout指令,通过将他们的值调低来发现不健康节点,进而将请求往健康节点转移。 以上就是nginx自带的两个和后端健康检查相关的模块。

今天发现一个奇怪的现象,前端请求后端服务多次后会超时一次,经过多次验证确定是大概10s左右就会超时一次,检查后端服务,发现其中一个节点已经夯死。

但是我们的nginx负载均衡策略是轮询机制,按照配置来看应该是每隔一次请求轮询到失败的节点时超时一次才对。为什么是每隔10s超时一次呢?

upstream app_server {
  server 192.168.15.98:9080;
  server 192.168.15.99:9080;
}

原来,Nginx负载均衡的检查模块中,有两个参数:max_fails和fail_timeout。

默认:fail_timeout为10s,max_fails为1次。

原理:Nginx是基于连接探测的,如果发现后端异常,在单位周期为fail_timeout设置的时间中失败次数达到max_fails次,这个周期次数内,如果后端同一个节点不可用,那么就将把节点标记为不可用,并等待下一个周期(同样时长为fail_timeout)再一次去请求,判断是否连接是否成功

这样就能说明我们发现的现象了。即在10s以内后端失败了1次【即一次请求超时】,那么这个后端就被标识为不可用了,所以在接下来的10s期间,nginx都会把请求分配给正常的后端【即多次的请求正常】。

二、nginx_upstream_check_module模块

淘宝技术团队开发的 nginx 模块 nginx_upstream_check_module,通过它可以用来检测后端 realserver 的健康状态。如果后端 realserver 不可用,则所以的请求就不会转发到该节点上。

在淘宝自己的 tengine 上是自带了该模块的,大家可以访问淘宝tengine的官网来获取该版本的nginx,官方地址:http://tengine.taobao.org/

如果我们没有使用淘宝的 tengine 的话,可以通过补丁的方式来添加该模块到我们自己的 nginx 中。我们业务线上就是采用该方式进行添加的。

目前我在tx-sg使用的:

yum -y install pcre-devel openssl openssl-devel

cd  /usr/local/src/
wget http://nginx.org/download/nginx-1.12.1.tar.gz
tar -zxvf nginx-1.12.1.tar.gz


cd /usr/local/src
wget https://codeload.github.com/openresty/echo-nginx-module/tar.gz/refs/tags/v0.62
tar zxvf v0.62

#下载 nginx_upstream_check_module模块
cd /usr/local/src
wget https://codeload.github.com/yaoweibin/nginx_upstream_check_module/zip/master
unzip master

#为nginx打补丁
cd  nginx-1.12.1
#查看对应nginx版本: ll ../nginx_upstream_check_module-master/
patch -p1 < ../nginx_upstream_check_module-master/check_1.12.1+.patch

#安装nginx
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_stub_status_module  --with-http_ssl_module   --add-module=/usr/local/src/echo-nginx-module-0.62 --add-module=/usr/local/src/nginx_upstream_check_module-master

make -j2  
make install


原因:我安装的nginx版本为1.12.1,在安装nginx_upstream_check_module模块时忘记修改补丁文件版本(先安装了1.5.12+,后面发现错了又安装1.12.1+),导致在在make时报错.

下面是部署流程!

1、下载 nginx_upstream_check_module模块

[root@localhost ~]# cd /usr/local/src
wget https://codeload.github.com/yaoweibin/nginx_upstream_check_module/zip/master
unzip master
[root@localhost /usr/local/src]# ll -d nginx_upstream_check_module-master
drwxr-xr-x. 6 root root 4096 Dec  1 02:28 nginx_upstream_check_module-master@localhost ~]# cd /usr/local/src
wget https://codeload.github.com/yaoweibin/nginx_upstream_check_module/zip/master
unzip master
[root@localhost /usr/local/src]# ll -d nginx_upstream_check_module-master
drwxr-xr-x. 6 root root 4096 Dec  1 02:28 nginx_upstream_check_module-master

2、为nginx打补丁

cd nginx-1.6.0 # 进入nginx的源码目录 patch -p1 < ../nginx_upstream_check_module-master/check_1.5.12+.patch

3、安装nginx

./configure --user=nginx --group=nginx --prefix=/usr/local/nginx-1.6.0 --with-http_ssl_module --with-openssl=/usr/local/src/openssl-0.9.8q --with-pcre=/usr/local/src/pcre-8.32 --add-module=/usr/local/src/nginx_concat_module/ --add-module=../nginx_upstream_check_module-master/ make (注意:此处只make,编译参数需要和之前的一样) mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx-1.6.0.bak cp ./objs/nginx /usr/local/nginx/sbin/ /usr/local/nginx/sbin/nginx -t # 检查下是否有问题

安装nginx报错:

rc/http/ngx_http_upstream_round_robin.c: In function ‘ngx_http_upstream_free_round_robin_peer’: src/http/ngx_http_upstream_round_robin.c:688:13: error: label ‘failed’ used but not defined goto failed;

原因:我安装的nginx版本为1.12.1,在安装nginx_upstream_check_module模块时忘记修改补丁文件版本(先安装了1.5.12+,后面发现错了又安装1.12.1+),导致在在make时报错.

解决:删除nginx-1.12.1目录重新打补丁。

4、在nginx.conf配置文件里面的upstream加入健康检查,如下:

upstream name {
       server 192.168.0.21:80;
       server 192.168.0.22:80;
       check interval=3000 rise=2 fall=5 timeout=1000 type=http;
       
}

上面 配置的意思是,对name这个负载均衡条目中的所有节点,每个3秒检测一次,请求2次正常则标记 realserver状态为up,如果检测 5 次都失败,则标记 realserver的状态为down,超时时间为1秒。

这里列出 nginx_upstream_check_module 模块所支持的指令意思:

Syntax: check interval=milliseconds [fall=count] [rise=count] [timeout=milliseconds] [default_down=true|false] [type=tcp|http|ssl_hello|mysql|ajp] [port=check_port]
Default: 如果没有配置参数,默认值是:interval=30000 fall=5 rise=2 timeout=1000 default_down=true type=tcp
Context: upstream: check interval=milliseconds [fall=count] [rise=count] [timeout=milliseconds] [default_down=true|false] [type=tcp|http|ssl_hello|mysql|ajp] [port=check_port]
Default: 如果没有配置参数,默认值是:interval=30000 fall=5 rise=2 timeout=1000 default_down=true type=tcp
Context: upstream

该指令可以打开后端服务器的健康检查功能。

指令后面的参数意义是:

  - interval:向后端发送的健康检查包的间隔。
  - fall(fall_count): 如果连续失败次数达到fall_count,服务器就被认为是down。
  - rise(rise_count): 如果连续成功次数达到rise_count,服务器就被认为是up。
  - timeout: 后端健康请求的超时时间。
  - default_down: 设定初始时服务器的状态,如果是true,就说明默认是down的,如果是false,就是up的。默认值是true,也就是一开始服务器认为是不可用,要等健康检查包达到一定成功次数以后才会被认为是健康的。
  - type:健康检查包的类型,现在支持以下多种类型
    - tcp:简单的tcp连接,如果连接成功,就说明后端正常。
    - ssl_hello:发送一个初始的SSL hello包并接受服务器的SSL hello包。
    - http:发送HTTP请求,通过后端的回复包的状态来判断后端是否存活。
    - mysql: 向mysql服务器连接,通过接收服务器的greeting包来判断后端是否存活。
    - ajp:向后端发送AJP协议的Cping包,通过接收Cpong包来判断后端是否存活。
  - port: 指定后端服务器的检查端口。你可以指定不同于真实服务的后端服务器的端口,比如后端提供的是443端口的应用,你可以去检查80端口的状态来判断后端健康状况。默认是0,表示跟后端server提供真实服务的端口一样。该选项出现于Tengine-1.4.0。
- interval:向后端发送的健康检查包的间隔。
- fall(fall_count): 如果连续失败次数达到fall_count,服务器就被认为是down。
- rise(rise_count): 如果连续成功次数达到rise_count,服务器就被认为是up。
- timeout: 后端健康请求的超时时间。
- default_down: 设定初始时服务器的状态,如果是true,就说明默认是down的,如果是false,就是up的。默认值是true,也就是一开始服务器认为是不可用,要等健康检查包达到一定成功次数以后才会被认为是健康的。
- type:健康检查包的类型,现在支持以下多种类型
    - tcp:简单的tcp连接,如果连接成功,就说明后端正常。
    - ssl_hello:发送一个初始的SSL hello包并接受服务器的SSL hello包。
    - http:发送HTTP请求,通过后端的回复包的状态来判断后端是否存活。
    - mysql: 向mysql服务器连接,通过接收服务器的greeting包来判断后端是否存活。
    - ajp:向后端发送AJP协议的Cping包,通过接收Cpong包来判断后端是否存活。
- port: 指定后端服务器的检查端口。你可以指定不同于真实服务的后端服务器的端口,比如后端提供的是443端口的应用,你可以去检查80端口的状态来判断后端健康状况。默认是0,表示跟后端server提供真实服务的端口一样。该选项出现于Tengine-1.4.0。

check_keepalive_requests request_num命令

Syntax: check_keepalive_requests request_num
Default: 1
Context: upstream: check_keepalive_requests request_num
Default: 1
Context: upstream

该指令可以配置一个连接发送的请求数,其默认值为1,表示Tengine完成1次请求后即关闭连接。

check_http_send命令

Syntax: check_http_send http_packet
Default: "GET / HTTP/1.0\r\n\r\n"
Context: upstream: check_http_send http_packet
Default: "GET / HTTP/1.0\r\n\r\n"
Context: upstream

该指令可以配置http健康检查包发送的请求内容。为了减少传输数据量,推荐采用"HEAD"方法。

当采用长连接进行健康检查时,需在该指令中添加keep-alive请求头,如:"HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"。 同时,在采用"GET"方法的情况下,请求uri的size不宜过大,确保可以在1个interval内传输完成,否则会被健康检查模块视为后端服务器或网络异常。

check_http_expect_alive命令

Syntax: check_http_expect_alive [ http_2xx | http_3xx | http_4xx | http_5xx ]
Default: http_2xx | http_3xx
Context: upstream: check_http_expect_alive [ http_2xx | http_3xx | http_4xx | http_5xx ]
Default: http_2xx | http_3xx
Context: upstream

该指令指定HTTP回复的成功状态,默认认为2XX和3XX的状态是健康的。

check_shm_size命令

Syntax: check_shm_size size
Default: 1M
Context: http: check_shm_size size
Default: 1M
Context: http

所有的后端服务器健康检查状态都存于共享内存中,该指令可以设置共享内存的大小。默认是1M,如果你有1千台以上的服务器并在配置的时候出现了错误,就可能需要扩大该内存的大小。

Syntax: check_status [html|csv|json]
Default: check_status html
Context: location: check_status [html|csv|json]
Default: check_status html
Context: location

显示服务器的健康状态页面。该指令需要在http块中配置。

5、状态页面配置

在Tengine-1.4.0以后,你可以配置显示页面的格式。支持的格式有: html、csv、 json。默认类型是html。

你也可以通过请求的参数来指定格式,假设‘/status’是你状态页面的URL, format参数改变页面的格式,比如:

/status?format=html
/status?format=csv
/status?format=json?format=html
/status?format=csv
/status?format=json

同时你也可以通过status参数来获取相同服务器状态的列表,比如:

/status?format=html&status=down
/status?format=csv&status=up?format=html&status=down
/status?format=csv&status=up

下面是一个状态也配置的范例:

http {
      server {
       location /nstatus {
         check_status;
         access_log off;
         #allow IP;
         #deny all;
       }
      }
}
       location /nstatus {
         check_status;
         access_log off;
         #allow IP;
         #deny all;
       }
      }
}

配置完毕后,重启nginx。此时通过访问定义好的路径,就可以看到当前 realserver 实时的健康状态啦。效果如下图: realserver 都正常的状态:

一台 realserver 故障的状态:

OK,以上nginx_upstream_check_module模块的相关信息,更多的信息大家可以去该模块的淘宝tengine页面和github上该项目页面去查看,下面是访问地址:

ngx_http_upstream_check_module - The Tengine Web Server

GitHub - yaoweibin/nginx_upstream_check_module: Health checks upstreams for nginx

在生产环境的实施应用中,需要注意的有 2 点:

1、主要定义好type。由于默认的type是tcp类型,因此假设你服务启动,不管是否初始化完毕,它的端口都会起来,所以此时前段负载均衡器为认为该服务已经可用,其实是不可用状态。

2、注意check_http_send值的设定。由于它的默认值是"GET / HTTP/1.0\r\n\r\n"。假设你的应用是通过http://ip/name访问的,那么这里你的 check_http_send值就需要更改为 "GET /name HTTP/1.0\r\n\r\n"才可以。

3、针对采用长连接进行检查的, 这里增加 keep-alive请求头,即"HEAD /name HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"。如果你后端的tomcat是基于域名的多虚拟机,此时你需要通过check_http_send定义host,不然每次访问都是失败,范例:check_http_send "GET /mobileapi HTTP/1.0\r\n HOST www.redhat.sx\r\n\r\n";

5、健康检查HEAD请求400错误

如果后端服务器对于Http1.1的协议检测比较严格,则HEAD请求时必须加上Host字段。否则http请求报400错误。例如:

check_http_send "HEAD /check HTTP/1.1\r\nConnection: keep-alive\r\nHost: check.com\r\n\r\n";

ps:Host字段的值可以根据自己的需求写,写了个 check.com.

三、 ngx_http_healthcheck_module模块

除了上面两个模块,nginx官方在早期的时候还提供了一个 ngx_http_healthcheck_module 模块用来进行 nginx后端节点的健康检查。nginx_upstream_check_module模块就是参照 该模块的设计理念进行开发的,因此在使用和效果上都大同小异。但是需要注意的是, ngx_http_healthcheck_module 模块仅仅支持nginx的1.0.0版本,1.1.0版本以后都不支持了!因此,对于目前常见的生产环境上都不会去用了,这里仅仅留个纪念,给大家介绍下这个模块!

具体的使用方法,这里可以贴出几篇靠谱的博文地址以及官方地址:

HTTP Healthcheck | NGINX

healthcheck_nginx_upstreams/README at master · cep21/healthcheck_nginx_upstreams · GitHub

四、nginx_upstream_check_module配置负载均衡和健康检查配置详解

nginx.conf,关键配置如下:

  http {
    include       mime.types;
    default_type  application/octet-stream;
    keepalive_timeout  65;
    #gzip  on;
    upstream 172.19.189.53{ #负载均衡配置,默认的策略,按时间先后,有其他按ip hash,权重
        server 172.19.189.49:7070;
        server 172.19.189.50:7070;
        server 172.19.189.51:7070;
        check interval=3000 rise=2 fall=3 timeout=3000 type=http port=7070;
# interval=3000:间隔3秒检查一次,rise=2:检查2次ok后端节点up,fall=3:三次检查失败后端节点down,timeout=3000:超时时间3秒,type=http:发http检查请求类型,port=7070检查端口,可省略,默认和server 172.19.189.49:7070中的端口一致。
 check_http_send "HEAD /kylin HTTP/1.0\r\n\r\n";①
        check_http_expect_alive http_2xx http_3xx;
    }
    server {
        listen       8880;#nginx监听的端口,可修改。
        server_name  kylin_balance;
        location / {
            proxy_pass http://172.19.189.53; #反向代理,代理哪个应用服务器----②
        }
        location /status { #健康检查
            check_status; 
            access_log off; 
        }
}
} 

注:

①处,若请求url类型是http://ip:port/name类型的,此处的设置要在HEAD后加 /kylin,否则后端健康检查节点一直为down。

②处,此url为访问应用服务的请求域名部分,如应用服务部署在

172.19.189.49/172.19.189.50/172.19.189.51三台

不通过nginx的请求url为:http://172.19.189.49:7070/kylin,通过nginx访问的请求url为:http:// 172.19.189.53:8880/即可。

指令后面的参数意义是:

  - interval:向后端发送的健康检查包的间隔。
  - fall(fall_count): 如果连续失败次数达到fall_count,服务器就被认为是down。
  - rise(rise_count): 如果连续成功次数达到rise_count,服务器就被认为是up。
  - timeout: 后端健康请求的超时时间。
  - default_down: 设定初始时服务器的状态,如果是true,就说明默认是down的,如果是false,就是up的。默认值是true,也就是一开始服务器认为是不可用,要等健康检查包达到一定成功次数以后才会被认为是健康的。
  - type:健康检查包的类型,现在支持以下多种类型
    - tcp:简单的tcp连接,如果连接成功,就说明后端正常。
    - ssl_hello:发送一个初始的SSL hello包并接受服务器的SSL hello包。
    - http:发送HTTP请求,通过后端的回复包的状态来判断后端是否存活。
    - mysql: 向mysql服务器连接,通过接收服务器的greeting包来判断后端是否存活。
    - ajp:向后端发送AJP协议的Cping包,通过接收Cpong包来判断后端是否存活。
  - port: 指定后端服务器的检查端口。你可以指定不同于真实服务的后端服务器的端口,比如后端提供的是443端口的应用,你可以去检查80端口的状态来判断后端健康状况。默认是0,表示跟后端server提供真实服务的端口一样。该选项出现于Tengine-1.4.0。- interval:向后端发送的健康检查包的间隔。
  - fall(fall_count): 如果连续失败次数达到fall_count,服务器就被认为是down。
  - rise(rise_count): 如果连续成功次数达到rise_count,服务器就被认为是up。
  - timeout: 后端健康请求的超时时间。
  - default_down: 设定初始时服务器的状态,如果是true,就说明默认是down的,如果是false,就是up的。默认值是true,也就是一开始服务器认为是不可用,要等健康检查包达到一定成功次数以后才会被认为是健康的。
  - type:健康检查包的类型,现在支持以下多种类型
    - tcp:简单的tcp连接,如果连接成功,就说明后端正常。
    - ssl_hello:发送一个初始的SSL hello包并接受服务器的SSL hello包。
    - http:发送HTTP请求,通过后端的回复包的状态来判断后端是否存活。
    - mysql: 向mysql服务器连接,通过接收服务器的greeting包来判断后端是否存活。
    - ajp:向后端发送AJP协议的Cping包,通过接收Cpong包来判断后端是否存活。
  - port: 指定后端服务器的检查端口。你可以指定不同于真实服务的后端服务器的端口,比如后端提供的是443端口的应用,你可以去检查80端口的状态来判断后端健康状况。默认是0,表示跟后端server提供真实服务的端口一样。该选项出现于Tengine-1.4.0。

五、Nginx健康检查状况问题

如果没有添加nginx_upstream_check_module模块

nginx自动健康检查error log:

018/02/27 16:16:52 [error] 20514#0: *19699 connect() failed (111: Connection refused) while connecting to upstream, client: 114.247.222.18, server: testlua.tuling123.com, request: "GET /getSyn%2Fw+%E7%A5%9D%2Fv+%E4%BD%A0%2Frr+%E5%BF%AB%E4%B9%90%2Fa+%EF%BC%81%2Fw+%F0%9F%98%82%2Fw&apikey=qwert HTTP/1.1", upstream: "http://10.46.168.191:8082/base-segment/getSyntaxResultByHanlp?req=%E5%A0%2Frr+%E5%BF%AB%E4%B9%90%2Fa+%EF%BC%81%2Fw+%F0%9F%98%82%2Fw&apikey=qwert", host: "testlua.tuling123.com"
2018/02/27 16:16:52 [warn] 20514#0: *19699 upstream server temporarily disabled while connecting to upstream, client: 114.247.222.18, server: testlua.tuling123.com, request: "GET /getSyntaxRes7%A5%9D%2Fv+%E4%BD%A0%2Frr+%E5%BF%AB%E4%B9%90%2Fa+%EF%BC%81%2Fw+%F0%9F%98%82%2Fw&apikey=qwert HTTP/1.1", upstream: "http://10.46.168.191:8082/base-segment/getSyntaxResultByHanlp?req=%E5%88%86%r+%E5%BF%AB%E4%B9%90%2Fa+%EF%BC%81%2Fw+%F0%9F%98%82%2Fw&apikey=qwert", host: "testlua.tuling123.com" 

比如这个服务器:10.45.147.96服务器9091端口宕掉以后,访问日志如下:

添加这个模块以后,nginx自动剔除宕掉的服务

Nginx 负载均衡技术默认情况下已经对于 connect refused(状态码表现为 502)和 time out(状态码表现为 504)态码表现为 504)做了失效转移,使用的是 upstream 模块的 proxy_next_upstream 指令(这个选项默认是启动的)来实现。

对于 http GET 请求,当这个请求转发到上游服务器发生断路,或者读取响应超时则会将同样的请求转发到其他上游服务器来处理,如果所有服务器都超时或者断路,则会返回 502 或者 504 错误。

对于http POST 请求,当这个请求转发到上游服务器发生断路,则会将请求转发到其他上游服务器来处理,但是如果这个请求发生了读取超时,则不会做失效转移,会返回 504 错误,Nginx 之所以这么做应该是为了防止同一个请求发送两次,比如涉及到银行的充值等操作就会发生很严重的 bug。以下是模拟线上的场景测试得出的结论:

上游服务器有两台,一台处于 down 状态,另一台处于正常服务状态,那么来自客户端的 GET 和 POST 请求都会通过 Nginx 的失效转移机制路由到正常状态的机器,返回 200 状态码,并不会返回给客户端 502 错误; 上游服务器有两台,两台都 down 了,那么会不管是 GET 还是 POST 请求都会直接返回给客户端 502 错误; 上游服务器有两台,一台机器的 http GET 和 POST 接口都正常 return,另一台相同的接口死循环,模拟超时。 这种情况下如果客户端的请求路由到了正常机器,那么直接返回 200。 如果请求路由到了死循环的接口,并且是 GET 请求,那么会等待 Nginx 设置的超时时间过后,然后将请求转发到另一台机器的正常接口。 如果请求路由到了死循环的接口,并且是 POST 请求,那么等待 nginx 设置的超时时间过后直接返回 504,没有进行失效转移,防止请求的重复发送; 上游服务器有两台,两台机器的 http GET 和 POST 接口都死循环,模拟超时,那么对于 GET 请求会进行请求转发到另一台尝试,对于 POST 请求直接返回 504,不会进行进一步尝试;

六、Nginx主动监测模块upstream check误用可能导致的502--no live upsteams

nginx出现502 Bad GateWay的原因大部分情况下应该都不是Nginx的问题,而是后端Server的问题,比如程序挂了,比如响应太慢了。不过有时问题也出在Nginx上,就是我们遇到的这种情况,nginx reload之后一段时间内的访问都是502,error log中大量的no live upstream日志。

问题的原因就是对check upstream module这个模块的误用。先来看下我的配置。

主配置文件,nignx.conf:

worker_processes 4;
pid logs/nginx.pid;
error_log /tmp/logs/error.log;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type  application/octet-stream;
    # 省略部分配置

    include upstreamA.conf;

    server {
        listen 8889;
        server_name localhost;
        access_log  /tmp/logs/m-access.log  main;
        error_log /tmp/logs/m-error.log;

        location ~ / {
            proxy_pass http://tornado_servers;
        }

    }
}

然后是upstreamA.conf:

upstream tornado_servers {
    server 127.0.0.1:29100 weight=100;
    server 127.0.0.1:29101 weight=100;
    check interval=30000 rise=3 fall=3 timeout=30000 type=tcp;
}

然后是upstreamB.conf:

upstream tornado_servers {
    server 127.0.0.1:29110 weight=100;
    server 127.0.0.1:29111 weight=100;
    check interval=30000 rise=3 fall=3 timeout=30000 type=tcp;
}

有兴趣的可以用我的这个配置测试下,nginx需要有nginx_upstream_check_module插件。

我们上线应用的流程是这样,假设现在线上用的是upstreamA,那么我们发布Python程序的时候,就把端口监听在upstreamB设置的端口上,发布成功之后,把Nginx配置中的upstreamA改为upstreamB,然后reload,从而达到新程序上线的目的。

而这时出现的问题就是,reload之后,立马出现 no live upstream的error,并且所有访问都是502。

原因之前已经说过了,是对check模块的误用导致的,来看下check模块的使用说明就清楚了.

Syntax: check interval=milliseconds [fall=count] [rise=count] [timeout=milliseconds] [default_down=true|false] [type=tcp|http|ssl_hello|mysql|ajp] [port=check_port]
Default: 如果没有配置参数,默认值是:interval=30000 fall=5 rise=2 timeout=1000 default_down=true type=tcp
Context: upstream

各项说明:

该指令可以打开后端服务器的健康检查功能。
指令后面的参数意义是:
interval:向后端发送的健康检查包的间隔。
fall(fall_count): 如果连续失败次数达到fall_count,服务器就被认为是down。
rise(rise_count): 如果连续成功次数达到rise_count,服务器就被认为是up。
timeout: 后端健康请求的超时时间。

【default_down】: 设定初始时服务器的状态,如果是true,就说明默认是down的,如果是false,就是up的。默认值是true,也就是一开始服务器认为是不可用,要等健康检查包达到一定成功次数以后才会被认为是健康的。

type:健康检查包的类型,现在支持以下多种类型
tcp:简单的tcp连接,如果连接成功,就说明后端正常。
ssl_hello:发送一个初始的SSL hello包并接受服务器的SSL hello包。
http:发送HTTP请求,通过后端的回复包的状态来判断后端是否存活。
mysql: 向mysql服务器连接,通过接收服务器的greeting包来判断后端是否存活。
ajp:向后端发送AJP协议的Cping包,通过接收Cpong包来判断后端是否存活。
port: 指定后端服务器的检查端口。你可以指定不同于真实服务的后端服务器的端口,比如后端提供的是443端口的应用,你可以去检查80端口的状态来判断后端健康状况。默认是0,表示跟后端server提供真实服务的端口一样。该选项出现于Tengine-1.4.0。

七、nginx的proxy_next_upstream使用中的一个坑:

非等幂操作

ngx_http_proxy_module 模块中包括proxy_next_upstream指令
语法: proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 |http_404 | off ...; 

默认值: proxy_next_upstream error timeout; 上下文: http, server, location
error # 和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现错误 
timeout # 和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现超时 
invalid_header # 后端服务器返回空响应或者非法响应头 
http_500 # 后端服务器返回的响应状态码为500 
http_502 # 后端服务器返回的响应状态码为502 
http_503 # 后端服务器返回的响应状态码为503 
http_504 # 后端服务器返回的响应状态码为504 
http_404 # 后端服务器返回的响应状态码为404 
off # 停止将请求发送给下一台后端服务器

运用场景

1、proxy_next_upstream http_500 | http_502 | http_503 | http_504 |http_404;当其中一台返回错误码404,500...等错误时,可以分配到下一台服务器程序继续处理,提高平台访问成功率,多可运用于前台程序负载,设置proxy_next_upstream proxy_next_upstream http_500 | http_502 | http_503 | http_504 |http_404;

2、proxy_next_upstream off 因为proxy_next_upstream 默认值: proxy_next_upstream error timeout; 场景:当访问A时,A返回error timeout时,访问会继续分配到下一台服务器处理,就等于一个请求分发到多台服务器,就可能出现多次处理的情况,如果涉及到充值等非幂等操作,就有可能充值多次的情况,这种情况下就要把proxy_next_upstream关掉proxy_next_upstream off

如果涉及到充值等非幂等操作,就有可能充值多次的情况,这种情况下就要把proxy_next_upstream关掉proxy_next_upstream off

本文参考: Nginx实战系列之功能篇----后端节点健康检查_Not Only Linux的技术博客_51CTO博客