当负载均衡的服务器,发生宕机或报异常或超时等待的时候,怎么容错?因为这些情况都会导致请求阻塞直到超时。
如果其中一台服务器有故障,我们是不是可以自动轮询到下一台服务器上,这样就可以不影响正常的流程。
容错机制
Proxy指令
1、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 - 禁止转发请求到下一台服务器。
转发请求只发生在没有数据传递到客户端的过程中。
其中记录到nginx后端错误数量的有500、502、503、504、timeout,404不记录错误。
2、proxy_connect_timeout
语法:proxy_connect_timeout timeout_in_seconds
默认值:proxy_connect_timeout 60s
使用字段:http, server, location
指定一个连接到代理服务器的超时时间,单位为秒,需要注意的是这个时间最好不要超过75秒。
这个时间并不是指服务器传回页面的时间(这个时间由proxy_read_timeout声明)。如果你的前端代理服务器是正常运行的,但是遇到一些状况(例如没有足够的线程去处理请求,请求将被放在一个连接池中延迟处理),那么这个声明无助于服务器去建立连接。
可以通过指定时间单位以免引起混乱,支持的时间单位有”s”(秒), “ms”(毫秒), “y”(年), “M”(月), “w”(周), “d”(日), “h”(小时),和 “m”(分钟)。
这个值不能大于597小时。
3、proxy_read_timeout
语法:proxy_read_timeout time
默认值:proxy_read_timeout 60s
使用字段:http, server, location
决定读取后端服务器应答的超时时间,单位为秒,它决定nginx将等待多久时间来取得一个请求的应答。超时时间是指完成了两次握手后并且状态为established的超时时间。
相对于proxy_connect_timeout,这个时间可以扑捉到一台将你的连接放入连接池延迟处理并且没有数据传送的服务器,注意不要将此值设置太低,某些情况下代理服务器将花很长的时间来获得页面应答(例如如当接收一个需要很多计算的报表时),当然你可以在不同的location里面设置不同的值。
可以通过指定时间单位以免引起混乱,支持的时间单位有”s”(秒), “ms”(毫秒), “y”(年), “M”(月), “w”(周), “d”(日), “h”(小时),和 “m”(分钟)。
这个值不能大于597小时。
4、proxy_send_timeout
语法:proxy_send_timeout seconds
默认值:proxy_send_timeout 60s
使用字段:http, server, location
设置代理服务器转发请求的超时时间,单位为秒,同样指完成两次握手后的时间,如果超过这个时间代理服务器没有数据转发到被代理服务器,nginx将关闭连接。
可以通过指定时间单位以免引起混乱,支持的时间单位有”s”(秒), “ms”(毫秒), “y”(年), “M”(月), “w”(周), “d”(日), “h”(小时),和 “m”(分钟)。
这个值不能大于597小时。
配置样例
upstream http_backend { # 10s内出现3次错误,该服务器将被熔断10s server 192.168.2.154:8080 max_fails=3 fail_timeout=10s; server 192.168.2.109:8080 max_fails=3 fail_timeout=10s; server 192.168.2.108:8080 max_fails=3 fail_timeout=10s; server 192.168.2.107:8080 max_fails=3 fail_timeout=10s; } server { proxy_connect_timeout 5s; # 与被代理服务器建立连接的超时时间为5s proxy_read_timeout 10s; # 获取被代理服务器的响应最大超时时间为10s proxy_send_timeout 8; #代理服务器转发请求的超时时间为8s # 当与被代理服务器通信出现指令值指定的情况时,认为被代理出错,并将请求转发给上游服务器组中的下一个可用服务器 proxy_next_upstream http_502 http_504 http_404 error timeout invalid_header; proxy_next_upstream_tries 3; # 转发请求最多3次 proxy_next_upstream_timeout 10s; # 总尝试超时时间为10s location /http/ { proxy_pass http://http_backend; } }
其中的参数和指令说明如下。
- 指令值参数 max_fails 是指 10s 内 Nginx 分配给当前服务器的请求失败次数累加值,每 10s 会重置为 0;
- 指令值参数 fail_timeout 既是失败计数的最大时间,又是服务器被置为失败状态的熔断时间,超过这个时间将再次被分配请求;
- 指令 proxy_connect_timeout 或 proxy_read_timeout 为超时状态时,都会触发 proxy_next_upstream 的 timeout 条件;
- proxy_next_upstream 是 Nginx 下提高请求成功率的机制,当被代理服务器返回错误并符合 proxy_next_upstream 指令值设置的条件时,将尝试转发给下一个可用的被代理服务器;
- 指令 proxy_next_upstream_tries 的指令值次数包括第一次转发请求的次数。
Nginx 被动检测机制的优点是不需要增加额外进程进行健康检测,但用该方法检测是不准确的。如当响应超时时,有可能是被代理服务器故障,也可能是业务响应慢引起的。如果是被代理服务器故障,那么 Nginx 仍会在一定时间内将客户端的请求转发给该服务器,用以判断其是否恢复。
Nginx 官方的主动健康检测模块仅集成在商业版本中,对于开源版本,推荐使用 Nginx 扩展版 OpenResty 中的健康检测模块 lua-resty-upstream-healthcheck。该模块的检测参数如下表所示。
参数 | 默认值 | 参数说明 |
---|---|---|
shm | -- | 指定用于健康检测的共享内存名称,共享内存名称由 lua_shared_dict 设定 |
upstream | -- | 指定要做健康检查的 upstream 组名 |
type | http | 指定检测的协议类型,目前只支持 http |
http_req | -- | 指定用于健康探测的 raw 格式 http 请求字符串 |
timeout | 1000 | 检测请求超时时间,单位为 ms |
interval | 1000 | 健康检测的时间间隔,单位为 ms |
valid_status | -- | 健康检测请求返回的合法响应码列表,比如 {200, 302} |
concurrency | 1 | 健康检测请求的并发数,建议大于上游服务器组中的节点数 |
fall | 5 | 对 UP 状态的设备,连续 fall 次失败,认定为 DOWN |
rise | 2 | 对 DOWN 状态的设备,连续 rise 次成功,认定为 UP |
version | 0 | 每次执行健康检测时的版本号,有节点状态改变,版本号加 1 |
模块 lua-resty-upstream-healthcheck 的原理是每到(interval)设定的时间,就会对被代理服务器的 HTTP 端口主动发起 GET 请求(http_req),当请求的响应状态码在确定为合法的列表(valid_status)中出现时,则认为被代理服务器是健康的,如果请求的连续(fall)设定次数返回响应状态码都未在列表(valid_status)中出现,则认为是故障状态。
对处于故障状态的设备,该模块会将其置为 DOWN 状态,直到请求的连续(rise)次返回的状态码都在确定为合法的列表中出现,被代理服务器才会被置为 UP 状态,并获得 Nginx 分配的请求,Nginx 在整个运行过程中不会将请求分配给 DOWN 状态的被代理服务器。
lua-resty-upstream-healthcheck 模块只会使用 Nginx 中的一个工作进程对被代理服务器进行检测,不会对被代理服务器产生大量的重复检测。
配置样例如下:
http { # 关闭socket错误日志 lua_socket_log_errors off; # 上游服务器组样例 upstream foo.com { server 127.0.0.1:12354; server 127.0.0.1:12355; server 127.0.0.1:12356 backup; } # 设置共享内存名称及大小 lua_shared_dict _foo_zone 1m; init_worker_by_lua_block { # 引用resty.upstream.health-check模块 local hc = require "resty.upstream.healthcheck" local ok, err = hc.spawn_checker{ shm = "_foo_zone", # 绑定lua_shared_dict定义的共享内存 upstream = "foo.com", # 绑定upstream指令域 type = "http", http_req = "GET /status HTTP/1.0\r\nHost: foo.com\r\n\r\n", # 用以检测的raw格式http请求 interval = 2000, # 每2s检测一次 timeout = 1000, # 检测请求超时时间为1s fall = 3, # 连续失败3次,被检测节点被置为DOWN状态 rise = 2, # 连续成功2次,被检测节点被置为UP状态 valid_statuses = {200, 302}, # 当健康检测请求返回的响应码为200或302时,被认 # 为检测通过 concurrency = 10, # 健康检测请求的并发数为10 } if not ok then ngx.log(ngx.ERR, "failed to spawn health checker: ", err) return end } server { listen 10080; access_log off; # 关闭access日志输出 error_log off; # 关闭error日志输出 # 健康检测状态页 location = /healthcheck { allow 127.0.0.1; deny all; default_type text/plain; content_by_lua_block { # 引用resty.upstream.healthcheck模块 local hc = require "resty.upstream.healthcheck" ngx.say("Nginx Worker PID: ", ngx.worker.pid()) ngx.print(hc.status_page()) } } } }
以下是对该配置样例的几点说明。
- 该配置样例参照 OpenResty 官方样例简单修改;
- 对不同的 upstream 需要通过参数 upstream 进行绑定;
- 建议为每个上游服务器组指定独享的共享内存,并用参数 shm 进行绑定。