记录一次由redis引起的线上系统假死问题

1、背景

        笔者所负责的一个线上系统在最近产生了一个难以排查的故障,该故障导致系统每隔几天就必须重启才能够恢复正常,每次重启的间隔时间不等,有时候三四天就得重启一次,有时候七八天才需要重启一次,如若不重启,那系统就无法使用,包括登录验证码也无法刷出。

        为了排查这个问题,集结了部门专业运维以及小组架构众多一起拉应用dump进行分析,分析过程中只发现应用线程数微高,但仍在可接受范围内,甚至没有发现存在堆栈溢出及报错情况,该问题也不了了之,每次出现假死现象时暂时也只有通过重启应用解决问题,因此排查该问题的重担就落到笔者身上。

2、故障解析

        至于为什么知道是由redis引起的系统假死,那是因为某次系统又再次面临假死问题时,笔者发现有的接口能够正常访问,而有的接口却拿不到响应,顺着这条线索,在对多次系统假死时现象的分析后得出这两类接口的差异之处,就是一类接口存在redis操作,而另一类接口不存在redis操作。

2.1 连接池资源无法归还

        至此,得出了系统是因redis而引起的假死,首先我们排除了redis服务端的问题,因为这套redis被多个应用所使用,而单单笔者所负责的系统存在这个问题,因此问题在于系统之内,排除业务代码引起的假死,那问题就只在于所使用的redis框架本身。

        我们使用的是spring-data-redis-2.1.5.RELEASE的集成环境,底层使用的是2.9.1版本的jedis框架,分析Jedis源码可以看出当有用户请求操作redis时,由于笔者所负责系统使用的是redis哨兵集群,因而首先通过下图中的getResource()方法获取到一个Jedis连接对象,并将具体的Pool对象给予Jedis连接实例,以供后续redis操作完成后进行连接池资源的归还等。

        分析完是如何从连接池获取jedis连接池实例后,下一步我们再来看看连接池资源的归还,下图即是连接池资源归还过程的主干源码,此处的this.dataSource便是上文提及的给予Jedis连接实例的Pool对象,此处是先进行连接池资源的归还,再将该redis连接实例的dataSource实例置空,那在高并发请求的环境下就会存在这么一个问题,当某个Jedis连接实例刚把资源归还给连接池后,就有下一个用户端请求获取到了该Jedis连接实例,而当前线程又恰好将该Jedis连接实例的dataSource置空,那就会导致下一个用户端请求在进行redis操作完成后,无法进行连接池资源的归还,以至于出现连接池连接数被打满的假象,导致无连接可用。

2.2 请求无法得到响应

        我们再来回顾上文提到的连接实例获取的过程,Jedis实例最终是通过Pool类的getResource()方法来获取的。

        在进行borrowObject时,需要传入一个时间参数,该时间默认情况下为-1,即永远阻塞等待,当连接池资源耗尽时,takeFirst拿到的连接实例状态都是非空闲的,那这个时候p永远为null,也就导致客户端请求永远阻塞等待获取可用连接。

 3、解决方案

        解决该问题的有效方案有两种,其一是升级Jedis框架,其二是使用其他redis框架,如lettuce、redission等,此处小编受限于历史业务,选择了方案一。

        方案一升级Jedis版本后再阅读源码可以发现,close方法先置空Jedis连接实例,再进行连接池资源的归还,对高并发环境下多个客户端持有相同Jedis实例时的同步操作可能导致的问题进行了防治。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值