nginx_upstream_check_module 是专门提供负载均衡器内节点的健康检查的外部模块,由淘宝的姚伟斌大神开发,通过它可以用来检测后端 realserver 的健康状态。如果后端 realserver 不可用,则后面的请求就不会转发到该节点上,并持续检查几点的状态。在淘宝自己的 tengine 上是自带了该模块。nginx_upstream_check_module模块对服务器主动健康检查,定时检查某个接口,只要这个接口没有报500,就认为该服务器没有异常。
项目地址:https://github.com/yaoweibin/nginx_upstream_check_module 。 下面的是一个带后端监控检查的 nginx.conf 配置:
upstream backend {
sticky; # or simple round-robin
server 172.29.88.226:8080 weight=2;
server 172.29.88.226:8081 weight=1 max_fails=2 fail_timeout=30s ;
server 172.29.88.227:8080 weight=1 max_fails=2 fail_timeout=30s ;
server 172.29.88.227:8081;
check interval=5000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "HEAD / HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
server {
location / {
proxy_pass http://backend;
}
location /status {
check_status;
access_log off;
allow 172.29.73.23;
deny all;
}
}
上面配置的意思是,对name这个负载均衡条目中的所有节点,每个5秒检测一次,请求2次正常则标记 realserver状态为up,如果检测 3 次都失败,则标记 realserver的状态为down,超时时间为1秒。
允许IP访问最后一定要加deny all;表示除了上面allow的其他都禁止
配完之后重启,然后打开http://ip/status 我们可以看到节点的状态
check指令只能出现在upstream中:
- interval : 向后端发送的健康检查包的间隔。
- fall : 如果连续失败次数达到fall_count,服务器就被认为是down。
- rise : 如果连续成功次数达到rise_count,服务器就被认为是up。
- timeout : 后端健康请求的超时时间。
- default_down : 设定初始时服务器的状态,如果是true,就说明默认是down的,如果是false,就是up的。默认值是true,也就是一开始服务器认为是不可用,要等健康检查包达到一定成功次数以后才会被认为是健康的。
- type:健康检查包的类型,现在支持以下多种类型
- tcp:简单的tcp连接,如果连接成功,就说明后端正常。
- http:发送HTTP请求,通过后端的回复包的状态来判断后端是否存活。
- ajp:向后端发送AJP协议的Cping包,通过接收Cpong包来判断后端是否存活。
- ssl_hello:发送一个初始的SSL hello包并接受服务器的SSL hello包。
- mysql: 向mysql服务器连接,通过接收服务器的greeting包来判断后端是否存活。
- fastcgi:发送一个fastcgi请求,通过接受解析fastcgi响应来判断后端是否存活
- port: 指定后端服务器的检查端口。你可以指定不同于真实服务的后端服务器的端口,比如后端提供的是443端口的应用,你可以去检查80端口的状态来判断后端健康状况。默认是0,表示跟后端server提供真实服务的端口一样。该选项出现于Tengine-1.4.0。
- 如果 type 为 http ,你还可以使用check_http_send来配置http监控检查包发送的请求内容,为了减少传输数据量,推荐采用 HEAD 方法。当采用长连接进行健康检查时,需在该指令中添加keep-alive请求头,如: HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n (
或GET /health.html HTTP/1.0\r\n\r\n
) 。当采用 GET 方法的情况下,请求uri的size不宜过大,确保可以在1个interval内传输完成,否则会被健康检查模块视为后端服务器或网络异常。 - check_http_expect_alive指定HTTP回复的成功状态,默认认为 2XX 和 3XX 的状态是健康的。
check_http_send "GET /web/v2/alive/check HTTP/1.0\r\n\r\n";
实战场景:
Java项目上线迭代时,发版完成后再检查服务是否可用。实现逻辑在/static/alive404文件。发版完成后调用/web/v2/alive/change接口后将文件改名后alive/check就会返回200
package net.test.common.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.*;
@Slf4j
@Api(description = "alive文件操作API")
@RestController
@RequestMapping(value = "/alive", produces = {"application/json"})
public class DevOpsFileController {
public static final String ALIVE = "alive";
public static final String ALIVE404 = "alive404";
/**
* alive文件是否存在
*/
@ApiOperation("alive文件是否存在")
@GetMapping("/check")
public ResponseEntity checkAlive() throws IOException {
File filePath = new File(System.getProperty("user.dir") + "/static");
if (!filePath.exists()) {
return ResponseEntity.notFound().build();
}
File fileArr[] = filePath.listFiles();
File aliveFile = fileArr[0];
if (aliveFile.exists() && aliveFile.isFile() && ALIVE.equals(aliveFile.getName())) {
} else {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok().build();
}
/**
* alive文件重命名
*/
@ApiOperation("alive文件重命名")
@GetMapping("/change")
public ResponseEntity changeStatus() {
File filePath = new File(System.getProperty("user.dir") + "/static");
if (!filePath.exists()) {
return ResponseEntity.notFound().build();
}
File fileArr[] = filePath.listFiles();
File oldFile = fileArr[0];
if (oldFile.exists() && oldFile.isFile()) {
String newFileName;
if (ALIVE404.equals(oldFile.getName())) {
newFileName = ALIVE;
} else {
newFileName = ALIVE404;
}
File newFile = new File(System.getProperty("user.dir") + "/static" + "/" + newFileName);
oldFile.renameTo(newFile);
return ResponseEntity.ok().build();
} else {
return ResponseEntity.notFound().build();
}
}
}