JedisClusterMaxRedirectionsException:Too many Cluster redirections

JedisClusterMaxRedirectionsException:Too many Cluster redirections

Redis异常信息:

redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException: Too many Cluster redirections? at
redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:143) ~
[jedis-3.0.1.g-RELEASE.jar:na] at 
redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:203) ~
[jedis-3.0.1.g-RELEASE.jar:na] at 
redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:203) ~
[jedis-3.0.1.g-RELEASE.jar:na] at 
redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:203) ~
[jedis-3.0.1.g-RELEASE.jar:na] at 
redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:203) ~
[jedis-3.0.1.g-RELEASE.jar:na] at 
redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:203) ~

Redis异常分析:

为什么会runWithRetries,为什么会有Too many Cluster redirections,下面从我们自身程序、JedisCluster源码说起,到如何解决这个问题。

private T runWithRetries(byte[] key, int redirections, boolean tryRandomNode, boolean asking) {
    if (redirections <= 0) {
      throw new JedisClusterMaxRedirectionsException("Too many Cluster redirections?");
    }

    Jedis connection = null;
    try {

      if (asking) {
        // TODO: Pipeline asking with the original command to make it
        // faster....
        connection = askConnection.get();
        connection.asking();

        // if asking success, reset asking flag
        asking = false;
      } else {
        if (tryRandomNode) {
          connection = connectionHandler.getConnection();
        } else {
          connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
        }
      }

      return execute(connection);
    } catch (JedisConnectionException jce) {
      if (tryRandomNode) {
        // maybe all connection is down
        throw jce;
      }

      // release current connection before recursion
      releaseConnection(connection);
      connection = null;

      // retry with random connection
      return runWithRetries(key, redirections - 1, true, asking);
    } catch (JedisRedirectionException jre) {
      // if MOVED redirection occurred,
      if (jre instanceof JedisMovedDataException) {
        // it rebuilds cluster's slot cache
        // recommended by Redis cluster specification
        this.connectionHandler.renewSlotCache(connection);
      }

      // release current connection before recursion or renewing
      releaseConnection(connection);
      connection = null;

      if (jre instanceof JedisAskDataException) {
        asking = true;
        askConnection.set(this.connectionHandler.getConnectionFromNode(jre.getTargetNode()));
      } else if (jre instanceof JedisMovedDataException) {
      } else {
        throw new JedisClusterException(jre);
      }

      return runWithRetries(key, redirections - 1, false, asking);
    } finally {
      releaseConnection(connection);
    }
  }



1. 我们new一个JedisCluster时,JedisCluster会根据我们传进去的ip跟port建立一个JedisPool实例(JedisPool对应着redis集群的一个节点,假设这个ip跟port对应着Node5),并从这个JedisPool实例中得到一个jedis实例,然后这个jedis实例执行cluster nodes命令,最后我们就知道整个集群的节点情况,包括每个节点的ip跟port,每个节点负责处理的slot(槽位),JedisCluster会缓存这些信息到node pools(Map),slots(Map)。 注意:这里已经建立了所有节点对应的JedisPool实例,但这个JedisPool池里面暂时还没有Jedis实例(除了我们指定ip和prot的那个JedisPool里有一个Jedis实例)。

2. 当我们要处理一个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次一样,抛出一开始所说的异常。

3. redis为什么会连不上,我们知道redis是单线程的,你可以想象当有很多client试图连接redis服务器时,他们排队(其实不是)等待连接,回到我们的程序,当有一个CalculateBolt要连到Node1节点时,这时由于我们处理的数据的不均衡性,很有可能所有其他的CalculateBolt都在这个Node1节点上排队,再加上网络的延迟等等因素,超时了,下次redirect后,还是超时,好,异常抛出。
4. 我们知道异常抛出的原因后,其实我们有很多fix方法的,如建立JedisPool时,先实例化一个到redis节点的jedis实例(jedis实例里有socket连接),但现在只有我们指定ip跟port的那个JedisPool有一个jedis实例,其他的是没有的,要在第一次访问时建立。当然最简单的当然是增大超时时间了。

 


 

猜你喜欢

转载自blog.csdn.net/xiao__jia__jia/article/details/104623476