ava實(shí)現(xiàn)一致性Hash算法
1. 實(shí)現(xiàn)原理
將key映射到 2^32 - 1 的空間中,將這個數(shù)字的首尾相連,形成一個環(huán)
- 計算節(jié)點(diǎn)(使用節(jié)點(diǎn)名稱、編號、IP地址)的hash值,放置在環(huán)上
- 計算key的hash值,放置在環(huán)上,順時針尋找到的第一個節(jié)點(diǎn),就是應(yīng)選取的節(jié)點(diǎn)

例如:p2、p4、p6三個節(jié)點(diǎn),key11、key2、key27按照順序映射到p2、p4、p6上面,假設(shè)新增一個節(jié)點(diǎn)p8在p6節(jié)點(diǎn)之后,這個時候只需要將key27從p6調(diào)整到p8就可以了;也就是說,每次新增刪除節(jié)點(diǎn)時,只需要重新定位該節(jié)點(diǎn)附近的一小部分?jǐn)?shù)據(jù)
2. 解決數(shù)據(jù)傾斜的問題
什么是數(shù)據(jù)傾斜?
如果服務(wù)器的節(jié)點(diǎn)過少,容易引起key的傾斜。例如上面的例子中p2、p4、p6分布在環(huán)的上半部分,下半部分是空的。那么映射到下半部分的key都會被分配給p2,key過度傾斜到了p2緩存間節(jié)點(diǎn)負(fù)載不均衡。
解決
為了解決這個問題,引入了虛擬節(jié)點(diǎn)的概念,一個真實(shí)的節(jié)點(diǎn)對應(yīng)多個虛擬的節(jié)點(diǎn)
假設(shè)1個真實(shí)的節(jié)點(diǎn)對應(yīng)3個虛擬節(jié)點(diǎn),那么p1對應(yīng)的就是p1-1、p1-2、p1-3
- 計算虛擬節(jié)點(diǎn)的Hash值,放置在環(huán)上
- 計算key的Hash值,在環(huán)上順時針尋找到對應(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ù)傾斜的問題,而且代價非常小,只需要新增一個字典(Map)維護(hù)真實(shí)的節(jié)點(diǎn)與虛擬節(jié)點(diǎn)的映射關(guān)系就可以了
3. 代碼實(shí)現(xiàn)
3.1 ConsistentHash
這里使用了泛型的方式來保存數(shù)據(jù),可以根據(jù)不同的類型,獲取到不同的節(jié)點(diǎn)存儲
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)個數(shù)來計算虛擬節(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)槭且粋€環(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()));
}
}
3.2 Hash
這里定義了一個函數(shù)結(jié)構(gòu),用于自定計算hash值
@FunctionalInterface
public static interface Hash<T> {
/**
* 計算hash值
*
* @param t
* @return int類型
*/
int hash(T t);
}
3.3 Utils
由于hashcode采用的int類型進(jìn)行存儲,那么就需要考慮,hash是否超過了int最大存儲,如果超過了那么存儲的數(shù)字就是負(fù)數(shù),會對獲取節(jié)點(diǎn)造成影響,所以這里在取hash值時,采用了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能夠平均的計算在 int類型之間
*
* @param o
* @return
*/
public static int rehash(Object o) {
int h = o.hashCode();
return (h ^ (h >>> 16)) & HASH_BITS;
}
}
3.4 main
下面是main方法進(jìn)行測試,在后面新增了一個節(jié)點(diǎn)之后,只會調(diào)整 zs 數(shù)據(jù)到 109 節(jié)點(diǎn),而且其他兩個key的獲取不會受到影響
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)獲取錯誤,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)獲取錯誤,key:" + k + ",獲取到的節(jié)點(diǎn)值為:" + node);
}
});
}
到此這篇關(guān)于ava實(shí)現(xiàn)一致性Hash算法的文章就介紹到這了,更多相關(guān)Java hash算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot3和ShardingSphere5框架實(shí)現(xiàn)數(shù)據(jù)分庫分表
這篇文章主要介紹了SpringBoot3和ShardingSphere5框架實(shí)現(xiàn)數(shù)據(jù)分庫分表的相關(guān)資料,需要的朋友可以參考下2023-08-08
SpringBoot整合之SpringBoot整合MongoDB的詳細(xì)步驟
這篇文章主要介紹了SpringBoot整合之SpringBoot整合MongoDB的詳細(xì)步驟,本文通過圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-07-07
解決Java變異出現(xiàn)錯誤No enclosing instance of type XXX is accessible
這牌你文章主要給大家分享解決Java變異出現(xiàn)錯誤,具體的饑餓絕方案請看下面文章的內(nèi)容,需要的朋友可以參考一下,希望能幫助到你2021-09-09

