日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

一致性哈希算法的原理与实现

發布時間:2024/3/12 69 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一致性哈希算法的原理与实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

分布式系統中對象與節點的映射關系,傳統方案是使用對象的哈希值,對節點個數取模,再映射到相應編號的節點,這種方案在節點個數變動時,絕大多數對象的映射關系會失效而需要遷移;而一致性哈希算法中,當節點個數變動時,映射關系失效的對象非常少,遷移成本也非常小。本文總結了一致性哈希的算法原理和Java實現,并列舉了其應用。

作者:王克鋒
出處:https://kefeng.wang/2018/08/10/consistent-hashing/
版權:自由轉載-非商用-非衍生-保持署名,轉載請標明作者和出處。

1 概述

1.1 傳統哈希(硬哈希)

分布式系統中,假設有 n 個節點,傳統方案使用 mod(key, n) 映射數據和節點。
當擴容或縮容時(哪怕只是增減1個節點),映射關系變為 mod(key, n+1) / mod(key, n-1),絕大多數數據的映射關系都會失效。

1.2 一致性哈希(Consistent Hashing)

1997年,麻省理工學院(MIT)的 David Karger 等6個人發布學術論文《Consistent hashing and random trees: distributed caching protocols for relieving hot spots on the World Wide Web(一致性哈希和隨機樹:用于緩解萬維網上熱點的分布式緩存協議)》,對于 K 個關鍵字和 n 個槽位(分布式系統中的節點)的哈希表,增減槽位后,平均只需對 K/n 個關鍵字重新映射。

1.3 哈希指標

評估一個哈希算法的優劣,有如下指標,而一致性哈希全部滿足:

  • 均衡性(Balance):將關鍵字的哈希地址均勻地分布在地址空間中,使地址空間得到充分利用,這是設計哈希的一個基本特性。
  • 單調性(Monotonicity): 單調性是指當地址空間增大時,通過哈希函數所得到的關鍵字的哈希地址也能映射的新的地址空間,而不是僅限于原先的地址空間。或等地址空間減少時,也是只能映射到有效的地址空間中。簡單的哈希函數往往不能滿足此性質。
  • 分散性(Spread): 哈希經常用在分布式環境中,終端用戶通過哈希函數將自己的內容存到不同的緩沖區。此時,終端有可能看不到所有的緩沖,而是只能看到其中的一部分。當終端希望通過哈希過程將內容映射到緩沖上時,由于不同終端所見的緩沖范圍有可能不同,從而導致哈希的結果不一致,最終的結果是相同的內容被不同的終端映射到不同的緩沖區中。這種情況顯然是應該避免的,因為它導致相同內容被存儲到不同緩沖中去,降低了系統存儲的效率。分散性的定義就是上述情況發生的嚴重程度。好的哈希算法應能夠盡量避免不一致的情況發生,也就是盡量降低分散性。
  • 負載(Load): 負載問題實際上是從另一個角度看待分散性問題。既然不同的終端可能將相同的內容映射到不同的緩沖區中,那么對于一個特定的緩沖區而言,也可能被不同的用戶映射為不同的內容。與分散性一樣,這種情況也是應當避免的,因此好的哈希算法應能夠盡量降低緩沖的負荷。

1.4 資料鏈接

原始論文《Consistent Hashing and Random Trees》鏈接如下:

  • 官方鏈接 - PDF 版本
  • 本站副本 - PDF 版本

相關論文《Web Caching with Consistent Hashing》鏈接如下:

  • 官方鏈接 - PDF 版本
  • 官方鏈接 - HTM 版本
  • 本站副本 - PDF 版本

更多資料:
WikiPedia - Consistent hashing
codeproject - Consistent hashing

2 算法原理

2.1 映射方案

2.1.1 公用哈希函數和哈希環

設計哈希函數 Hash(key),要求取值范圍為 [0, 2^32)
各哈希值在上圖 Hash 環上的分布:時鐘12點位置為0,按順時針方向遞增,臨近12點的左側位置為2^32-1。

2.1.2 節點(Node)映射至哈希環

如圖哈希環上的綠球所示,四個節點 Node A/B/C/D,
其 IP 地址或機器名,經過同一個 Hash() 計算的結果,映射到哈希環上。

2.1.3 對象(Object)映射于哈希環

如圖哈希環上的黃球所示,四個對象 Object A/B/C/D,
其鍵值,經過同一個 Hash() 計算的結果,映射到哈希環上。

2.1.4 對象(Object)映射至節點(Node)

在對象和節點都映射至同一個哈希環之后,要確定某個對象映射至哪個節點,
只需從該對象開始,沿著哈希環順時針方向查找,找到的第一個節點,即是。
可見,Object A/B/C/D 分別映射至 Node A/B/C/D。

2.2 刪除節點

現實場景:服務器縮容時刪除節點,或者有節點宕機。如下圖,要刪除節點 Node C:
只會影響欲刪除節點(Node C)與上一個(順時針為前進方向)節點(Node B)與之間的對象,也就是 Object C,
這些對象的映射關系,按照 2.1.4 的規則,調整映射至欲刪除節點的下一個節點 Node D。
其他對象的映射關系,都無需調整。

2.3 增加節點

現實場景:服務器擴容時增加節點。比如要在 Node B/C 之間增加節點 Node X:
只會影響欲新增節點(Node X)與上一個(順時針為前進方向)節點(Node B)與之間的對象,也就是 Object C,
這些對象的映射關系,按照 2.1.4 的規則,調整映射至新增的節點 Node X。
其他對象的映射關系,都無需調整。

2.4 虛擬節點

對于前面的方案,節點數越少,越容易出現節點在哈希環上的分布不均勻,導致各節點映射的對象數量嚴重不均衡(數據傾斜);相反,節點數越多越密集,數據在哈希環上的分布就越均勻。
但實際部署的物理節點有限,我們可以用有限的物理節點,虛擬出足夠多的虛擬節點(Virtual Node),最終達到數據在哈希環上均勻分布的效果:
如下圖,實際只部署了2個節點 Node A/B,
每個節點都復制成3倍,結果看上去是部署了6個節點。
可以想象,當復制倍數為 2^32 時,就達到絕對的均勻,通常可取復制倍數為32或更高。
虛擬節點哈希值的計算方法調整為:對“節點的IP(或機器名)+虛擬節點的序號(1~N)”作哈希。

3 算法實現

一致性哈希算法有多種具體的實現,包括 Chord 算法,KAD 算法等,都比較復雜。
這里給出一個簡易實現及其演示,可以看到一致性哈希的均衡性和單調性的優勢。
單調性在本例中沒有統計數據,但根據前面原理可知,增刪節點后只有很少量的數據需要調整映射關系。

3.1 源碼

/*** @author: https://kefeng.wang* @date: 2018-08-10 11:08**/ public class ConsistentHashing {// 物理節點private Set<String> physicalNodes = new TreeSet<String>() {{add("192.168.1.101");add("192.168.1.102");add("192.168.1.103");add("192.168.1.104");}};//虛擬節點private final int VIRTUAL_COPIES = 1048576; // 物理節點至虛擬節點的復制倍數private TreeMap<Long, String> virtualNodes = new TreeMap<>(); // 哈希值 => 物理節點// 32位的 Fowler-Noll-Vo 哈希算法// https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_functionprivate static Long FNVHash(String key) {final int p = 16777619;Long hash = 2166136261L;for (int idx = 0, num = key.length(); idx < num; ++idx) {hash = (hash ^ key.charAt(idx)) * p;}hash += hash << 13;hash ^= hash >> 7;hash += hash << 3;hash ^= hash >> 17;hash += hash << 5;if (hash < 0) {hash = Math.abs(hash);}return hash;}// 根據物理節點,構建虛擬節點映射表public ConsistentHashing() {for (String nodeIp : physicalNodes) {addPhysicalNode(nodeIp);}}// 添加物理節點public void addPhysicalNode(String nodeIp) {for (int idx = 0; idx < VIRTUAL_COPIES; ++idx) {long hash = FNVHash(nodeIp + "#" + idx);virtualNodes.put(hash, nodeIp);}}// 刪除物理節點public void removePhysicalNode(String nodeIp) {for (int idx = 0; idx < VIRTUAL_COPIES; ++idx) {long hash = FNVHash(nodeIp + "#" + idx);virtualNodes.remove(hash);}}// 查找對象映射的節點public String getObjectNode(String object) {long hash = FNVHash(object);SortedMap<Long, String> tailMap = virtualNodes.tailMap(hash); // 所有大于 hash 的節點Long key = tailMap.isEmpty() ? virtualNodes.firstKey() : tailMap.firstKey();return virtualNodes.get(key);}// 統計對象與節點的映射關系public void dumpObjectNodeMap(String label, int objectMin, int objectMax) {// 統計Map<String, Integer> objectNodeMap = new TreeMap<>(); // IP => COUNTfor (int object = objectMin; object <= objectMax; ++object) {String nodeIp = getObjectNode(Integer.toString(object));Integer count = objectNodeMap.get(nodeIp);objectNodeMap.put(nodeIp, (count == null ? 0 : count + 1));}// 打印double totalCount = objectMax - objectMin + 1;System.out.println("======== " + label + " ========");for (Map.Entry<String, Integer> entry : objectNodeMap.entrySet()) {long percent = (int) (100 * entry.getValue() / totalCount);System.out.println("IP=" + entry.getKey() + ": RATE=" + percent + "%");}}public static void main(String[] args) {ConsistentHashing ch = new ConsistentHashing();// 初始情況ch.dumpObjectNodeMap("初始情況", 0, 65536);// 刪除物理節點ch.removePhysicalNode("192.168.1.103");ch.dumpObjectNodeMap("刪除物理節點", 0, 65536);// 添加物理節點ch.addPhysicalNode("192.168.1.108");ch.dumpObjectNodeMap("添加物理節點", 0, 65536);} }

3.2 復制倍數為 1 時的均衡性

修改代碼中 VIRTUAL_COPIES = 1(相當于沒有虛擬節點),運行結果如下(可見各節點負荷很不均衡):

======== 初始情況 ======== IP=192.168.1.101: RATE=45% IP=192.168.1.102: RATE=3% IP=192.168.1.103: RATE=28% IP=192.168.1.104: RATE=22% ======== 刪除物理節點 ======== IP=192.168.1.101: RATE=45% IP=192.168.1.102: RATE=3% IP=192.168.1.104: RATE=51% ======== 添加物理節點 ======== IP=192.168.1.101: RATE=45% IP=192.168.1.102: RATE=3% IP=192.168.1.104: RATE=32% IP=192.168.1.108: RATE=18%

3.2 復制倍數為 32 時的均衡性

修改代碼中 VIRTUAL_COPIES = 32,運行結果如下(可見各節點負荷比較均衡):

======== 初始情況 ======== IP=192.168.1.101: RATE=29% IP=192.168.1.102: RATE=21% IP=192.168.1.103: RATE=25% IP=192.168.1.104: RATE=23% ======== 刪除物理節點 ======== IP=192.168.1.101: RATE=39% IP=192.168.1.102: RATE=37% IP=192.168.1.104: RATE=23% ======== 添加物理節點 ======== IP=192.168.1.101: RATE=35% IP=192.168.1.102: RATE=20% IP=192.168.1.104: RATE=23% IP=192.168.1.108: RATE=20%

3.2 復制倍數為 1M 時的均衡性

修改代碼中 VIRTUAL_COPIES = 1048576,運行結果如下(可見各節點負荷非常均衡):

======== 初始情況 ======== IP=192.168.1.101: RATE=24% IP=192.168.1.102: RATE=24% IP=192.168.1.103: RATE=25% IP=192.168.1.104: RATE=25% ======== 刪除物理節點 ======== IP=192.168.1.101: RATE=33% IP=192.168.1.102: RATE=33% IP=192.168.1.104: RATE=33% ======== 添加物理節點 ======== IP=192.168.1.101: RATE=25% IP=192.168.1.102: RATE=24% IP=192.168.1.104: RATE=24% IP=192.168.1.108: RATE=24%

4 應用

一致性哈希是分布式系統組件負載均衡的首選算法,它既可以在客戶端實現,也可以在中間件上實現。其應用有:

  • 分布式散列表(DHT)的設計;
  • 分布式關系數據庫(MySQL):分庫分表時,計算數據與節點的映射關系;
  • 分布式緩存:Memcached 的客戶端實現了一致性哈希,還可以使用中間件 twemproxy 管理 redis/memcache 集群;
  • RPC 框架 Dubbo:用來選擇服務提供者;
  • 亞馬遜的云存儲系統 Dynamo;
  • 分布式 Web 緩存;
  • Bittorrent DHT;
  • LVS。

總結

以上是生活随笔為你收集整理的一致性哈希算法的原理与实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。