Redis Cluster路由请求原理详解

https://cloud.tencent.com/developer/article/1605813

Redis Cluster的客户端采用直连Redis的方式。

一、MOVED重定向

Redis Cluster发送redis指令时,先根据key计算出对应的slot,在根据slot从客户端的slot与node的映射表中找到对应的node,然后发送redis指令:

  • 如果指令在这个node上,则处理指令;
  • 如果不在这个node上,则redis会返回给客户端MOVED重定向错误,通知客户端重新请求正确的node, 这个过程称为MOVED重定向。
    在这里插入图片描述
    重定向信息中包含了key, slot,node的地址,根据这些信息,客户端就可以去请求正确的node。

在使用redis-cli时,可以加入-c参数,支持自动重定向,简化手动发起重定向的操作:
在这里插入图片描述
这里redis-cli自动帮我们连接到了正确的node, 这个过程是redis-cli内部维护的,他先收到了MOVED信息,然后向新node发起请求。

二、key命令执行分2步

1、计算slot

根据key的有效部分使用CRC16函数计算出散列值,在对16383取余,得到slot编号。这样的key都会映射到0~16383槽范围内:
在这里插入图片描述

2、查找slot对应的node

根据MOVED重定向机制,Redis客户端可以随机连接集群内任一redis节点获取其slot所在的node,这种客户端又叫Dummy(傀儡)客户端,他优点是代码实现简单,对客户端协议影响小,只需要根据重定向信息再次发送请求即可。

但他的弊端也很明显,就是需要重定向才能找到要执行命令的节点,额外增加了IO,这不是高效的方式,所以Redis Cluster通常采用Smart客户端。

三、Smart客户端

Smart客户端通过内部维护slot–>node的映射关系,本地就可以根据key找到node,从而避免额外IO,而MOVED重定向负责协助Smart客户端更新slot–>node映射。

3.1、JedisCluster操作集群的过程

首先JedisCluster初始化时,会选择一个正在运行的node, 初始化slot和node的映射关系,使用cluster slots命令完成:
在这里插入图片描述
Jedis解析Cluster slots结果,并缓存到本地,并为每个节点创建唯一的JedisPool连接池。映射关系在JedisClusterInfoCache类中
在这里插入图片描述
JedisCluster执行命令的过程:

public abstract class JedisClusterCommand<T> {
    
    

  // 集群节点连接处理器
  private JedisClusterConnectionHandler connectionHandler;
  // 重试次数,默认5次
  private int redirections;
  private ThreadLocal<Jedis> askConnection = new ThreadLocal<Jedis>();

  // 模板回调方法
  public abstract T execute(Jedis connection);

  // 利用重试机制运行命令
  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 {
    
    
          // 使用 slot 缓存获取目标节点连接
          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
        // 如果出现 MOVED 重定向错误 , 在连接上执行 cluster slots 命令重新初始化 slot 缓存
        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);
      }

      // slot 初始化后重试执行命令
      // 每次命令重试对redirections参数减1
      return runWithRetries(key, redirections - 1, false, asking);
    } finally {
    
    
      releaseConnection(connection);
    }
  }

  private void releaseConnection(Jedis connection) {
    
    
    if (connection != null) {
    
    
      connection.close();
    }
  }

}

整个流程为:
1、根据key计算出slot, 并根据slot找到node建立连接,执行命令
2、如果连接出现错误,则使用随机连接重新执行命令,每次命令重试对redirections参数减1
3、捕获到MOVED重定向错误,使用cluster slots命令更新slots缓存
4、重复执行前3步,知道命令执行成功,或者当redirections<=0 时抛出异常。

四、ASK重定向

4.1、客户端ASK重定向流程

当slot中的数据从源节点到目标节点迁移过程中,客户端需要保证key的命令可正常执行。
例如,当一个slot数据从源节点迁移到目标节点时,期间可能出现一部分key在源节点,而另一部分key在目标节点,这时,客户端key命令的执行流程为:
1、客户端根据本地slot与node的映射关系,发送请求到node上,如果该node上存在key对象,则直接执行并给客户端返回结果
2、如果该node上key不存在,则可能存在于目标节点,这时源节点会回复ASK重定向异常,格式如下:

扫描二维码关注公众号,回复: 12678361 查看本文章
(error) ASK {
    
    slot} {
    
    targetIP}:{
    
    targetPort}

3、客户端收到ASK重定向指令后,先去目标节点执行一个不带参数的 asking 命令,然后在目标节点执行原理的操作请求指令。
说明:

  • 客户端先执行一个 asking 指令的原因是,在迁移完成之前,按道理来说,这个slot还是不属于目标节点的,于是目标节点会跟客户端返回 -MOVED 指令,让客户端去源节点执行操作,这样就会形成“互相推脱”的重定向循环。
  • asking 指令就是告诉目标节点,我的指令必须你处理,请求slot就当成是你的吧

4、如果目标节点存在这个key,就执行命令,不存在则返回不存在信息。
5、迁移会影响指令执行的效率,在正常情况下,一次请求就可以完成操作,而迁移过程中,客户端需要做3次请求(发送给源节点,asking指令到目标节点,发送给目标节点真正的处理请求)

ASK与MOVED虽然都是客户端重定向指令,但有着本质的区别:

  • ASK重定向:说明集群正在进行slot数据迁移,客户端无法知道什么时候迁移完成,因此只是临时性重定向,客户端不会更新slots缓存。
  • MOVED重定向:说明key对应的slot已经明确到了新的节点,因此需要更新slots缓存。

4.2、slot迁移感知

如果某个slot已经迁移完了,客户端如何才能知道slot与node关系的变化呢?即何时更新客户端上slot–>node的映射关系表呢?

在RedisCluster客户端接收到-MOVED重定向后,会执行刷新缓存slot–>node映射表

猜你喜欢

转载自blog.csdn.net/shijinghan1126/article/details/108641728