nginx upstream server主动健康检测模块ngx_http_upstream_check_module 使用和源码分析(上)

目录

  • 1. 缘起
  • 2. 配置指令
    • 2.1 check
    • 2.2 check_keepalive_requests
    • 2.3 check_http_send
    • 2.4 check_http_expect_alive
    • 2.5 check_shm_size
    • 2.6 check_status
  • 3. 加载健康检测模块
    • 3.1 模块的编译
    • 3.2 模块的配置
  • 4. 测试验证
  • 5. 思考与问题
  • 6. 源码分析

1. 缘起

众所周知,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负载均衡模块的少许变动,本文不进行展开。

2. 配置指令

2.1 check

格式如下:

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: 被检测后端服务器的端口号

2.2 check_keepalive_requests

格式如下:

 check_keepalive_requests request_num
本指令表示与上游服务器建立tcp连接后,这个连接将被复用的次数,如果设置request_num > 1,那么不会每次检测都和上游服务器重新建立连接,request_num 默认为1。	

2.3 check_http_send

如果检测类型是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"。

2.4 check_http_expect_alive

格式如下:

check_http_expect_alive [ http_2xx | http_3xx | http_4xx | http_5xx ]
如果检测类型是http的,那么本指令表示检测上游服务器的HTTP响应代码,响应那种类型的代码才认为检测成功。多个选项可以用空格分隔表示。如:
check_http_expect_alive http_2xx http_3xx	

2.5 check_shm_size

格式如下:

check_shm_size size
本指令为健康检测模块定义了分配多少字节的共享内存,默认是1M。

2.6 check_status

开启nginx上提供展示上游服务器状态信息的页面接口。

格式如下:

check_status [html|csv|json]

本指令在location块中进行配置。本指令列出了提供哪一种或者多种格式的服务器状态信息数据。

譬如如下配置:

location = /status {
	check_status json;
}

那么我们可以通过访问 http://ip:port/status?format=html 以html 格式来显示服务器的状态。
如果没有指定format,按照上面的配置,则接口返回的是默认的json格式。

3. 加载健康检测模块

3.1 模块的编译

首先在编译nginx前,需要通过configure将本模块添加进去。

如:

./configure --add-module=./modules/ngx_http_upstream_check_module
然后,运行make进行编译即可。
目前,这个模块有个不是太大的问题,就是对nginx内核代码有一定的侵入性,tengine修改了负载均衡代码,使得这些代码和本模块强耦合,导致涉及到若干个负载均衡模块会静态引用本模块中的一些函数,所以不祥[[ngx_http_upstream_dynamic_module]]模块那样,它是不支持用动态模块的形式进行编译加载。

3.2 模块的配置

以下是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之类的错误信息拒绝访问。

4. 测试验证

启动tengine服务,加入上游nginx服务器没有开启,那么可以看到下面的页面表明上游服务器已经离线了。

nginx upstream server主动健康检测模块ngx_http_upstream_check_module 使用和源码分析(上)_第1张图片

接下去开启上游nginx服务器,那么可以看到下面的页面表明上游服务器已经正常了。

nginx upstream server主动健康检测模块ngx_http_upstream_check_module 使用和源码分析(上)_第2张图片

同时,我们可以看到上游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和其他健康检查的方式也是类似的,本文不再进行赘述。

5. 思考与问题

1. 本模块是如何利用nginx的异步I/O框架来实现后端服务器的健康检测的?
2. 如果要将本模块移植到原生nginx上,需要做那些改动?
3. 如果要将本模块改成支持动态模块加载,怎么来改?
4. 如果要将本模块支持其他健康检测的协议,譬如haproxy中有的udp检测和dns检测,那需要怎么扩展?
5. 目前本模块只能支持http模式探测上游服务器,不能支持https进行探测,虽然它支持ssl_hello,但它只是在ssl握手层面的,还无法进入到http的会话交互层面,如果需要支持,那该怎么做?

后面咱们就带着这些问题去看本模块的源码实现。

6. 源码分析

【未完待续】

你可能感兴趣的:(nginx学习,nginx,http,运维,健康检测,主动,upstream)