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();
    }
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章