上一篇中介紹了
首先我們來看一下
看完了圖,那么我們一步一步跟著我們的代碼調用來看,以我們最簡單的%20ShardedJedis.get(key)方法為例:
%20%20public%20String%20get(String%20key)%20{%20%20%20%20Jedis%20j%20=%20getShard(key);%20%20%20%20return%20j.get(key);%20%20}
這邊有調用一個getShard%20方法,參數為我們傳入的key,然后返回一個普通的jedis對象,那么這個getShard是用來做什么的呢,大家可能已經猜到了,這個方法就是會根據我們傳入的key做一致性哈希判斷,然后返回key落到的那個redis實例上的一個redis連接,不同的key返回的redis連接可能是不同的。
進入getShard%20方法,你會發現這個實現是在Sharded類中實現的(看上面的類圖可以發現頂層的Sharded類),代碼如下:
public%20R%20getShard(String%20key)%20{%20%20%20%20return%20resources.get(getShardInfo(key));%20%20}%20%20public%20S%20getShardInfo(byte[]%20key)%20{%20%20%20%20SortedMap<Long,%20S>%20tail%20=%20nodes.tailMap(algo.hash(key));%20%20%20%20if%20(tail.isEmpty())%20{%20%20%20%20%20%20return%20nodes.get(nodes.firstKey());%20%20%20%20}%20%20%20%20return%20tail.get(tail.firstKey());%20%20}%20%20public%20S%20getShardInfo(String%20key)%20{%20%20%20%20return%20getShardInfo(SafeEncoder.encode(getKeyTag(key)));%20%20}
上面的方法是層層調用的關系,在這邊不細說,我們主要看下第二個方法(getShardInfo(byte[]%20key))實現(上面的nodes變量是一個TreeMap%20類型,%20algo%20是Hashing類型,即key分片所使用的hash算法,這個在前一篇有簡單說過),那么這段代碼的含義我們大概成猜出來了,
那么這個S、%20R對象各自代表什么呢?看下面的代碼
public%20class%20Sharded<R,%20S%20extends%20ShardInfo<R>>
public%20class%20BinaryShardedJedis%20extends%20Sharded<Jedis,%20JedisShardInfo>%20implements%20%20%20%20BinaryJedisCommands
可以得出 %20S%20=%20JedisShardInfo,%20R%20=%20Jedis%20對象,即在TreeMap存儲了服務器劃分的虛擬節點的信息,LinkedHashMap中存儲了服務器的物理連接。
JedisShardInfo具體信息如下:里面包含了jedis服務器的一些信息,最重要的是它的父類中有一個weight字段,作為本jedis服務器的權值。
ok,那我們了解了實際上就是根據jedis服務器的信息去獲取一個jedis的連接,返回給上層調用。
我們可以梳理下這個邏輯:
那么我們的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來進行連接池的一些操作,會根據我們配置的連接池參數來生成對應的連接并保存,其中的細節很復雜,博主沒有繼續深究,實現其實和數據庫連接池是一致的。
redis服務器節點劃分:將每臺服務器節點采用hash算法劃分為160個虛擬節點(可以配置劃分權重)
將劃分虛擬節點采用TreeMap存儲
對每個redis服務器的物理連接采用LinkedHashMap存儲
對Key or KeyTag 采用同樣的hash算法,然后從TreeMap獲取大于等于鍵hash值得節點,取最鄰近節點存儲;當key的hash值大于虛擬節點hash值得最大值時,存入第一個虛擬節點
sharded采用的hash算法:md5 和 MurmurHash兩種;默認采用64位的MurmurHash算法;
好了,大概的實現如上面所說的,都是圖可能會有點亂,大家如果有什么問題或者發現了什么錯誤,please tell me!
新聞熱點
疑難解答