一致性哈希

简介:

在 1997 年,MIT 的计算机科学实验室研究员 David Karger, Eric Lehman, Tom Leighton, Matthew Levine, Daniel Lewin, Rina Panigrahy 等发表了论文《Consistent hashing and random trees: distributed caching protocols for relieving hot spots on the World Wide Web》,提出了一致性哈希(Consistent Hashing)的概念,其设计目标是为了解决大型网络中的热点问题(Hot Spots)。

在实际应用中,有很多需要将一个 Item 分布到多个 Bucket 中的场景:

  • 将数据加载到内存中(Data --> Memory)
  • 将文件写入到磁盘中(File --> Disk)
  • 将任务分配到处理器(Task --> Processor)
  • 将页面加载到缓存中(Page --> Cache)

在这些场景中,我们的设计目标就是要将 Item 均匀地分布(Even Distribution)到不同的 Bucket 中,也就是负载均衡(Load Balancing)

对于负载均衡中的负载,我们实际上会面对两种情况:

  • 持续性的负载(Permanent Loads)
  • 临时性的负载(Temporary Loads)

对于持续性的负载可以通过提高服务器的能力或者通过添加固定数量的机器来分担压力。而临时性的负载则是最难处理的,这需要系统的设计能够抵御突发的请求高峰。

对于处理临时性的负载,通常我们会考虑使用缓存代理(Proxy Caching)技术,它使得对 Home 主机的访问模式由原先的每次浏览均访问一次转换到每个页面仅访问一次。

在使用 Cache 机制时,会遇到多种情况:

  • 如果某一个 Cache 持有了大量的 Items,则该 Cache 将被大量的客户请求所淹没。所以Cache 应该持有较少的 Items
  • 如果某一个 Item 会被大量的 Cache 所持有,则 Cache 对 Home 的请求将会成为压力的来源。所以 Item 应该被放入到少量的 Cache 中
  • 如果请求方不知道正确的 Cache 位置,则需要 Server 端重定向,则重定向 Server 将成为瓶颈。所以请求方必须知道 Cache 的正确位置

通常,使用 Hashing 技术可以提供简单有效的负载均衡。Hashing 使得查找存放 Item 的 Bucket 的时间是 O(1)。但如果涉及到新增 Cache 或者移除 Cache 等操作,将会导致所有 Item 重新 Hashing,已经存在的缓存内容将会失效。

例如,设 n 为 Cache 的数量,使用最简单的 Linear Hashing 则有:

y = ax + b (mod n)

而当新增一个 Cache 时,Cache 的数量变为 n + 1:

y = ax + b (mod (n+1))

而当移除一个 Cache 时,Cache 的数量变为 n - 1:

y = ax + b (mod (n-1))

当所有的缓存都失效时,新的请求都会抵达 Home 主机,导致 Home 主机崩溃。所以,在处理 Cache 的新增和移除时,较好的效果就是只有少量的 Items 失效。

一致性哈希的性质

一致性哈希(Consistent Hashing)将会从单视角和多视角来看待 Item 和 Bucket 的关系,这使得一致性哈希会满足一些特定的性质。

视角(View)的不一致性(Inconsistent Views)将影响对 Cache 的选择。从用户视角(Client View)来看,每个 Client 只了解一组不同的 Cache 集合。

如果 View 特别多,则同一个 Item 可以出现在每个 Cache 上,这样 Home 将会被 Cache 的请求淹没。所以,对于分散 Item 的设计目标是,不管有多少 View,Item 将仅存在于少数 Cache 上。

单视角性质(Single View Properties)

  • 平衡(Balance):所有的 Bucket 都可以获取到大致平均的 Item 数量。
  • 平滑(Smooth):当添加第 kth 个 Bucket 时,仅会影响所有 Items 中的 1/k 部分,并且仅影响 O(log n) 个服务器。又称为单调性(Monotonicity)。

多视角性质(Multiple View Properties)

假设有 n 个视角,每一个对应到所有 Buckets 的一个任意常量的部分。

  • 负载(Load):任意一个 Bucket 从所有 Views 中所获得的 Items 的数量是 O(log n),而无论有多少视角,来保证负载的平衡。
  • 分散(Spread):从所有 Views 来看,每一个 Item 将出现在 O(log n) 个 Buckets 中,而无论有多少视角,对于单一的 Item 都将出现在较少的 Buckets 中。

一致性哈希的实现

对所有的 Buckets 和 Items 应用相同的哈希函数 H,按照哈希值的顺序把它们排列到一条线上,然后将距离 Bucket 最近的 Items 都放入该 Bucket 中。

另一种实现方式是把哈希值的最大值和最小值连接到一起,形成一个哈希环(Consistent Hashing Ring),按照顺时针方向将 Items 放入 Bucket 中。

使用哈希函数 H 对 Item 进行计算后,需要找到合适的 Bucket 放入,此时可以选择不同的算法,例如:

  • 使用二分查找法为 O(log n)。
  • 可以预先计算 Bucket 的哈希,使用额外的哈希表为 O(1)。

平衡(Balance)

如果哈希函数 H 可以将 Bucket 均匀地分布到线上,则每个 Bucket 都拥有线上均等的部分。

同样哈希函数 H 将 Item 也均匀地分布到线上,这样 Item 将等可能地分布到任意的 Bucket 中,所有 Bucket 都能获得均等数量的 Items。

平滑(Smooth)

通常 Bucket 会捕获离其最近的 Items。

当要添加第 kth 个 Bucket 时,使用同样的哈希函数 H 将其添加到线上。

 这样,新的 Bucket 将捕获离其最近的 Items。


我们发现,在这种条件下:

  • 仅有 Items 总数的 1/k 部分会被影响到。
  • 仅影响新的 Bucket 左右两边的 2 个 Buckets。

负载(Load)

一个 Bucket 将捕获其附近的 Items,如果 Item 离它是最近的。但 Item 不可能总是离某一个 Bucket 是最近的,则任意一个 Bucket 都不可能捕获特别多的 Items,所以负载是均衡的。

分散(Spread)

对于一个 Item,只有真正离其最近的 Bucket 才会捕获它。在每一个视角中,都只会有一个离其最近的 Bucket,这样对于单一的 Item 都将出现在较少的 Buckets 中。

虚拟节点策略(Virtual Node Strategy)

如果使用的哈希算法 H 并不能保证绝对的平衡性,或者可使用的 Buckets 数量较少时,Items 可能无法被均匀地映射到 Buckets 上。为了解决这种问题,可以在哈希环中引入虚拟节点(Virtual Node)的策略。

虚拟节点(Virtual Node)是实际节点在哈希空间中的复制品(Replica)。一个实际节点可以对应若干个虚拟节点,虚拟节点在哈希空间中使用同样的哈希函数 H 计算哈希值并进行排列。

一致性哈希的简单代码实现

首先构建哈希算法的抽象。

  public interface IHashAlgorithm
  {
    int Hash(string item);
  }

向 ConsistentHash<T> 类中注入 IHashAlgorithm 的具体实现。

ConsistentHash<T> 类内实现哈希环,并可以指定虚拟节点的复制因子。

复制代码
 1   public class ConsistentHash<T>
 2   {
 3     private SortedDictionary<int, T> _ring = new SortedDictionary<int, T>();
 4     private int[] _nodeKeysInRing = null;
 5     private IHashAlgorithm _hashAlgorithm;
 6     private int _virtualNodeReplicationFactor = 1000;
 7 
 8     public ConsistentHash(IHashAlgorithm hashAlgorithm)
 9     {
10       _hashAlgorithm = hashAlgorithm;
11     }
12 
13     public ConsistentHash(IHashAlgorithm hashAlgorithm, int virtualNodeReplicationFactor)
14       : this(hashAlgorithm)
15     {
16       _virtualNodeReplicationFactor = virtualNodeReplicationFactor;
17     }
18 
19     public int VirtualNodeReplicationFactor
20     {
21       get { return _virtualNodeReplicationFactor; }
22     }
23 
24     public void Initialize(IEnumerable<T> nodes)
25     {
26       foreach (T node in nodes)
27       {
28         AddNode(node);
29       }
30       _nodeKeysInRing = _ring.Keys.ToArray();
31     }
32 
33     public void Add(T node)
34     {
35       AddNode(node);
36       _nodeKeysInRing = _ring.Keys.ToArray();
37     }
38 
39     public void Remove(T node)
40     {
41       RemoveNode(node);
42       _nodeKeysInRing = _ring.Keys.ToArray();
43     }
44 
45     private void AddNode(T node)
46     {
47       for (int i = 0; i < _virtualNodeReplicationFactor; i++)
48       {
49         int hashOfVirtualNode = _hashAlgorithm.Hash(node.GetHashCode().ToString() + i);
50         _ring[hashOfVirtualNode] = node;
51       }
52     }
53 
54     private void RemoveNode(T node)
55     {
56       for (int i = 0; i < _virtualNodeReplicationFactor; i++)
57       {
58         int hashOfVirtualNode = _hashAlgorithm.Hash(node.GetHashCode().ToString() + i);
59         _ring.Remove(hashOfVirtualNode);
60       }
61     }
62 
63     public T GetItemNode(string item)
64     {
65       int hashOfItem = _hashAlgorithm.Hash(item);
66       int nearestNodePosition = GetClockwiseNearestNode(_nodeKeysInRing, hashOfItem);
67       return _ring[_nodeKeysInRing[nearestNodePosition]];
68     }
69 
70     private int GetClockwiseNearestNode(int[] keys, int hashOfItem)
71     {
72       int begin = 0;
73       int end = keys.Length - 1;
74 
75       if (keys[end] < hashOfItem || keys[0] > hashOfItem)
76       {
77         return 0;
78       }
79 
80       int mid = begin;
81       while ((end - begin) > 1)
82       {
83         mid = (end + begin) / 2;
84         if (keys[mid] >= hashOfItem) end = mid;
85         else begin = mid;
86       }
87 
88       return end;
89     }
90   }
复制代码

然后,我们就可以实现任意一个符合需求的哈希算法,比如如下的 MurmurHash2 算法。

复制代码
 1   public class MurmurHash2HashAlgorithm : IHashAlgorithm
 2   {
 3     public int Hash(string item)
 4     {
 5       uint hash = Hash(Encoding.ASCII.GetBytes(item));
 6       return (int)hash;
 7     }
 8 
 9     private const UInt32 m = 0x5bd1e995;
10     private const Int32 r = 24;
11 
12     public static UInt32 Hash(Byte[] data)
13     {
14       return Hash(data, 0xc58f1a7b);
15     }
16 
17     public static UInt32 Hash(Byte[] data, UInt32 seed)
18     {
19       Int32 length = data.Length;
20       if (length == 0)
21         return 0;
22 
23       UInt32 h = seed ^ (UInt32)length;
24       Int32 c = 0; // current index
25       while (length >= 4)
26       {
27         UInt32 k = (UInt32)(
28           data[c++]
29           | data[c++] << 8
30           | data[c++] << 16
31           | data[c++] << 24);
32         k *= m;
33         k ^= k >> r;
34         k *= m;
35 
36         h *= m;
37         h ^= k;
38         length -= 4;
39       }
40       switch (length)
41       {
42         case 3:
43           h ^= (UInt16)(data[c++] | data[c++] << 8);
44           h ^= (UInt32)(data[c] << 16);
45           h *= m;
46           break;
47         case 2:
48           h ^= (UInt16)(data[c++] | data[c] << 8);
49           h *= m;
50           break;
51         case 1:
52           h ^= data[c];
53           h *= m;
54           break;
55         default:
56           break;
57       }
58 
59       // Do a few final mixes of the hash to ensure the last few
60       // bytes are well-incorporated.
61 
62       h ^= h >> 13;
63       h *= m;
64       h ^= h >> 15;
65 
66       return h;
67     }
68   }
复制代码

这样,我们就可以测试该 ConsistentHash<T> 的分布效果了。

复制代码
 1   class Program
 2   {
 3     static void Main(string[] args)
 4     {
 5       List<CacheServer> servers = new List<CacheServer>();
 6       for (int i = 0; i < 5; i++)
 7       {
 8         servers.Add(new CacheServer(i));
 9       }
10 
11       var consistentHashing = new ConsistentHash<CacheServer>(
12         new MurmurHash2HashAlgorithm(), 10000);
13       consistentHashing.Initialize(servers);
14 
15       int searchNodeCount = 10;
16 
17       SortedList<int, int> bucketNodes = new SortedList<int, int>();
18       for (int i = 0; i < searchNodeCount; i++)
19       {
20         string item = i.ToString();
21         int serverId = consistentHashing.GetItemNode(item).ID;
22         bucketNodes[i] = serverId;
23 
24         Console.WriteLine("Item[{0}] is in Node[{1}]", i, bucketNodes[i]);
25       }
26 
27       Console.Read();
28     }
29   }
30 
31   public class CacheServer
32   {
33     public CacheServer(int serverId)
34     {
35       ID = serverId;
36     }
37 
38     public int ID { get; set; }
39 
40     public override int GetHashCode()
41     {
42       return ("CacheServer-" + ID).GetHashCode();
43     }
44   }
复制代码

上面代码示例中向一致性哈希环获取给定的 10 个 Items 的顺时针方向的节点,结果如下。

完整代码位置在 Github :ConsistentHashingDemo

参考资料






本文转自匠心十年博客园博客,原文链接:http://www.cnblogs.com/gaochundong/p/consistent_hashing.html,如需转载请自行联系原作者

目录
相关文章
|
5月前
|
Web App开发 编解码 数据可视化
实时云渲染:数字孪生可视化最稳定省心的方案
数字孪生技术发展面临大内容与轻终端的矛盾,实时云渲染成为解决这一问题的关键。它支持大规模复杂数据处理、低延迟交互、跨终端访问、高精度可视化及弹性扩展,同时保障数据安全。平行云Lark XR作为企业级实时云渲染PaaS平台,相比Epic Games UE引擎的像素流送插件,提供更稳定、兼容性强、功能全面的解决方案,适用于工业数字孪生、云游戏、元宇宙活动等场景,助力企业专注于业务创新。
|
9月前
|
存储 监控 安全
实时记录和查看Apache 日志
Apache 是一个开源、跨平台的 Web 服务器,保护其平台需监控活动和事件。Apache 日志分为访问日志和错误日志,分别记录用户请求和服务器错误信息。EventLog Analyzer 是一款强大的日志查看工具,提供集中收集、分析、实时警报和安全监控功能,帮助管理员识别趋势、检测威胁并确保合规性。通过直观的仪表板和自动化响应,它简化了大规模日志管理,增强了 Apache 服务器的安全性和性能。
175 5
|
网络协议
Qt中的网络编程(Tcp和Udp)运用详解以及简单示范案例
Tcp和Udp是我们学习网络编程中经常接触到的两个通讯协议,在Qt也被Qt封装成了自己的库供我们调用,对于需要进行网络交互的项目中无疑是很重要的,希望这篇文章可以帮助到大家。 是关于Qt中TCP和UDP的基本使用和特点:
1744 7
|
8月前
|
存储 人工智能 编解码
Pippo:Meta放出AI大招!单张照片秒转3D人像多视角视频,AI自动补全身体细节
Pippo 是 Meta 推出的图像到视频生成模型,能够从单张照片生成 1K 分辨率的多视角高清人像视频,支持全身、面部或头部的生成。
518 9
Pippo:Meta放出AI大招!单张照片秒转3D人像多视角视频,AI自动补全身体细节
|
存储 大数据 对象存储
ClickHouse 如何实现数据一致性
本文探讨了在 ClickHouse 中实现数据一致性的方法,主要关注 `ReplacingMergeTree` 引擎。该引擎允许更新已有数据,通过定期合并操作删除重复并保持最终一致性。然而,由于合并时间不可预测,单纯依赖此引擎无法确保实时一致性。为解决此问题,文章提出了四种策略:1)手动触发合并,但不建议频繁使用;2)使用 `FINAL` 查询,但在查询时合并数据,效率较低;3)通过标记和 `GroupBy` 查询实现一致性;4)在允许一定偏差的情况下,直接使用 `ReplacingMergeTree` 保持最终一致性。在实践中,推荐结合标记列和 `GroupBy` 以保证数据一致性。
796 0
一键语法错误增强工具 ChineseErrorCorrector
【8月更文挑战第8天】ChineseErrorCorrector是一款实用的中文语法纠错工具,能迅速检测并修正文本中的语法错误,提升文本质量。其特点包括:1) 精准检测各种语法错误如词语搭配不当等;2) 提供详细错误解释及修改建议,助您学习语法规则;3) 快速高效地检查文本,适用于写作、编辑等场景。适合学生提高写作水平、商务文档的专业审阅、网络内容创作的质量把控以及翻译工作的校对等。总之,这款工具为中文文本提供了便捷高效的语法检查方案。
403 4
|
C# 开发者 Windows
一款基于Fluent设计风格、现代化的WPF UI控件库
一款基于Fluent设计风格、现代化的WPF UI控件库
318 1
|
数据采集 存储 数据可视化
基于Python flask的猫眼电影票房数据分析可视化系统,可以定制可视化
本文介绍了一个基于Python Flask框架开发的猫眼电影票房数据分析可视化系统,该系统集成了数据爬取、存储处理、可视化展示和用户交互功能,使用户能够直观地分析和展示电影票房数据,具有高度定制性。
412 0
基于Python flask的猫眼电影票房数据分析可视化系统,可以定制可视化
|
机器学习/深度学习 人工智能 芯片
一文详解多模态大模型发展及高频因子计算加速GPU算力 | 英伟达显卡被限,华为如何力挽狂澜?
近年来,全球范围内的芯片禁令不断升级,给许多企业和科研机构带来了很大的困扰,需要在技术层面进行创新和突破。一方面,可以探索使用国产芯片和其他不受限制的芯片来替代被禁用的芯片;另一方面,可以通过优化算法和架构等方法来降低对特定芯片的依赖程度。
1146 0
|
Java 测试技术 项目管理
产品入门第三讲:Axure产品流程图绘制
产品入门第三讲:Axure产品流程图绘制
322 0
产品入门第三讲:Axure产品流程图绘制