记录一次Redis连接数打满引发的生产故障

一. 现象

1.15点50分开始,grafana监控无法查看生产token redis集群各个节点的监控数据,只剩其中一台从节点:..92.198,断断续续可以获得监控数据。
2.15点52分开始,
** 等团队反馈业务系统无法使用。
3.16点30分,某台从节点的服务端日志可以看出,连接主节点出现了超时的现象
在这里插入图片描述
4.应用容器内部,发现大量redis报错,报错显示,无法从连接池获取资源。
在这里插入图片描述

5.16点34分,** 团队反馈生产应用出现大量异常日志:

平台redis连接异常:
在这里插入图片描述
6.16点41分,进入某一台从节点 ..37.122,执行客户端命令时,报客户端连接数达上限的报错:
在这里插入图片描述
7.16点45分,进入某一台从节点 .
.37.122,执行客户端命令info clients时,显示客户端连接数,达到了9600多,距离默认配置的上限10000很接近。
在这里插入图片描述
默认配置:
在这里插入图片描述
8.16:58分左右,修改从节点 ..37.122的maxclients配置,从10000调整到20000,并重启。重启后,部分业务系统反馈已能正常使用,但是仍有部分不行。没多久后发现这台机器得客户端连接数就开始飙升,并达到10000上限。

9.17:18分左右,重启剩余得两台从节点 ..92.198, ..213.88后,redis几台节点,客户端连接数均恢复正常。

10.17:25左右,应用恢复后,**等依赖系统逐步恢复。

二. 故障分析

1.client list命令能列出与Redis服务端相连的所有客户端连接信息,在某一台从机重启前执行此命令:
在这里插入图片描述
client list中的age和idle分别代表当前客户端已经连接的时间和最近一次的空闲时间。上面截图可以看出,age基本等于idle,实际上这种就属于不太正常的情况,说明连接一直处于空闲状态。

2.从zabbix观察4台redis机器的TCP连接数:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.syn_recv出现原因:

如果服务器上出现大量的syn_recv状态的TCP连接说明这些连接一直没有收到ACK包,这主要有两种可能,一种是对方(请求方或客户端)没有收到服务器发送的[SYN,ACK]包,另一种可能是对方收到了[SYN,ACK]包却没有ACK。对于第一种情况一般是由于网络结构或安全规则限制导致(SYN,ACK)包无法发送到对方。对于第二种情况要稍微复杂一些,这种情况还有两种可能:一种是对方根本就不打算ACK,一般在对方程序有意为之才会出现,如SYN Flood类型的DOS/DDOS攻击;另一种可能是对方收到的[SYN,ACK]包不合法,常见的是SYN包的目的地址(服务地址)和应答[SYN,ACK]包的源地址不同。这种情况在只配置了DNAT而不进行SNAT的服务网络环境下容易出现,主要是由于inbound(SYN包)和outbound([SYN,ACK]包)的包穿越了不同的网关/防火墙/负载均衡器,从而导致[SYN,ACK]路由到互联网的源地址(一般是防火墙的出口地址)与SYN包的目的地址(服务的虚拟IP)不同,这时客户机无法将SYN包和[SYN,ACK]包关联在一起,从而会认为已发出的SYN包还没有被应答,于是继续等待应答包。这样服务器端的连接一直保持在syn_recv状态(半开连接)直到超时。结合当时运维反馈K8S网络组件服务出现异常,所以当时很有可能是第一种情况导致。

4.应用对于redis超时部分的设置:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
connectionTimeout(spring.redis.pool.timeout):表示连接超时时间,soTimeout(spring.redis.pool.timeout):表示读取数据超时时间。maxWaitMillis(spring.redis.pool.max-wait):客户端和Redis服务器建立连接的等待时间。这三个配置在生产环境,均配置的是5000,即5秒。

5.tcp建连数超出阈值原因:

从1.7可以看出,客户端连接数最大值为默认值10000,而2.2可以明显看出,4台机器的客户端连接数已经达到甚至超过了10000, 结合2.3发现的大量的syn_recv状态的TCP连接,可以断定,造成此问题的原因在于TCP超时重传机制,此机制是TCP协议保证数据可靠性的一个重要机制,其原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的ACK报文,那么就重新发送数据,直到发送成功为止。再结合2.4的应用超时配置,以及生产当时单个从节点每秒执行3~4万的命令数,在超时阻塞连接得不到释放的情况下,是很容易达到10000的连接数上限的。老的连接会迟迟得不到释放,我们在2.1发现的大量idle连接,也佐证了这一点。

三. 优化思路

1.Redis默认的timeout是0,也就是不会检测客户端的空闲。在实际开发和运维中,需要将timeout设置成⼤于0,例如可以设置为3秒,同时在客户端使⽤上添加空闲检测和验证等等措施。以JedisPoolConfig在空闲检测上的⼀些设置为例:

public class JedisPoolConfig extends GenericObjectPoolConfig {
        public JedisPoolConfig() {
            //在连接池空闲时是否测试连接对象的有效性,默认false
             setTestWhileIdle(true);
            //设置连接最小的逐出间隔时间,默认1800000毫秒
             setMinEvictableIdleTimeMillis(6000);
            //设置连接对象有效性扫描间隔,设置为-1,则不运行逐出线程
             setTimeBetweenEvictionRunsMillis(3000);
             //每次逐出检查时,逐出连接的个数
             setNumTestsPerEvictionRun(-1);
        }
    }
  1. 修改redis服务端配置文件redis.conf, 将maxclients适当调大,比如由原来的10000调大成20000。

  2. 修改应用redis的超时配置:
    connectionTimeout(spring.redis.pool.timeout), soTimeout(spring.redis.pool.timeout), maxWaitMillis(spring.redis.pool.max-wait):这三个配置在生产环境,可以适当调小,比如3秒。

  3. 故障的时候,可以写个脚本:cat 1.txt | awk -F " " ‘{print $2}’ | awk -F : ‘{print $1}’ |sort |uniq -c |sort -rn,ip连接数排行一下。1.txt就是执行client list的结果。获得主机ip后,再通过这个ip反向查找是哪个云应用。然后把对应的client端应用也重启一下。如果情况仍得不到好转,可以直接禁用部分故障节点,保障其他节点的正常运转。

  4. 降低对redis的依赖,改为jwt

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值