提取jedis原始碼的一致性hash程式碼作為通用工具類
一致性Hash熱點
一致性Hash演算法是來解決熱點問題,如果虛擬節點設定過小熱點問題仍舊存在。 關於一致性Hash演算法的原理我就不說了,網上有很多人提供自己編寫的一致性Hash演算法的程式碼示例,我在跑網上的程式碼示例發現還是有熱點問題。為此我翻閱了Jedis的ShardedJedis類的原始碼把它的一致性Hash演算法提取出來,作為自己的一個工具類,以後自己工程開發中用起來也放心些,畢竟jedis的程式碼經受了大家的驗證。
提取jedis的一致性hash程式碼作為通用工具類
看看人家碼神寫的程式碼,這泛型,這繼承,這多型用的,寫的真是好,程式碼通用性真是沒話說。 在Sharded方法中: 1 ,定義了一個TreeMap ,TreeMap 用於儲存虛擬節點(在初始化方法中,將每臺伺服器節點採用hash演算法劃分為160個(預設的,DEFAULT_WEIGHT)虛擬節點(當然也可以配置劃分權重) 2 ,定義一個LinkedHashMap,用於儲存每一個Redis伺服器的物理連線,其中shardInfo的createResource就是物理連線資訊 。 3,對於key採用與初始化時同樣的hash(MurmurHash或者MD5)演算法,然後從TreeMap獲取大於等於鍵hash值得節點,取最鄰近節點; 4,當key的hash值大於虛擬節點hash值得最大值時(也就是tail為空),取第一個虛擬節點。 相關完整的原始碼可以檢視我的github的intsmaze-hash這個model,傳送點https://github.com/intsmaze/intsmaze。
package cn.intsmaze.hash.shard; public class Sharded<R, S extends ShardInfo<R>> { public static final int DEFAULT_WEIGHT = 1; private TreeMap<Long, S> nodes; private final Hashing algo; private final Map<ShardInfo<R>, R> resources = new LinkedHashMap<ShardInfo<R>, R>(); public Sharded(List<S> shards) { this(shards, Hashing.MURMUR_HASH); // MD5 is really not good as we works // with 64-bits not 128 } public Sharded(List<S> shards, Hashing algo) { this.algo = algo; this.shards=shards; initialize(shards); } 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.getTableName() == 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.getTableName() + "*" + shardInfo.getWeight() + n), shardInfo); } resources.put(shardInfo, shardInfo.createResource());//呼叫IntsmazeShardInfo的createResource()方法 如果我們的實現不需要控制遠端的連線,那麼這個方法就不沒什麼用 } } /** * 這個是找到key對應的節點後,不是僅僅返回屬於的節點名稱而是返回對應的例項連線 * @param key * @return */ public R getShardByResources(String key) { return resources.get(getShardInfo(key)); } /** * 這個是找到key對應的節點後,返回屬於的節點名稱 * @param key * @return */ public S getShard(String key) { return 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(key)); } } package cn.intsmaze.hash.shard; public class IntsmazeShardedConnection extends Sharded<Intsmaze, IntsmazeShardInfo>{ public IntsmazeShardedConnection(List<IntsmazeShardInfo> shards) { super(shards); } public String getTable(String key) { IntsmazeShardInfo intsmazeShardInfo = getShard(key); return intsmazeShardInfo.getTableName(); } } package cn.intsmaze.hash.shard; public class IntsmazeShardInfo extends ShardInfo<Intsmaze> { private String host; private int port; public IntsmazeShardInfo(String host, String tableName) { super(Sharded.DEFAULT_WEIGHT, tableName); URI uri = URI.create(host); this.host = uri.getHost(); this.port = uri.getPort(); } @Override public Intsmaze createResource() { return new Intsmaze(this); } } package cn.intsmaze.hash.shard; public abstract class ShardInfo<T> { private int weight; private String tableName; public ShardInfo() { } public ShardInfo(int weight,String tableName) { this.weight = weight; this.tableName=tableName; } protected abstract T createResource(); } package cn.intsmaze.hash.shard; public class Test { private static IntsmazeShardedConnection sharding; public static void setUpBeforeClass() throws Exception { List<IntsmazeShardInfo> shards = Arrays.asList( new IntsmazeShardInfo("localhost:6379", "intsmaze-A"), new IntsmazeShardInfo("localhost::6379", "intsmaze-B"), new IntsmazeShardInfo("localhost::6379", "intsmaze-C"), new IntsmazeShardInfo("localhost::6379", "intsmaze-D"), new IntsmazeShardInfo("localhost::6379", "intsmaze-E")); sharding = new IntsmazeShardedConnection(shards); } public void shardNormal() { Map<String,Long> map=new HashMap<String,Long>(); for (int i = 0; i < 10000000; i++) { String result = sharding.getTable("sn" + i); Long num=map.get(result); if(num==null) { map.put(result,1L); } else { num=num+1; map.put(result,num); } } Set<Map.Entry<String, Long>> entries = map.entrySet(); Iterator<Map.Entry<String, Long>> iterator = entries.iterator(); while(iterator.hasNext()) { Map.Entry<String, Long> next = iterator.next(); System.out.println(next.getKey()+"--->>>"+next.getValue()); } } public static void main(String[] args) throws Exception { Test t=new Test(); t.setUpBeforeClass(); t.shardNormal(); } }
沒有熱點問題
把jedis的原始碼提取出來後,跑了一下,發現沒有熱點問題,原理不是採用演算法的問題,而是一個物理節點對應的虛擬節點的數量的問題導致使用hash演算法後,還是有熱點問題。jedis原始碼物理節點對應虛擬節點時160,而網上大部分程式碼都是10以下,所以導致了熱點問題,這也告訴我們,實現一致性Hash演算法時,不要太吝嗇,虛擬節點設定的大點,熱點問題就不會再有。
相關完整的原始碼可以檢視我的github的intsmaze-hash這個model,傳送點https://github.com/intsmaze/intsmaze。