Redis学习--JedisCluster源码解读

JedisCluster
  1. JedisCluster是针对RedisCluster的JAVA客户端,它封装了java访问redis集群的各种操作,包括初始化连接,请求重定向等操作。具体内部实现原理主要有如下两个方面:
    1.1. JedisCluster初始化时,所有的集群连接信息都是封装在JedisClusterInfoCache这类中。
    1.2. JedisClusterInfoCache类中有两个非常重要的Map数据结构,分别是
 /**
  **nodes存放集群IP地址信息和JedisPool。其中String是IP:Port信息,集群中有多少个节点,IP:Port就有多少个。
  **/
 Map<String, JedisPool> nodes = new HashMap<String, JedisPool>();
 /**
 **slots中存储key-value是集群中数据槽的编号和jedisPool实例。即slots中有16384个键值对。
 **/
 Map<Integer, JedisPool> slots = new HashMap<Integer, JedisPool>();
JedisCluster类图

JedisCluster类图
1. 从图中可以看出,JedisCluster主要是继承二进制的BinaryJedisCluster类,这个类中的各种操作都是基于字节数组方式进行的。而且BinaryJedisCluster类实现的4个接口中有3个是基于字节数组操作。
2. JedisCluster实现了JedisCommands,MultiKeyJedisClusterCommands,JedisClusterScriptingCommands接口。这三个接口提供的基于字符串类型的操作,即key都是字符串类型。
3. BasicCommands是关于redis服务本身基本操作,比如save,ping,bgsave等操作。
4. MultiKeyBinaryJedisClusterCommands和MultiKeyJedisClusterCommands接口一个字节数组的批量操作,一个是字符串的批量操作。

JedisClusterCommand
  1. 在JedisCluster客户端中,JedisClusterCommand是一个非常重要的类,采用模板方法设计,高度封装了操作Redis集群的操作。对于集群中各种存储操作,提供了一个抽象execute方法。
  2. JedisCluster各种具体操作Redis集群方法,只需要通过匿名内部类的方式,灵活扩展Execute方法。
  3. 内部通过JedisClusterConnectionHandler封装了Jedis的实例。
  4. JedisClusterCommand源码分析:
/**
**该类主要由两个重点:采用模板方法设计,具体存取操作有子类实现
**在集群操作中,为了保证高可用方式,采用递归算法进行尝试发生的MOVED,ASK,数据迁移操作等。
**/
public abstract class JedisClusterCommand<T> {
  // JedisCluster的连接真正持有类
  private JedisClusterConnectionHandler connectionHandler;
  // 尝试次数,默认为5
  private int maxAttempts;
  private ThreadLocal<Jedis> askConnection = new ThreadLocal<Jedis>();

  public JedisClusterCommand(JedisClusterConnectionHandler connectionHandler, int maxAttempts) {
    this.connectionHandler = connectionHandler;
    this.maxAttempts = maxAttempts;
  }
 // redis各种操作的抽象方法,JedisCluster中都是匿名内部类实现。
  public abstract T execute(Jedis connection);
 // 
  public T run(String key) {
    if (key == null) {
      throw new JedisClusterException("No way to dispatch this command to Redis Cluster.");
    }

    return runWithRetries(SafeEncoder.encode(key), this.maxAttempts, false, false);
  }


  /***
  *** 该方法采用递归方式,保证在往集群中存取数据时,发生MOVED,ASKing,数据迁移过程中遇到问题,也是一种实现高可用的方式。
  ***该方法中调用execute方法,该方法由子类具体实现。
  ***/
  private T runWithRetries(byte[] key, int attempts, boolean tryRandomNode, boolean asking) {
    if (attempts <= 0) {
      throw new JedisClusterMaxRedirectionsException("Too many Cluster redirections?");
    }

    Jedis connection = null;
    try {
      /**
       *第一执行该方法,asking为false。只有发生JedisAskDataException
       *异常时,才asking才设置为true
       **/
      if (asking) {
        connection = askConnection.get();
        connection.asking();

        // if asking success, reset asking flag
        asking = false;
      } else {
        // 第一次执行时,tryRandomNode为false。
        if (tryRandomNode) {
          connection = connectionHandler.getConnection();
        } else {
        /** 根据key获取分配的嘈数,然后根据数据槽从JedisClusterInfoCache 中获取Jedis的实例
        **/
          connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
        }
      }
     /***
     ** 调用子类方法的具体实现。
     **/
      return execute(connection);

    } catch (JedisNoReachableClusterNodeException jnrcne) {
      throw jnrcne;
    } catch (JedisConnectionException jce) {
      //释放已有的连接
      releaseConnection(connection);
      connection = null;
       /***
        ***只是重建键值对slot-jedis缓存即可。已经没有剩余的redirection了。
        ***已经达到最大的MaxRedirection次数,抛出异常即可。
        ***/
      if (attempts <= 1) {
        this.connectionHandler.renewSlotCache();
        throw jce;
      }
      // 递归调用该方法
      return runWithRetries(key, attempts - 1, tryRandomNode, asking);
    } catch (JedisRedirectionException jre) {
      // if MOVED redirection occurred,
      if (jre instanceof JedisMovedDataException) {
        // 发生MovedException,需要重建键值对slot-Jedis的缓存。
        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, attempts - 1, false, asking);
    } finally {
      releaseConnection(connection);
    }
  }

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

}
JedisClusterInfoCache
  1. 这个缓存类主要完成如下功能:
    1.1. 缓存键值对IP:Port——>JedisPool,缓存键值对slot——>JedisPool。
  2. 将redisCluster中的每一个数据槽对应的jedis实例事先加入缓存,每个数据节点的连接信息缓存到本地中。
  3. 该类中最重要的方法就是discoverClusterNodesAndSlots(Jedis),源码如下:
public void discoverClusterNodesAndSlots(Jedis jedis) {
    w.lock();

    try {
      reset();
      /**根据当前redis实例,获取集群中master,slave节点信息。包括每个master节点 上分配的数据嘈。slots结果如下:
      ****[10923, 16383, [[B@924fda2, 9000], [[B@5b879b5e, 9001]]]
      *** [[5461, 10922, [[B@3681fe9a, 7001], [[B@10724c6b, 8000]], 
      *** [0, 5460, [[B@3ff70d3c, 7000], [[B@7485fef2, 8001]],
      ***/
      List<Object> slots = jedis.clusterSlots();
      // 遍历List中的集合,总共就3个master节点信息
      for (Object slotInfoObj : slots) {
        // slotInfo指一个master,slave,分配槽的个数信息
        List<Object> slotInfo = (List<Object>) slotInfoObj;
        if (slotInfo.size() <= MASTER_NODE_INDEX) {
          continue;
        }
        // 获取分配到每一个master节点的数据槽的个数
        List<Integer> slotNums = getAssignedSlotArray(slotInfo);
        /**slotInfo的大小为4,其中前两项为槽数的最小值和最大值。
        *** 后两项为master,slave实例信息
        ***[10923, 16383, [[B@924fda2, 9000], [[B@5b879b5e, 9001]]]
        ***/
        int size = slotInfo.size();
        for (int i = MASTER_NODE_INDEX; i < size; i++) {
          // hostInfos就是[B@924fda2, 9000]集合。就俩元素
          List<Object> hostInfos = (List<Object>) slotInfo.get(i);
          if (hostInfos.size() <= 0) {
            continue;
          }
          //根据ip,port构建HostAndPort实例
          HostAndPort targetNode = generateHostAndPort(hostInfos);
          /**根据HostAndPort解析出ip:port的key值,
           **再根据key从缓存中查询对应的jedisPool实例。如果没有jedisPool实例,
          **就创建JedisPool实例,最后放入缓存中。
          ** key的值是 ip:port,value的值是jedisPool
          **/
          setupNodeIfNotExist(targetNode);
          if (i == MASTER_NODE_INDEX) {
            // 将每一个数据槽对应的jedisPool缓存起来。
            // key的值是:数据槽的下标,value的值是 JedisPool。
            // slots缓存中总共有16384的key-value键值对
            assignSlotsToNode(slotNums, targetNode);
          }
        }
      }
    } finally {
      w.unlock();
    }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值