众所周知,nginx原生的upstream模块提供了后端服务器的健康检测的功能,但是它的检测方式是被动模式的,被动模式意味着nginx不会主动发起健康检查,只有到客户端请求nginx的时候,nginx被迫需要向上游服务器发起正常的请求,顺便完成后端服务器的健康检测的任务。这种方式对nginx的实现来说是不需要太多额外的健康检测的代码,因此实现比较简单。
但是有利必有弊,一个问题显而易见的问题是,如果有上游服务器中途故障了,但是这次又没有客户端请求路由到该服务器,那么nginx是没法感知的,这样子当有客户端请求过来的时候,自然就有可能路由到该服务器,从而导致请求失败的问题发生,虽然nginx本身也提供了选择下一个上游服务器进行重试的机制,但是不可避免地会导致响应延时的问题出现,影响了用户体验。
同时,被动式健康检测的方案导致健康检测手段也被局限了,因为它是nginx向上游服务器请求的协议。而往往真实业务环境中需要多样化的检测手段,如tcp可连接性探测、http请求探测、ping等等,而nginx显然是没有这方面功能的,这方面haproxy则做得相对比较好,大家有兴趣可以学习一下haproxy的健康检测方面的相关内容。
幸好,作为nginx的一个衍生版本tengine提供了ngx_http_upstream_check_module,为我们提供了upstream上游服务器主动健康检测的手段。利用这个模块提供的功能,我们可以进行tcp、http、ssl握手、ajp、mysql、fastcgi等几种主动检测的手段,我们也可以利用它提供的检测框架,根据业务需要提供自己的其他协议的检测能力,譬如dns检测,或者其他任何你想要的协议类型。
虽然这个模块是在tengine中提供的,但是我们完全可以拿过来,在官方提供的原生nginx版本中进行完美集成,不过由于这个模块加入到原生nginx中会涉及到nginx负载均衡模块的少许变动,本文不进行展开。
格式如下:
check interval=milliseconds [fall=count] [rise=count] [timeout=milliseconds] [default_down=true|false] [type=tcp|http|ssl_hello|mysql|ajp] [port=check_port]
默认值: 以上参数都可以省略,省略的参数采用默认值,默认值定义如下:
interval=30000
fall=5
rise=2
timeout=1000
default_down=true
type=tcp
参数描述如下:
interval: 两次检测的时间间隔,单位:ms
fall: 连续累计检测失败次数,超过这个值那么后端服务器将被标识为不可用
rise: 连续累计检测ok的次数,原来被标识为不可用的后端服务器,
超过这个值那么后端服务器重新被标识为可用
timeout: 一次检测的超时时间,单位:ms
default_down: nginx启动的时候,默认设置服务器可用还是不可用
type: 检测协议类型,包括 tcp、mysql、fastcgi、ssl_hello、ajp、http六种
port: 被检测后端服务器的端口号
格式如下:
check_keepalive_requests request_num
本指令表示与上游服务器建立tcp连接后,这个连接将被复用的次数,如果设置request_num > 1,那么不会每次检测都和上游服务器重新建立连接,request_num 默认为1。
如果检测类型是http的,那么本指令指定了发送到上游服务器的HTTP请求头信息。
格式如下:
check_http_send http_packet
其中http_packer就是HTTP请求头信息,如:
check_http_send "GET / HTTP/1.0\r\n\r\n"
如果不设置,默认值就是"GET / HTTP/1.0\r\n\r\n"。
格式如下:
check_http_expect_alive [ http_2xx | http_3xx | http_4xx | http_5xx ]
如果检测类型是http的,那么本指令表示检测上游服务器的HTTP响应代码,响应那种类型的代码才认为检测成功。多个选项可以用空格分隔表示。如:
check_http_expect_alive http_2xx http_3xx
格式如下:
check_shm_size size
本指令为健康检测模块定义了分配多少字节的共享内存,默认是1M。
开启nginx上提供展示上游服务器状态信息的页面接口。
格式如下:
check_status [html|csv|json]
本指令在location块中进行配置。本指令列出了提供哪一种或者多种格式的服务器状态信息数据。
譬如如下配置:
location = /status {
check_status json;
}
那么我们可以通过访问 http://ip:port/status?format=html 以html 格式来显示服务器的状态。
如果没有指定format,按照上面的配置,则接口返回的是默认的json格式。
首先在编译nginx前,需要通过configure将本模块添加进去。
如:
./configure --add-module=./modules/ngx_http_upstream_check_module
然后,运行make进行编译即可。
目前,这个模块有个不是太大的问题,就是对nginx内核代码有一定的侵入性,tengine修改了负载均衡代码,使得这些代码和本模块强耦合,导致涉及到若干个负载均衡模块会静态引用本模块中的一些函数,所以不祥[[ngx_http_upstream_dynamic_module]]模块那样,它是不支持用动态模块的形式进行编译加载。
以下是nginx.conf配置文件的内容:
#user nobody;
worker_processes 1;
daemon off;
master_process off;
error_log logs/error.log;
pid logs/nginx.pid;
load_module objs/ngx_http_upstream_check_module.so;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
keepalive_timeout 65;
upstream backend {
check type=http interval=3000 rise=2 fall=5 timeout=1000 port=8888;
check_http_send "GET / HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
server 127.0.0.1:8888;
}
server {
listen 9080;
server_name localhost;
# 开启本模块的状态查询接口
location /status {
check_status html;
}
location / {
proxy_pass http://backend;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
以上假设了上游服务器是本地运行在8888端口的一个nginx服务器,并采用http接口进行健康检查。
这里需要说明一下,check_http_send指令设置为发送的请求采用HTTP 1.0版本,如果要采用HTTP 1.1版本,那么需要添加一个名称为Host的HTTP头部,这是HTTP 1.1规范所规定必须带的,否则上游nginx服务器将返回400错误。采用HTTP 1.1的指令如下:
check_http_send "GET / HTTP/1.1\r\nHost: 127.0.0.1:8888\r\n\r\n";
需要提醒一下,check_http_send指令设置的值是不允许用nginx的动态变量的,只能静态写死,这不能不说是一个缺憾,主要的原因是因为nginx的动态变量是和请求上下文关联在一起的,而这个模块和客户端的请求没有关系,所以也没有请求上下文,所以在现有的nginx框架体系里面暂时无法在这里做动态变量。
对于采用http协议来检测上游服务器健康状况的,设置Host还是非常重要的,因为往往web服务器是作为虚拟主机绑定到域名的,如果Host没有设置,上游服务器往往会返回400之类的错误信息拒绝访问。
启动tengine服务,加入上游nginx服务器没有开启,那么可以看到下面的页面表明上游服务器已经离线了。
接下去开启上游nginx服务器,那么可以看到下面的页面表明上游服务器已经正常了。
同时,我们可以看到上游nginx服务器的access日志已经产生了访问记录,两条记录之间的时间间隔正式我们设置的3000ms,日志输出如下:
127.0.0.1 - - [05/Feb/2024:14:41:25 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
127.0.0.1 - - [05/Feb/2024:14:41:28 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
127.0.0.1 - - [05/Feb/2024:14:41:31 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
127.0.0.1 - - [05/Feb/2024:14:41:34 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
127.0.0.1 - - [05/Feb/2024:14:41:37 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
127.0.0.1 - - [05/Feb/2024:14:41:40 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
127.0.0.1 - - [05/Feb/2024:14:41:43 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
127.0.0.1 - - [05/Feb/2024:14:41:46 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
127.0.0.1 - - [05/Feb/2024:14:41:49 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
127.0.0.1 - - [05/Feb/2024:14:41:52 +0800] "GET / HTTP/1.1" 200 555 "-" "-" "-" "Range: -
我们把tengine的worker_process设置成4个进行测试,验证是否会因为多个worker进程,导致4倍的健康检测请求,实际验证下来上游nginx还是每隔3s收到了一个http请求,证明本模块对不会随着worker进程的增加而增加健康检测的数量,这样子对上游服务器比较友好,至于它是如何实现的留待后续源码分析的时候进行探索。
最后在来测试一下json格式的状态输出, 输出内容如下:
{
"servers": {
"total": 1,
"up": 1,
"down": 0,
"generation": 1,
"server": [
{
"index": 0,
"upstream": "backend",
"name": "127.0.0.1:8888",
"status": "up",
"rise": 227,
"fall": 0,
"type": "http",
"port": 8888
}
]
}
}
对于TCP和其他健康检查的方式也是类似的,本文不再进行赘述。
1. 本模块是如何利用nginx的异步I/O框架来实现后端服务器的健康检测的?
2. 如果要将本模块移植到原生nginx上,需要做那些改动?
3. 如果要将本模块改成支持动态模块加载,怎么来改?
4. 如果要将本模块支持其他健康检测的协议,譬如haproxy中有的udp检测和dns检测,那需要怎么扩展?
5. 目前本模块只能支持http模式探测上游服务器,不能支持https进行探测,虽然它支持ssl_hello,但它只是在ssl握手层面的,还无法进入到http的会话交互层面,如果需要支持,那该怎么做?
后面咱们就带着这些问题去看本模块的源码实现。
【未完待续】