mysqlalready微服务

【HAproy】HAproxy TCP源端口耗尽问题解决方法

2022-06-07  本文已影响0人  Bogon

一、 问题背景和分析

Haproxy作为MySQL中间层是很成熟的方案,特别是解决从库的负载均衡和故障切换,在生产环境中有着广泛的应用。

MySQL client(应用工程) <------> HAproxy <-----> MySQL

HAproxy作为反向代理,会使用自己的IP地址作为源地址连接后端的MySQL服务器。
根据TCP协议,无论任何类型操作系统都只能拥有64K个左右的源TCP端口,用于向外发起TCP连接。

一旦"srcIP:port => dstIP:port"建立,这个源端口将不能被重用于其它连接。

image.png

TCP状态图中有一个TIME_WAIT状态,就是所谓的2MSL状态。
MSL就是maximum segment lifetime(最大分节生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间将在网络中消失。
MSL在RFC 1122上建议是2分钟,而源自Berkeley的TCP实现传统上使用30秒。
因而,TIME_WAIT状态一般维持在1-4分钟。
该状态是为了可靠地实现TCP全双工连接的终止,保证在tcp客户端发给tcp服务端的最后一个ACK能顺利到达。

若没有TIME_WAIT状态,tcp客户端将直接进入CLOSED状态。
如果tcp客户端直接进入CLOSED状态,那么由于IP协议的不可靠性或者是其它网络原因,导致tcp服务端没有收到tcp客户端最后回复的ACK。那么tcp服务端就会在超时之后继续发送FIN,此时由于tcp客户端已经CLOSED了,就找不到与重发的FIN对应的连接,最后tcp服务端就会收到 RST而不是ACK,tcp服务端就会以为是连接错误把问题报告给高层协议。
这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。
所以,tcp客户端不是直接进入CLOSED状态,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。

注意2点:

1、对于tcp请求来说,tcp的客户端服务端概念和http的不同,请求双方,哪边关闭请求,哪边就是tcp客户端,另一边就为服务端。
2、tcp的一个链接由4个值确定,源ip、源端口、目标ip、目标地址。

“There is no way for the person who sent the first FIN to get an ACK back for that last ACK. You might want to reread that now. The person that initially closed the connection enters the TIME_WAIT state; in case the other person didn’t really get the ACK and thinks the connection is still open. Typically, this lasts one to two minutes.”

根据上述的论述,如果一个源端口在2分钟内不能再次使用,则超过534个/秒的MySQL Client请求将会耗尽其本地TCP源端口。
64000 (可用端口) / 120 (2分钟,即120秒) = 533.333

因为HAproxy作为反向代理,会将所有MySQL请求转发给MySQL Server,因此HAproxy会比MySQL Client更快的耗尽本地TCP源端口!

但是如果MySQL Client和MySQL Server在同一台主机上,使用looback接口通信,则MySQL关闭序列是一个相对"干净"的序列。

对于分布式部署架构,用 loopback接口是不现实了。

二、 解决方案

1.增加本地端口范围

#  vim  /etc/sysctl.conf
net.ipv4.ip_local_port_range = 1024  65535

2.允许处于TIME_WAIT状态的源端口重用

#  vim  /etc/sysctl.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1

注意:不要开启此参数 net.ipv4.tcp_tw_recycle = 1 ,某些情况下会导致数据包被丢弃。

如果client通过NAT连接HAproxy,并且HAproxy端打开了tcp_tw_recycle,同时tcp_timestamps也没有关闭,当第一个连接建立并关闭后,此端口(句柄)处于TIME_WAIT状态,在2*MSL时间内又一个client(相同IP,如果打开了相同PORT)发一个syn包,此时Linux内核就会认为这个数据包异常,从而丢掉这个包,并发送RST包。
不过通常情况下,client都是通过内网直接连接HAproxy,所以可以认为tcp_tw_recycle是安全的,只是需要记住此坑。
新版本内核中已经将此配置废弃了。

3.使用多个IP连接单一dstIP:port,并让HAproxy来管理源端口

配置示例:

....
server mysql1     10.0.0.1:3306 check source 10.0.0.100:1024-65535
server mysql1_bis 10.0.0.1:3306 check source 10.0.0.101:1024-65535
....

经过测试,如果不让HAproxy管理源端口,则4个源IP地址最多管理不超过80K个TIME_WAIT连接,但是haproxy管理源端口可以达到170K+个TIME_WAIT连接!

  1. 使用长连接
    客户端-服务端最好使用长连接,一是避免频繁的申请连接,导致端口耗尽;二是避免创建连接带来的时间消耗。

三、参考

Linux Increase TCP Port Range with net.ipv4.ip_local_port_range Kernel Parameter
https://www.cyberciti.biz/tips/linux-increase-outgoing-network-sockets-range.html

HAProxy, High MySQL Request Rate and TCP Source Port Exhaustion
https://www.haproxy.com/blog/haproxy-high-mysql-request-rate-and-tcp-source-port-exhaustion
http://omnitraining.net/networking-101/98-networking-101-understanding-tcp-part-2

How to view and edit the ephemeral port range on Linux?
https://stackoverflow.com/questions/28573390/how-to-view-and-edit-the-ephemeral-port-range-on-linux

如何解决使用nginx作为反向代理端口耗尽问题?
https://blog.csdn.net/michaelwoshi/article/details/120170134

HAproxy开启了IP_BIND_ADDRESS_NO_PORT支持 ,即可以复用source port
这样可以从更基础的内核层面解决这个问题,唯一不足是需要将内核升级到4.2以上版本才可以
http://www.haproxy.org/download/1.7/src/CHANGELOG
https://kernelnewbies.org/Linux_4.2#head-8ccffc90738ffcb0c20caa96bae6799694b8ba3a

Overcoming Ephemeral Port Exhaustion in NGINX and NGINX Plus
https://www.nginx.com/blog/overcoming-ephemeral-port-exhaustion-nginx-plus

tcp四次挥手状态 TIME_WAIT
https://www.jianshu.com/p/3658730d76d7

nginx使用proxy_bind负载tcp socket,解决代理端口耗尽
https://b.sundayle.com/nginx-proxy-65535-port

上一篇 下一篇

猜你喜欢

热点阅读