Jedis下的ShardedJedis(分布式)使用方法(二),jedisshardedjedis
上一篇中介紹了ShardedJedis的基本使用方法以及示範了一個簡單的例子,在這一篇中我們來介紹了ShardedJedis的原理。
1.ShardedJedis內部實現
首先我們來看一下ShardedJedis的一個繼承關係
看完了圖,那麼我們一步一步跟著我們的代碼調用來看,以我們最簡單的 ShardedJedis.get(key)方法為例:
public String get(String key) { Jedis j = getShard(key); return j.get(key); }
這邊有調用一個getShard 方法,參數為我們傳入的key,然後返回一個普通的jedis對象,那麼這個getShard是用來做什麼的呢,大家可能已經猜到了,這個方法就是會根據我們傳入的key做一致性雜湊判斷,然後返回key落到的那個redis執行個體上的一個redis串連,不同的key返回的redis串連可能是不同的。
進入getShard 方法,你會發現這個實現是在Sharded類中實現的(看上面的類圖可以發現頂層的Sharded類),代碼如下:
public R getShard(String key) { return resources.get(getShardInfo(key)); } public S getShardInfo(byte[] key) { SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key)); if (tail.isEmpty()) { return nodes.get(nodes.firstKey()); } return tail.get(tail.firstKey()); } public S getShardInfo(String key) { return getShardInfo(SafeEncoder.encode(getKeyTag(key))); }
上面的方法是層層調用的關係,在這邊不細說,我們主要看下第二個方法(getShardInfo(byte[] key))實現(上面的nodes變數是一個TreeMap 類型, algo 是Hashing類型,即key分區所使用的hash演算法,這個在前一篇有簡單說過),那麼這段代碼的含義我們大概成猜出來了,
那麼這個S、 R對象各自代表什麼呢?看下面的代碼
public class Sharded<R, S extends ShardInfo<R>>
public class BinaryShardedJedis extends Sharded<Jedis, JedisShardInfo> implements BinaryJedisCommands
可以得出 S = JedisShardInfo, R = Jedis 對象,即在TreeMap儲存了伺服器劃分的虛擬節點的資訊,LinkedHashMap中儲存了伺服器的物理串連。
JedisShardInfo具體資訊如下:裡麵包含了jedis伺服器的一些資訊,最重要的是它的父類中有一個weight欄位,作為本jedis伺服器的權值。
ShardedJedis去查一個key時,首先它會把這個key進行一個hash演算法
根據這個hash值然後去treeMap中,查出這個key落在哪個執行個體中,並返回redis執行個體對應的具體資訊
根據這個redis的執行個體資訊,到一個儲存jedis連結和執行個體資訊對應關係的LinkedHashMap中找到這個jedis串連
最終返回jedis串連,執行對象的命令操作(到這步後實際上和單機操作一樣了)
那麼我們的nodes 伺服器虛擬節點和resources 伺服器物理串連是什麼時候初始化的呢,接下來繼續看
2.redis虛擬節點(nodes)和物理串連(resources) 初始化
我們繼續看Sharded的構造方法
public Sharded(List<S> shards, Hashing algo) { this.algo = algo; initialize(shards); }public Sharded(List<S> shards, Hashing algo, Pattern tagPattern) { this.algo = algo; this.tagPattern = tagPattern; initialize(shards); }
這邊有一個initialize方法,就是用來對虛擬節點和物理串連進行初始化的,看其實現
private void initialize(List<S> shards) { nodes = new TreeMap<Long, S>(); for (int i = 0; i != shards.size(); ++i) { final S 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); } resources.put(shardInfo, shardInfo.createResource()); } }
具體細節就不說了,根據上面的,我們就知道是在這邊進行了初始化,將每台伺服器節點採用hash演算法劃分為160個虛擬節點(可以配置劃分權重),儲存在TreeMap中,
然後把每台伺服器節點的資訊和物理串連以索引值對儲存LinkedHashMap中。
然後通過一系列的尋找,發現Sharded的構造方法其實是在我們 jedisPool.getResource() 時就完成的
//初始化ShardedJedisPool List<JedisShardInfo> infoList = Arrays.asList(shardInfo1, shardInfo2, shardInfo3); ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig, infoList); ShardedJedis jedis = jedisPool.getResource();
納尼? 每次jedisPool.getResource() 才初始化?那會不會造成很慢呢,其實不用擔心,其底層是使用了commons.pool來進行串連池的一些操作,會根據我們配置的串連池參數來產生對應的串連並儲存,其中的細節很複雜,博主沒有繼續深究,實現其實和資料庫連接池是一致的。
3.總結
ShardedJedis分布式具體的的實現思路:
redis伺服器節點劃分:將每台伺服器節點採用hash演算法劃分為160個虛擬節點(可以配置劃分權重)
將劃分虛擬節點採用TreeMap儲存
對每個redis伺服器的物理串連採用LinkedHashMap儲存
對Key or KeyTag 採用同樣的hash演算法,然後從TreeMap擷取大於等於鍵hash值得節點,取最鄰近節點儲存;當key的hash值大於虛擬節點hash值得最大值時,存入第一個虛擬節點
sharded採用的hash演算法:MD5 和 MurmurHash兩種;預設採用64位的MurmurHash演算法;
好了,大概的實現如上面所說的,都是圖可能會有點亂,大家如果有什麼問題或者發現了什麼錯誤,please tell me!