解决ProxySQL连接MySQL产生大量TIME_WAIT连接的问题
2018/01/10
posted in
ProxySQL
2018/01/10
posted in
ProxySQL
有时候解决一个问题很简单,但是其中发现问题和深入问题的过程却值得我们反复思考
最近在测试环境新搭的一套proxysql
忽然无法正常登录了,一直提示连接hostgroup
超时。
我首先跳过proxysql
,直接连接后端的mysql
节点,确认是proxysql
的问题还是mysql
的问题。
但是连接一直处于进行中的状态,不提示登录成功也不提示登录失败。
初步判断应该是mysql
的连接数被打满了。
使用命令查看连接数
netstat -naplt|grep 6033|wc -l
连接数显示mysql
的连接已经被占满了。
由于后端的mysql
节点只通过proxysql
来访问,其他的程序并不知道mysql
实例的端口号,所以尝试重启proxysql
来释放连接。
再次尝试连接后端mysql
节点,这次直接提示连接数过多,连接失败。
显然,重启proxysql
并没有成功的解决问题。
仔细查看netstat
输出的信息。
发现绝大部分的连接都是TIME_WAIT
。
之后,通过重启mysql
暂时的清理掉了这些连接,但是,首先,重启mysql
的成本过高,在线上根本不可行,其次,暂时清理掉TIME_WAIT
的连接之后,TIME_WAIT
的连接数又很快的涨了上来。这并没有从根本上解决问题。
尝试通过proxysql
的参数进行连接数限制,但是,TIME_WAIT
状态的连接根本不被计算在proxysql
的连接中,无法被限制。
查阅资料暂时解决了问题
查询了相关资料后,发现可以通过修改Linux
内核参数来优化TCP连接
。
编辑/etc/sysctl.conf
# 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1
# 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_tw_recycle = 1
# 表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。
net.ipv4.tcp_fin_timeout = 30
# 表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。
net.ipv4.tcp_max_tw_buckets = 400
通过开启tcp复用
,tcp快速回收
,修改tcp fin超时时间
依然无法降低TIME_WAIT
连接的数量。
最后,通过修改TIME_WAIT
的最大保持数量,将TIME_WAIT
的连接数量控制在mysql
的最大连接数以内,暂时保证了mysql
的可用性。
但是,我们依然并没有从根本解决问题。
为了解决问题,我们先了解一下TIME_WAIT
是什么。
在关闭TCP
连接的四次握手中,客户端
先向服务器端
发送FIN报文
,告诉服务器端“我要断开连接了”,服务器端收到FIN
后会回复一个ACK
,表示收到断开连接的请求,但此时服务器端
可能仍有数据未发送完,当服务器将数据发送完成后,服务器端
会发送一个FIN报文
,表示可以断开连接,客户端
接收到FIN报文
以后会发送一个ACK报文
,此时客户端
会发送一个ACK报文
,然后客户端
进入TIME_WAIT
状态,当等待2MSL(两个最大报文段生存时间)
之后,如果没有再接收到服务器端
的请求,连接就会自动断开。
换句话说,当连接进入TIME_WAIT
状态以后,我们不需要做任何事情,也无法做任何事情,我们所能做的唯一的事情就是等待一段时间以后,TIME_WAIT
的连接就会自动断开。
所以TIME_WAIT
的问题并不是连接没有被释放,而是这些TIME_WAIT
的连接被创建的太多了。
由于在该环境中,mysql
只有proxysql
在连接,所以我尝试修改了一些proxysql
中关于连接的参数mysql-free_connections_pct
、mysql-max_stmts_per_connection
等,但是依然无效。
最后,该问题的解决是通过修复mysql
中monitor
用户而解决的。
monitor
用户是proxysql
用以监控mysql
的用户,proxysql
会定时调用该用户从mysql
中获取数据。而在mysql
中该用户其实并没有被正确的创建,虽然在一开始我就从log
中发现了这个问题,但是我并不认为这会导致mysql
节点不可用,所以就忽略了这问题。但是,没有想到,虽然monitor
用户无法正常的连接到mysql
,但是会创建一个TIME_WAIT
的连接。而且,由于不同的尝试连接,导致连接数过大,造成mysql
无法使用。
在解决了问题之后,又通过简单的python
程序验证了这一状况。
import mysql.connector
for i in range(100):
try:
conn1 = mysql.connector.connect(user='aaa', password='aaa', host='192.168.100.10', port=3306)
except (mysql.connector.errors.ProgrammingError) as e:
print(i + 1, " : ", e)
print("the end")
在上面的脚本中,使用错误的用户名密码不断地连接数据库。
然后,通过监控TIME_WAIT
数量,发现虽然连接都失败了,但是每一次尝试连接都会产生一个TIME_WAIT
的连接。