Redis學習--JedisCluster源碼解讀

來源:互聯網
上載者:User
JedisCluster 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類圖


1. 從圖中可以看出,JedisCluster主要是繼承二進位的BinaryJedisCluster類,這個類中的各種操作都是基於位元組數組方式進行的。而且BinaryJedisCluster類實現的4個介面中有3個是基於位元組數組操作。
2. JedisCluster實現了JedisCommands,MultiKeyJedisClusterCommands,JedisClusterScriptingCommands介面。這三個介面提供的基於字串類型的操作,即key都是字串類型。
3. BasicCommands是關於redis服務本身基本操作,比如save,ping,bgsave等操作。
4. MultiKeyBinaryJedisClusterCommands和MultiKeyJedisClusterCommands介面一個位元組數組的大量操作,一個是字串的大量操作。 JedisClusterCommand 在JedisCluster用戶端中,JedisClusterCommand是一個非常重要的類,採用模板方法設計,高度封裝了操作Redis叢集的操作。對於叢集中各種儲存操作,提供了一個抽象execute方法。 JedisCluster各種具體操作Redis叢集方法,只需要通過匿名內部類的方式,靈活擴充Execute方法。 內部通過JedisClusterConnectionHandler封裝了Jedis的執行個體。 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. 緩衝索引值對IP:Port——>JedisPool,緩衝索引值對slot——>JedisPool。 將redisCluster中的每一個資料槽對應的jedis執行個體事先加入緩衝,每個資料節點的串連資訊緩衝到本地中。 該類中最重要的方法就是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();    }  }
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.