udp_0">为什么要适配udp健康检查?
Tengine通过xquic实现HTTP3协议,不同于HTTP2和HTTP1.1以及之前的HTTP协议,HTTP3最大的不同就是其传输层协议由TCP改成了基于UDP实现的QUIC协议。QUIC 协议实现在用户态,建立在内核态的 UDP 的基础之上,集成了 TCP 的可靠传输特性,集成了 TLS1.3 协议,保证了用户数据传输的安全。不过HTTP3具体的特性不是本文的重点,我们在适配HTTP3的过程中遇到了许多问题,本文主要介绍关于UDP健康检查的部分。
Tengine作为Nginx的一个重要分支,在应用中常作为7层负载均衡使用。但在实际的生产中,7层负载均衡服务之前往往还有lvs这种4层负载均衡服务。为了保证高可用,就少不了在lvs 和 Tengine之前需要做健康检查探测。
常见的健康检查模式
TCP协议的健康检查一般通过TCP三次握手来检查,握手成功即代表服务可用。但是UDP的协议没有连接状态,默认不会回复任何内容,所以它的健康检查要麻烦一些。
目前比较常见的UDP健康检查主要有两种。
方法一
通过UDP报文加ICMP协议进行UDP端口探测,比如nmap命令
sudo nmap -sU 127.0.0.1 -p 123
返回
PORT STATE SERVICE
123/udp open|filtered powerclientcsf
它的检查逻辑大致是向目标端口发送一个UDP报文,并且对对端IP进行一次ping探测,如果主机可达且未收到ICMP的port unreachable报错(端口未监听,类似于TCP协议会回复rst)则认为是服务正常,否则则为服务异常。
但这个探测逻辑有个缺陷,如果服务器能ping通,但是假死;或者有防火墙策略,drop了指定端口的UDP报文,这些情况下能通过健康检查探测,但服务其实是不可用的。
方法二
另一种就是偏应用层探测。向服务发送特定的UDP报文,服务需要在指定时间内回复相应的UDP报文,才会认为服务健康。但是这种健康检查需要服务测适配。
udp_28">Tengine的 udp健康检查适配
Tengine是通过xquic实现的quic协议。xquic模块是可以通过方法2来实现被健康检查探测。在xquic的ngx_xquic_recv.h中,定义了3个宏:
#define NGX_XQUIC_HEALTH_CHECK "Healthcheck"
#define NGX_XQUIC_HEALTH_CHECK_REQ "UDPSTATUS"
#define NGX_XQUIC_HEALTH_CHECK_RSP "UDPOK"
这三个宏的作用是,如果UDP 报文的payload中以NGX_XQUIC_HEALTH_CHECK 定义的字符串开头,则会认为是健康检查探测包,跳过后续流程。
如果UDP 报文的payload 为 NGX_XQUIC_HEALTH_CHECK_REQ 定义的字符串,则会返回payload为 NGX_XQUIC_HEALTH_CHECK_RSP 定义的字符串。通过这个配置即可自定义实现上面方法二的探测。
但是这个种方式定义的字符串有特定条件才能适配。因为xquic模块在进行健康检查报文检测之前会先做一个 quic头检测
/* check QUIC magic bit */
if (!NGX_XQUIC_CHECK_MAGIC_BIT(packet->buf)) {
ngx_log_error(NGX_LOG_WARN, c->log, 0,
"|xquic|invalid packet head|");
return;
}
#define NGX_XQUIC_CHECK_MAGIC_BIT(pos) (((*(pos)) & 0x40) == 0x40)
这个校验逻辑很简单,检查UDPpayload里第一个字节第二位是否为1。但是这导致健康检查字符串的第一个字符也必须符合这个特点。比如a-zA-Z可以通过,但是数字不可以。具体可以查下ascii表。
如果已经约定好了UDP健康检查的字符串,通过修改 NGX_XQUIC_HEALTH_CHECK_REQ和NGX_XQUIC_HEALTH_CHECK_RSP “UDPOK” 重新编译一下就可以适配了。