前言
在前文分散式理論(八)—— Consistent Hash(一致性雜湊演算法)中,我們討論了一致性 hash 演算法的原理,並說了,我們會自己寫一個簡單的演算法。今天就來寫一個。
普通 hash 的結果
先看看普通 hash 怎麼做。
首先,需要快取節點物件,快取中的儲存物件,還有一個快取節點集合,用於儲存有效的快取節點。
- 實際儲存物件,很簡單的一個類,只需要獲取他的 hash 值就好:
static class Obj {
String key;
Obj(String key) {
this.key = key;
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
public String toString() {
return "Obj{" +
"key='" + key + '\'' +
'}';
}
}
複製程式碼
- 快取節點物件,用於儲存實際物件:
static class Node {
Map<Integer, Obj> node = new HashMap<>();
String name;
Node(String name) {
this.name = name;
}
public void putObj(Obj obj) {
node.put(obj.hashCode(), obj);
}
Obj getObj(Obj obj) {
return node.get(obj.hashCode());
}
@Override
public int hashCode() {
return name.hashCode();
}
}
複製程式碼
也很簡單,內部使用了一個 map 儲存節點。
- 快取節點集合,用於儲存有效的快取節點:
static class NodeArray {
Node[] nodes = new Node[1024];
int size = 0;
public void addNode(Node node) {
nodes[size++] = node;
}
Obj get(Obj obj) {
int index = obj.hashCode() % size;
return nodes[index].getObj(obj);
}
void put(Obj obj) {
int index = obj.hashCode() % size;
nodes[index].putObj(obj);
}
}
複製程式碼
內部一個陣列,取資料時,通過取餘機器數量獲取快取節點,再從節點中取出資料。
- 測試:當增減節點時,還能不能找到原有資料:
/**
* 驗證普通 hash 對於增減節點,原有會不會出現移動。
*/
public static void main(String[] args) {
NodeArray nodeArray = new NodeArray();
Node[] nodes = {
new Node("Node--> 1"),
new Node("Node--> 2"),
new Node("Node--> 3")
};
for (Node node : nodes) {
nodeArray.addNode(node);
}
Obj[] objs = {
new Obj("1"),
new Obj("2"),
new Obj("3"),
new Obj("4"),
new Obj("5")
};
for (Obj obj : objs) {
nodeArray.put(obj);
}
validate(nodeArray, objs);
}
複製程式碼
private static void validate(NodeArray nodeArray, Obj[] objs) {
for (Obj obj : objs) {
System.out.println(nodeArray.get(obj));
}
nodeArray.addNode(new Node("anything1"));
nodeArray.addNode(new Node("anything2"));
System.out.println("========== after =============");
for (Obj obj : objs) {
System.out.println(nodeArray.get(obj));
}
}
複製程式碼
測試步驟如下:
- 向集合中新增 3 個節點。
- 向
叢集
中新增 5 個物件,這 5 個物件會根據 hash 值雜湊到不同的節點中。 - 列印
未增減前
的資料。 - 列印
增加 2 個節點
後資料,看看還能不能訪問到資料。
結果:
一個都訪問不到了。這就是普通的取餘的缺點,在增減機器的情況下,這種結果無法接收。
再看看一致性 hash 如何解決。
一致性 Hash 的結果
關鍵的地方來了。
快取節點物件和實際儲存物件不用更改,改的是什麼?
改的是儲存物件的方式和取出物件的方式,也就是不使用對機器進行取餘的演算法。
新的 NodeArray 物件如下:
static class NodeArray {
/** 按照 鍵 排序*/
TreeMap<Integer, Node> nodes = new TreeMap<>();
void addNode(Node node) {
nodes.put(node.hashCode(), node);
}
void put(Obj obj) {
int objHashcode = obj.hashCode();
Node node = nodes.get(objHashcode);
if (node != null) {
node.putObj(obj);
return;
}
// 找到比給定 key 大的集合
SortedMap<Integer, Node> tailMap = nodes.tailMap(objHashcode);
// 找到最小的節點
int nodeHashcode = tailMap.isEmpty() ? nodes.firstKey() : tailMap.firstKey();
nodes.get(nodeHashcode).putObj(obj);
}
Obj get(Obj obj) {
Node node = nodes.get(obj.hashCode());
if (node != null) {
return node.getObj(obj);
}
// 找到比給定 key 大的集合
SortedMap<Integer, Node> tailMap = nodes.tailMap(obj.hashCode());
// 找到最小的節點
int nodeHashcode = tailMap.isEmpty() ? nodes.firstKey() : tailMap.firstKey();
return nodes.get(nodeHashcode).getObj(obj);
}
}
複製程式碼
該類和之前的類的不同之處在於:
- 內部沒有使用陣列,而是使用了有序 Map。
- put 方法中,物件如果沒有落到快取節點上,就找比他小的節點且離他最近的。這裡我們使用了 TreeMap 的 tailMap 方法,具體 API 可以看文件。
- get 方法中,和 put 步驟相同,否則是取不到物件的。
具體尋找節點的方式如圖:
相同的測試用例,執行結果如下:
找到了之前所有的節點。解決了普通 hash 的問題。
總結
程式碼比較簡單,主要是通過 JDK 自帶的 TreeMap 實現的尋找臨近節點。當然,我們這裡也只是測試了新增,關於修改還沒有測試,但思路是一樣的。這裡只是做一個拋磚引玉。
同時,我們也沒有實現虛擬節點,感興趣的朋友可以嘗試一下。
good luck!!!!