Redis学习--JedisCluster源码解读

来源:互联网 发布:网络大电影编剧收费 编辑:程序博客网 时间:2024/06/06 00:09

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();    }  }
阅读全文
0 0
原创粉丝点击