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();
}
}