您好,登錄后才能下訂單哦!
這篇文章主要介紹了ava如何實(shí)現(xiàn)一致性Hash算法的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇ava如何實(shí)現(xiàn)一致性Hash算法文章都會(huì)有所收獲,下面我們一起來看看吧。
將key映射到 2^32 - 1 的空間中,將這個(gè)數(shù)字的首尾相連,形成一個(gè)環(huán)
計(jì)算節(jié)點(diǎn)(使用節(jié)點(diǎn)名稱、編號、IP地址)的hash值,放置在環(huán)上
計(jì)算key的hash值,放置在環(huán)上,順時(shí)針尋找到的第一個(gè)節(jié)點(diǎn),就是應(yīng)選取的節(jié)點(diǎn)
例如:p2、p4、p6三個(gè)節(jié)點(diǎn),key11、key2、key27按照順序映射到p2、p4、p6上面,假設(shè)新增一個(gè)節(jié)點(diǎn)p8在p6節(jié)點(diǎn)之后,這個(gè)時(shí)候只需要將key27從p6調(diào)整到p8就可以了;也就是說,每次新增刪除節(jié)點(diǎn)時(shí),只需要重新定位該節(jié)點(diǎn)附近的一小部分?jǐn)?shù)據(jù)
如果服務(wù)器的節(jié)點(diǎn)過少,容易引起key的傾斜。例如上面的例子中p2、p4、p6分布在環(huán)的上半部分,下半部分是空的。那么映射到下半部分的key都會(huì)被分配給p2,key過度傾斜到了p2緩存間節(jié)點(diǎn)負(fù)載不均衡。
為了解決這個(gè)問題,引入了虛擬節(jié)點(diǎn)的概念,一個(gè)真實(shí)的節(jié)點(diǎn)對應(yīng)多個(gè)虛擬的節(jié)點(diǎn)
假設(shè)1個(gè)真實(shí)的節(jié)點(diǎn)對應(yīng)3個(gè)虛擬節(jié)點(diǎn),那么p1對應(yīng)的就是p1-1、p1-2、p1-3
計(jì)算虛擬節(jié)點(diǎn)的Hash值,放置在環(huán)上
計(jì)算key的Hash值,在環(huán)上順時(shí)針尋找到對應(yīng)選取的虛擬節(jié)點(diǎn),例如:p2-1,對應(yīng)真實(shí)的節(jié)點(diǎn)p2
虛擬節(jié)點(diǎn)擴(kuò)充了節(jié)點(diǎn)的數(shù)量,解決了節(jié)點(diǎn)較少的情況下數(shù)據(jù)傾斜的問題,而且代價(jià)非常小,只需要新增一個(gè)字典(Map)維護(hù)真實(shí)的節(jié)點(diǎn)與虛擬節(jié)點(diǎn)的映射關(guān)系就可以了
這里使用了泛型的方式來保存數(shù)據(jù),可以根據(jù)不同的類型,獲取到不同的節(jié)點(diǎn)存儲(chǔ)
public class ConsistentHash<T> { //自定義hash方法 private Hash<Object> hashMethod; //創(chuàng)建hash映射,虛擬節(jié)點(diǎn)映射真實(shí)節(jié)點(diǎn) private final Map<Integer, T> hashMap = new ConcurrentHashMap<>(); //將所有的hash保存起來 private List<Integer> keys = new ArrayList<>(); //默認(rèn)虛擬節(jié)點(diǎn)數(shù)量 private final int replicas; public ConsistentHash() { this(3, Utils::rehash); } public ConsistentHash(int replicas, Hash<Object> hashMethod) { this.replicas = replicas; this.hashMethod = hashMethod; } @SafeVarargs public final void add(T... keys) { for (T key : keys) { //根據(jù)虛擬節(jié)點(diǎn)個(gè)數(shù)來計(jì)算虛擬節(jié)點(diǎn) for (int i = 0; i < this.replicas; i++) { //根據(jù)函數(shù)獲取到對應(yīng)的hash值 int hash = this.hashMethod.hash(i + ":" + key.toString()); this.keys.add(hash); this.hashMap.put(hash, key); } } //排序,因?yàn)槭且粋€(gè)環(huán)狀結(jié)構(gòu) Collections.sort(this.keys); } /** * 根據(jù)對應(yīng)的key來獲取到節(jié)點(diǎn)信息 * * @param key * @return */ public T get(Object key) { Objects.requireNonNull(key, "key不能為空"); int hash = this.hashMethod.hash(key); //獲取到對應(yīng)的節(jié)點(diǎn)信息 int idx = Utils.search(this.keys.size(), h -> this.keys.get(h) >= hash); //如果idx == this.keys.size() ,就代表需要取 this.keys.get(0); 因?yàn)槭黔h(huán)狀,所以需要使用 % 來進(jìn)行處理 return this.hashMap.get(this.keys.get(idx % this.keys.size())); } }
這里定義了一個(gè)函數(shù)結(jié)構(gòu),用于自定計(jì)算hash值
@FunctionalInterface public static interface Hash<T> { /** * 計(jì)算hash值 * * @param t * @return int類型 */ int hash(T t); }
由于hashcode采用的int類型進(jìn)行存儲(chǔ),那么就需要考慮,hash是否超過了int最大存儲(chǔ),如果超過了那么存儲(chǔ)的數(shù)字就是負(fù)數(shù),會(huì)對獲取節(jié)點(diǎn)造成影響,所以這里在取hash值時(shí),采用了hashmap中獲取到hashcode之后對其進(jìn)行與操作,可以減少hash沖突,也可以避免負(fù)數(shù)的產(chǎn)生
public static class Utils { // int類型的最大數(shù)據(jù) static final int HASH_BITS = 0x7fffffff; /** * 通過二分查找法,定義數(shù)組索引位置 * * @param len * @param f * @return */ public static int search(int len, Function<Integer, Boolean> f) { int i = 0, j = len; //通過二分查找發(fā)來定為索引位置 while (i < j) { //長度除于2 int h = (i + j) >> 1; //調(diào)用函數(shù),判斷當(dāng)前的索引值是否大于 if (f.apply(h)) { //向低半段進(jìn)行遍歷 j = h; } else { //向高半段進(jìn)行遍歷 i = h + 1; } } return i; } /** * 將返回的hash能夠平均的計(jì)算在 int類型之間 * * @param o * @return */ public static int rehash(Object o) { int h = o.hashCode(); return (h ^ (h >>> 16)) & HASH_BITS; } }
下面是main方法進(jìn)行測試,在后面新增了一個(gè)節(jié)點(diǎn)之后,只會(huì)調(diào)整 zs 數(shù)據(jù)到 109 節(jié)點(diǎn),而且其他兩個(gè)key的獲取不會(huì)受到影響
public static void main(String[] args) { ConsistentHash<String> consistentHash = new ConsistentHash<>(); consistentHash.add("192.168.2.106", "192.168.2.107", "192.168.2.108"); Map<String, Object> map = new HashMap<>(); map.put("zs", "192.168.2.108"); map.put("999999", "192.168.2.106"); map.put("233333", "192.168.2.106"); map.forEach((k, v) -> { String node = consistentHash.get(k); if (!v.equals(node)) { throw new IllegalArgumentException("節(jié)點(diǎn)獲取錯(cuò)誤,key:" + k + ",獲取到的節(jié)點(diǎn)值為:" + node); } }); consistentHash.add("192.168.2.109"); map.put("zs", "192.168.2.109"); map.forEach((k, v) -> { String node = consistentHash.get(k); if (!v.equals(node)) { throw new IllegalArgumentException("節(jié)點(diǎn)獲取錯(cuò)誤,key:" + k + ",獲取到的節(jié)點(diǎn)值為:" + node); } }); }
關(guān)于“ava如何實(shí)現(xiàn)一致性Hash算法”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“ava如何實(shí)現(xiàn)一致性Hash算法”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。