JedisRedirectionException:Too many Cluster redirections

最近用Jedis作为redis客户端,经常碰到这个问题,程序刚启动,没几秒钟,就出现这个问题:

redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException: Too many Cluster redirections?
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:38)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:86)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:71)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:86)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:71)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:86)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:71)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:86)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:71)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:86)
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:71)
at redis.clients.jedis.JedisClusterCommand.run(JedisClusterCommand.java:32)
at redis.clients.jedis.JedisCluster.hsetnx(JedisCluster.java:325)

一开始怀疑redis集群没搭好,网络不稳定,程序写的不好导致,排查了很长时间,最后还是从错误log去排查,为什么会runWithRetries,为什么会有Too many Cluster redirections,下面从我们自身程序、JedisCluster源码说起,到如何解决这个问题,以及一些Jedis不足的地方。

1. 我们系统用jstorm+redis集群对数据进行处理,并导出统计结果。Spout每分钟要分发100万条记录给下游的CalculateBolt(50个Task,在不同的节点上运行),CalculateBolt对每条记录进行处理,与redis现有数据比对,更新计数器等等。最后由OutputBolt导出统计结果。在每个CalculateBolt中都有一个JedisCluster全局变量(private JedisCluster jedisCluster),并在prepare方法中初始化,在execute方法中处理记录。

2. 我们new一个JedisCluster时,JedisCluster会根据我们传进去的ip跟port建立一个JedisPool实例(JedisPool对应着redis集群的一个节点,假设这个ip跟port对应着Node5),并从这个JedisPool实例中得到一个jedis实例,然后这个jedis实例执行cluster nodes命令,最后我们就知道整个集群的节点情况,包括每个节点的ip跟port,每个节点负责处理的slot(槽位),JedisCluster会缓存这些信息到node pools(Map<String,JedisPool>),slots(Map<Integer,JedisPool>)。

注意:这里已经建立了所有节点对应的JedisPool实例,但这个JedisPool池里面暂时还没有Jedis实例(除了我们指定ip和prot的那个JedisPool里有一个Jedis实例)。

3. 当我们要处理一个key时,正常情况下,根据CRC16得到slot,然后根据这个slot从slots映射中得到对应的JedisPool,然后从这个Pool中得到Jedis(new Socket对象,设置KeepAlive,TcpNoDelay等参数,建立连接,设置read超时时间等),然后操作redis命令。但建立连接时,是有时间期限的,2秒钟,如果2秒钟内没有建立,返回JedisConnectionException异常,我们捕获异常,然后重试。这里的重试就是redirect retry了,重试的流程是不一样的,重试时会Collections.shuffle(pools),这里的pools就是上面的node pools,这时的pools已经乱序了,建立连接,然后访问,然后redis会返回move data指令,告知去哪个正确的节点上执行命令,然后jedis会更新slot到JedisPool的映射关系(我们会操作redis集群,进行线性扩展,槽位可能改变),这样就完成一次redirect了。然后jedis从新开始,试图得到connection,又超时,又redirect,5次一样,抛出一开始所说的异常。

4. redis为什么会连不上,我们知道redis是单线程的,你可以想象当有很多client试图连接redis服务器时,他们排队(其实不是)等待连接,回到我们的程序,当有一个CalculateBolt要连到Node1节点时,这时由于我们处理的数据的不均衡性,很有可能所有其他的CalculateBolt都在这个Node1节点上排队,再加上网络的延迟等等因素,超时了,下次redirect后,还是超时,好,异常抛出。

5. 我们知道异常抛出的原因后,其实我们有很多fix方法的,如建立JedisPool时,先实例化一个到redis节点的jedis实例(jedis实例里有socket连接),但现在只有我们指定ip跟port的那个JedisPool有一个jedis实例,其他的是没有的,要在第一次访问时建立。当然最简单的当然是增大超时时间了,但变态的是,这个超时时间在jedis里竟然是硬编码,Protocol.DEFAULT_TIMEOUT,你要改,下载源码,修改,重新编译打包,我不清楚为什么这个是硬编码,也许jedis作者认为2s已经是很长的时间了,不可能连接超时了。

6. 我是用上面第二点,增大超时时间,幸好有github,幸好有maven,改起来也算快速。但也算是Jedis需要改进的地方了。Jedis还需要改进的是它的异常捕获,很垃圾。举个例子,上面的异常中的71行其实是JedisConnectionException异常,但这个异常的栈信息却丢失了,其实我们知道最先抛出的应该是一个SocketTimeoutException,但这个RuntimeException在外面的上上层方法中,被捕获了,catch(Exception e),然后重新抛出了一个JedisConnectionException,但这个异常信息里却没有原先的异常栈信息,导致异常信息丢失。

转载于:https://my.oschina.net/lzm3/blog/914797

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值