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()

 

Spring-data-redis+Jedis+Redis集群导致的阻塞问题

4.2、JedisClusterConnection.bRPop

Spring-data-redis+Jedis+Redis集群导致的阻塞问题

4.3、ClusterCommandExecutor.executeMuliKeyCommand

Spring-data-redis+Jedis+Redis集群导致的阻塞问题

在这里我们看到了这个方法其实内部是异步执行的

4.4、executor的创建

Spring-data-redis+Jedis+Redis集群导致的阻塞问题

4.5、ThreadPoolTaskExecutor.corePoolSize

Spring-data-redis+Jedis+Redis集群导致的阻塞问题

大家发现了没有,corePoolSize是写死的1,并且在4.4、executor的创建的地方并没有修改它

4.6、能改变corePoolSize的大小吗?

卧槽,这有点出乎我的意料,居然没找到任何地方可以修改它的值

4.7、到此就能解释了为什么有一个队列里面有数据,却消费的非常慢的原因了,其实是另外两个队列里面没数据导致的阻塞,单线程执行,另外两个队列就把这个有数据的队列的阻塞住了。
三、影响范围

1、ClusterCommandExecutor里面使用executor的方法:executeCommandAsyncOnNodes、executeMuliKeyCommand

2、ClusterCommandExecutor.executeCommandAsyncOnNodes调用的地方

Spring-data-redis+Jedis+Redis集群导致的阻塞问题

3、ClusterCommandExecutor.executeCommandOnAllNodes调用的地方

Spring-data-redis+Jedis+Redis集群导致的阻塞问题

4、ClusterCommandExecutor.executeMuliKeyCommand调用的地方

Spring-data-redis+Jedis+Redis集群导致的阻塞问题

红色箭头标识的地方,就是本次问题发生的地方,一头冷汗,我觉得最危险的地方应该是del命令+blpop/brpop,如果这样组合起来,都不知道为什么del命令删不掉键了。

四、解决方案

1、试试高版本的spring-data-redis?

我们现在使用的是spring-data-redis-1.8.18.RELEASE.jar,本来以为高版本的会修复这个问题,然并卵

2、使用Redisson替换Jedis问题解决