我們到底能走多遠系列(32)
扯淡:
工作是容易的賺錢是困難的
戀愛是容易的成家是困難的
相愛是容易的相處是困難的
決定是容易的可是等待是困難的
主題:
1,Sharded的實現
Memcached 和 redis 都使用了該算法來實現自己的多服務器均勻分派存儲值的。
shardedJedisPool的配置如下:(具體可以參考《spring和redis的整合》)
<bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool" scope="singleton"> <constructor-arg index="0" ref="jedisPoolConfig" /> <constructor-arg index="1"> <list> <bean class="redis.clients.jedis.JedisShardInfo"> <constructor-arg name="host" value="${redis.host}" /> <constructor-arg name="port" value="${redis.port}" /> <constructor-arg name="timeout" value="${redis.timeout}" /> <constructor-arg name="weight" value="1" /> </bean> </list> </constructor-arg> </bean>
注入了兩個對象:jedisPoolConfig 和 JedisShardInfo
然后產生ShardedJedis:
public ShardedJedis getRedisClient() { try { ShardedJedis shardJedis = shardedJedisPool.getResource(); return shardJedis; } catch (Exception e) { log.error("getRedisClent error", e); } return null; }
ShardedJedis 繼承 BinaryShardedJedis 繼承 Sharded<Jedis, JedisShardInfo>
而Sharded的實現就是前面一致性哈希算法的實現啦~
// 使用TreeMap來完成構造出一個很多節點的環形 private TreeMap<Long, S> nodes; // 構造方法 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 // 將設置的權重放大160倍,產生更多的節點,因為hash一下就散落到各道各處了,如此就是所謂的虛擬節點,以保證均勻分布 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()); } }
redis放key value的時候,需要判斷應該放在那個服務器上,就是判斷hash后更靠近哪個節點。
public R getShard(byte[] key) { return resources.get(getShardInfo(key)); } public R getShard(String key) { return resources.get(getShardInfo(key)); } //最終調用方法 public S getShardInfo(byte[] key) { // 首先判斷是不是tree中最大的key,及最后一個,注意我們是環,所以最大的后面就要從頭開始。 SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key)); // 是最后一個key了,所以取第一個節點對應的服務器 if (tail.size() == 0) { return nodes.get(nodes.firstKey()); } // 不是最后一個就是比自己離自己最近的大的key對應的服務器 return tail.get(tail.firstKey()); } public S getShardInfo(String key) { return getShardInfo(SafeEncoder.encode(getKeyTag(key))); }
到這里基本明白了如何抽象實現一個環狀的排序的數據結構了。值得借鑒。
2,實踐中的一個例子
TreeMap<Integer, AwardConfigDO> extentTree = new TreeMap<Integer, AwardConfigDO>(); // 獲獎區間划分 for (AwardConfigDO awardConfig : configList) { //Probability是區間節點,如100,500 extentTree.put(awardConfig.getProbability(), awardConfig); } // 進入中獎區 random 是隨機產生的數字,首先判斷是否進入中獎區 if (random < extentTree.lastKey()) { //然后判斷 中獎獎項 是哪個 AwardConfigDO awardConfig = extentTree.higherEntry(random).getValue(); }
所以TreeMap可以來抽象實現這種區間的結構。關於TreeMap可以看API哦。
--------------------20130827補充-----------------------
需要注意的是 使用了TreeMap 需要考慮key相同的情況,這種情況就需要接受前一個映射關系會被替換的情況。
public static void main(String[] args) { TreeMap<Integer, String> extentTree = new TreeMap<Integer, String>(); extentTree.put(1, "1"); extentTree.put(10, "2"); extentTree.put(10, "3"); extentTree.put(100, "4"); String value = extentTree.higherEntry(5).getValue(); System.out.println(value);//output:3 }
讓我們繼續前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不會成功。
共勉。