Jedis下的ShardedJedis(分布式)使用方法(二)


上一篇中介紹了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算法,這個在前一篇有簡單說過),那么這段代碼的含義我們大概成猜出來了,

  1. 就是在一個TreeMap中取出大於等於key之后的部分視圖SortMap
  2. 在SortMap取得第一個鍵值對的值,然后返回一個 S 對象,
  3. 然后根據這個S 對象,去resources(resources = new LinkedHashMap<ShardInfo<R>, R>())中get一個R對象

 

那么這個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服務器的權值。

 

ok,那我們了解了實際上就是根據jedis服務器的信息去獲取一個jedis的連接,返回給上層調用。

 

我們可以梳理下這個邏輯:

  1. 當我們使用ShardedJedis去查一個key時,首先它會把這個key進行一個hash算法
  2. 根據這個hash值然后去treeMap中,查出這個key落在哪個實例中,並返回redis實例對應的具體信息
  3. 根據這個redis的實例信息,到一個保存jedis鏈接和實例信息對應關系的LinkedHashMap中找到這個jedis連接
  4. 最終返回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!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM