Spring-data-redis+Jedis+Redis集群导致的阻塞问题
一、现象
1、项目上使用三个队列来缓存数据,发现其中有一个队列有很多数据,但是消费却非常慢
2、代码使用redisTemplate.opsForList().rightPop 来获取队列里面的数据,这个命令对应的Redis命令是bRPop
3、通过调用链查看,发现brpop命令执行的耗时非常长,最大响应时间达到2秒多
4、在Redis客户端直接使用BRPOP course 30获取数据,执行非常快,并没有感觉到阻塞
二、分析过程
1、BRPOP
是列表的阻塞式(blocking)弹出原语,它是 RPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BRPOP 命令阻塞,直到等待超时或发现可弹出元素为止。
2、测试环境是否可复现
通过测试环境测试,问题依旧
3、通过JedisCluster进行测试
采用JedisCluster直连的方式,发现并没有阻塞的迹象
4、源码分析
4.1、DefaultListOperations.rightPop()
4.2、JedisClusterConnection.bRPop
4.3、ClusterCommandExecutor.executeMuliKeyCommand
在这里我们看到了这个方法其实内部是异步执行的
4.4、executor的创建
4.5、ThreadPoolTaskExecutor.corePoolSize
大家发现了没有,corePoolSize是写死的1,并且在4.4、executor的创建的地方并没有修改它
4.6、能改变corePoolSize的大小吗?
卧槽,这有点出乎我的意料,居然没找到任何地方可以修改它的值
4.7、到此就能解释了为什么有一个队列里面有数据,却消费的非常慢的原因了,其实是另外两个队列里面没数据导致的阻塞,单线程执行,另外两个队列就把这个有数据的队列的阻塞住了。
三、影响范围
1、ClusterCommandExecutor里面使用executor的方法:executeCommandAsyncOnNodes、executeMuliKeyCommand
2、ClusterCommandExecutor.executeCommandAsyncOnNodes调用的地方
3、ClusterCommandExecutor.executeCommandOnAllNodes调用的地方
4、ClusterCommandExecutor.executeMuliKeyCommand调用的地方
红色箭头标识的地方,就是本次问题发生的地方,一头冷汗,我觉得最危险的地方应该是del命令+blpop/brpop,如果这样组合起来,都不知道为什么del命令删不掉键了。
四、解决方案
1、试试高版本的spring-data-redis?
我们现在使用的是spring-data-redis-1.8.18.RELEASE.jar,本来以为高版本的会修复这个问题,然并卵
2、使用Redisson替换Jedis问题解决