redis connect timeout问题排查

问题表现:周期性出现connect timeout: 具体如下:

redis.clients.jedis.exceptions.JedisConnectionException

java.net.SocketException

java.net.SocketTimeoutException: connect timed out

问题定位:redis 参数tcp-backlog默认设置过小(还有别的可能会造成上面的问题,但是我这次是因为这个)

这个场景下,如果我们到服务器上看看 listen情况,watch "netstat -s | grep listen",会看到“xxx times the listen queue of a socket overflowed”,并且这个xxx在不断增加,这个xxx就是我们没有对网络请求正常处理的次数

问题分析:

首先说下TCP中backlog

Linux内核为每个TCP服务器程序维护两条backlog队列,一条是TCP层的未连接队列,一条是应用层的已连接队列,分别对应net.ipv4.tcp_max_syn_backlog和net.core.somaxconn两个内核参数。

一个客户端连接在完成TCP 3次握手之前首先进入到未连接队列,完成握手之后正式建立连接,进入已连接队列,交付给应用程序处理。应用程序调用accept()函数从已连接队列取出连接进行处理。应用层在调用listen()函数时指定的backlog是已连接队列大小,如果大于somaxconn将被设为somaxconn。

解释:针对redis server来说已经完成三次握手的tcp连接进入accept 队列,然后等待redis server去从accept 队列中取出来和redis server建立连接,然后accept 队列就会减少一个值。这里说的accept 队列是由net.core.somaxconn和redis参数tcp-backlog的最小值来控制!

核心问题:如果应用层不调用accept()函数处理一个连接,或者处理不及时的话,将会导致已连接队列堆满。已连接队列已满的话会导致未连接队列在处理完3次握手之后无法进入已连接队列,最终也导致未连接队列堆满,在服务器看到处于未连接队列中的连接状态为SYN_RECV。 新进来的客户端连接将会一直处于SYN_SENT状态等待服务器的ACK应答,最终导致连接超时。

查看队列大小:

查看未连接队列值:

[root@t1-26-89 redis-3.0.7]# cat /proc/sys/net/ipv4/tcp_max_syn_backlog

262144

查看已连接队列值:

[root@t1-26-89 redis-3.0.7]# cat /proc/sys/net/core/somaxconn

32768

修改队列大小:可以直接改写这两个文件的值。要永久修改这两个内核参数的话可以写到/etc/sysctl.conf:

改完后执行sysctl -p 让修改立即生效。

vim /etc/sysctl.conf

net.ipv4.tcp_max_sync_backlog=1024

net.core.somaxconn = 2048

sysctl -p

再次解释下tcp三次握手:

我们看到Send-Q的值为100, 即是我们配置的tcp-backlog值. 为了搞清楚这个值的意思, 了解了下tcp的三次握手进行中的一些queue的知识. 参考下图我们可以看到在server接收到sny的时候会进入到一个syn queue队列, 当server端最终收到ack时转换到accept queue队列. 然后通过ss或者netstat就可以看到listen状态下的连接, 其Send-Q就是这个accept queue队列的最大值. 只有server端执行了accept后才会从这个队列中移除这个连接. 这个值的大小是受somaxconn影响的, 因为是取的它们两者的最小值, 所以如果要调大的话必需修改内核的somaxconn值.

关于redis 的参数tcp-backlog:

默认值 511, 如果tcp-backlog大于/proc/sys/net/core/somaxconn值,则截断为/proc/sys/net/core/somaxconn值

tcp-backlog:511

此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度, 当然此值必须不超过Linux系统定义的/proc/sys/net/core/somaxconn值(默认参数值是128),tcp-backlog参数默认是511。当系统并发量大并且客户端速度缓慢的时候,可以将这二个参数一起参考设定。


建议修改为 2048

修改somaxconn

该内核参数默认值一般是128,对于负载很大的服务程序来说大大的不够。一般会将它修改为2048或者更大。

echo 2048 > /proc/sys/net/core/somaxconn 但是这样系统重启后保存不了

在/etc/sysctl.conf中添加如下

net.core.somaxconn = 2048

然后在终端中执行

sysctl -p

总结:redis的每个参数都值得研究,做出合适的调整,针对redis server来说已经完成三次握手的tcp连接进入accept 队列,然后等待redis server去从accept 队列中取出来和redis server建立连接,然后accept 队列就会减少一个值。这里说的accept 队列是由net.core.somaxconn和redis参数tcp-backlog的最小值来控制!

请使用浏览器的分享功能分享到微信等