如何簡單地實現易用的ShardedJedisSentinelPool

Jedis包中有個很噁心的問題,那就是包裏面有支持分片的ShardeJedis客戶端類,也有支持哨兵的池類JedisSentinelPool,就是沒有既支持分片又支持哨兵的池類,所以必須自己自定義一個ShardedJedisSentinelPool,定義這個類,在網上有個很受歡迎的版本,是繼承了Pool<ShardeJedis>類後重寫相關的池操作的方法,個人覺得這種方案太麻煩,而且據反饋也有很多考慮不全面的細節,造成bug。一開始我試圖去繼承JedisSentinelPool類並重寫相關方法把分配功能加進去,發現很難搞。後來改變了思路,其實可以建立一個JedisSentinelPool數組,每個元素其實對應監視一個主備的redis服務節點,然後可以根據分片規則去確定從哪個JedisSentinelPool中獲取jedis。這個方案比較簡單實用,唯一難點就是要去實現跟ShardeJedis一樣的一致性哈希分片規則。好在sharde.java中有相關源碼,只要稍作修改即可。實現源碼如下:

public class ShardedJedisSentinelPool {
private Map<String,JedisSentinelPool> poolMap = new HashMap<String,JedisSentinelPool>();
private Hashing algo = Hashing.MURMUR_HASH;
private TreeMap<Long, JedisShardInfo> nodes;

public ShardedJedisSentinelPool(Set<HostAndPort> hostInfo, Set<String> sentinels){
List<JedisShardInfo> list = new ArrayList<JedisShardInfo>();
for(HostAndPort iter:hostInfo){
JedisShardInfo info = new JedisShardInfo(iter.getHost(),iter.getPort());
list.add(info);
}
initialize(list);

for(HostAndPort iter:hostInfo){
String masterName = "master-"+iter.getHost()+":"+iter.getPort();
JedisSentinelPool sentinuelPool = new JedisSentinelPool(masterName, sentinels);
poolMap.put(masterName, sentinuelPool);
}
}

public Jedis getResource(String key){
JedisShardInfo shardInfo = getShardInfo(key.getBytes());
String masterName = "master-"+shardInfo.getHost()+":"+shardInfo.getPort();
JedisSentinelPool sentinuelPool = poolMap.get(masterName);
Jedis sentinueJedis = sentinuelPool.getResource();
return sentinueJedis;
}

private void initialize(List<JedisShardInfo> shards) {
   nodes = new TreeMap<Long, JedisShardInfo>();


   for (int i = 0; i != shards.size(); ++i) {
     final JedisShardInfo shardInfo = shards.get(i);
     if (shardInfo.getName() == null) for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
       nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);
     }
     else for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
       nodes.put(this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo);
     }
   }
 }

 public JedisShardInfo getShardInfo(byte[] key) {
   SortedMap<Long, JedisShardInfo> tail = nodes.tailMap(algo.hash(key));
   if (tail.isEmpty()) {
     return nodes.get(nodes.firstKey());
   }
   return tail.get(tail.firstKey());
 }
 
 public void close(){
 for(Entry<String, JedisSentinelPool> iter:poolMap.entrySet()){
 iter.getValue().destroy();
 }
 }
}

 

--在這個方法中,構造函數先初始化了分片規則得到了每個主節點和160個虛擬節點的映射關係TreeMap<Long, JedisShardInfo> nodes,然後初始化了JedisSentinelPool數組,對於每個需要根據key獲取jedis的用戶,先經過hash(key),然後nodes.get(hash(key))得到對應的JedisShardInfo,然後根據JedisShardInfo對象獲取服務主節點的ip和端口號組成masterName,這樣就可以根據masterName去哨兵池JedisSentinelPool動態獲取真正可用的jedis對象。這樣一來如果某個redis服務主節點掛了,哨兵集羣會發現並且把從節點當作新的主節點。這樣就能達到高可用的效果。

其中的一致性哈希算法過程稍微解釋下:

當我們傳入一個JedisShardInfo數組進去後,就會爲每個JedisShardInfo對象分配160個虛擬節點,每個虛擬節點的可key由this.algo.hash("SHARD-" + i +"-NODE-" + n)或者this.algo.hash(shardInfo.getName() +"*" + shardInfo.getWeight() + n)計算得出,這樣就形成160個分散的虛擬key和1個JedisShardInfo的映射關係的map, JedisShardInfo數組裏每個JedisShardInfo都會得到160個這樣的映射關係(虛擬節點),並且最後都全部加入大的map(TreeMap<Long, JedisShardInfo> nodes)中。當我們需要獲取某個真實key值對應的真正JedisShardInfo(裏面包含服務節點的ip和port信息),就對該key也執行hash操作algo.hash(key),然後通過TreeMap.tailMap獲取虛擬節點的key大於或等於algo.hash(key)的第一個虛擬節點已經該虛擬節點對應的JedisShardInfo,如果algo.hash(key)大於所有虛擬節點的key ,那麼由於一致性哈希環的概念,該key將取所有虛擬節點中的第一個(也就是最小那一個)並獲取第一個虛擬節點對應的JedisShardInfo。

這裏還有個小的知識點就是TreeMap或則TreeXXXX的容器的作用一般就是通過紅黑樹的數據結構進行了排序,它們的作用就是這是一個有序的容器。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章