让我们先了解我们要解决的问题。
为什么我们需要这个?
在传统的基于哈希的分发方法中,我们使用哈希函数对分区密钥(即请求ID或IP)进行散列。然后,我们对节点总数(服务器或数据库)使用模运算。这会使我们得到要路由请求的节点。
这里,
key:请求ID或IP。
H: 哈希函数结果。
N: 节点总数。
Node:请求将路由到的节点。
问题是,如果我们添加或删除节点,它将导致 N 发生变化,这意味着我们的映射策略被破坏,因为相同的请求现在将映射到不同的服务器。因此,大多数请求需要重新分配,这是非常低效的。
我们希望在不同的节点之间均匀分布请求,以便能够以最小的代价添加或删除节点。因此,我们需要一个不直接依赖于节点(或服务器)数量的分发方案,以便在添加或删除节点时,需要重新定位的密钥数量最小化。
一致性哈希通过确保每次向上或向下扩展时,我们不必重新排列所有键或获悉所有服务器,从而解决了这个水平可扩展性问题。
既然我们理解了这个问题,让我们详细讨论一致哈希。
它是如何工作的
一致性哈希是一种分布式哈希方案,通过在抽象圆或哈希环上为节点分配位置,独立于分布式哈希表中的节点数进行操作。这允许在不影响整个系统的情况下扩展服务器和对象。
使用一致性哈希,只有 K/N 数据需要重新分发。
者里
-
R: 需要重新分发的数据。 -
K: 分区键的数量。 -
N: 节点数。
哈希函数的输出是一个区间,比如 0...m-1 这些我们可以在哈希环上表示的数字。我们对请求进行哈希,并根据输出结果在环上分发它们。类似地,我们还对节点进行哈希,并将它们分布在同一个环上。
这里,
key:请求/节点ID或IP。
P: 哈希环上的位置。
m: 哈希环的总区间。
现在,当请求到来时,我们可以简单地以顺时针(也可以逆时针)的方式将其路由到最近的节点。这意味着,如果添加或删除新节点,我们可以使用最近的节点,只需要重新路由一小部分请求。
理论上,一致性哈希应该均匀地分配负载,但在实践中不是这样的。通常,负载分布是不均匀的,一台服务器可能处理大部分请求,成为热点,本质上是系统的瓶颈。我们可以通过添加额外节点来解决这个问题,但这可能会很昂贵。
让我们看看如何解决这些问题。
虚拟节点
为了确保负载分布更均匀,我们可以引入虚拟节点的概念,有时也称为VNode。
这里没有将单个位置分配给节点,而是将哈希区间划分为多个较小的区间,并为每个物理节点分配几个较小的区间。这些子区间中的每一个都被视为VNode。因此,虚拟节点基本上是跨哈希环多次映射的现有物理节点,以最小化对节点分配区间的更改。
为此,我们可以使用 k 个哈希函数。
这里,
密钥
key:请求/节点ID或IP。
k: 哈希函数的数量。
P: 哈希环上的位置。
m: 哈希环的总区间。
由于 vNode 通过将哈希区间划分为更小的子区间,有助于将负载更均匀地分布在集群上的物理节点上,因此可以加快添加或删除节点后的重新平衡过程。这也有助于我们减少出现热点的概率。
数据复制
为了确保高可用性和持久性,一致性哈希在系统中的多个 N 节点上复制每个数据项,其中值 N 等于复制因子。
复制因子是接收相同数据副本的节点数。在最终一致的系统中,这个过程一般异步完成。
优点
让我们看看一致性哈希的一些优点:
- 使快速的向上或向下扩展更可预测。
- 便于跨节点进行分区和复制。
- 实现可扩展性和可用性。
- 减少热点。
缺点
以下是一致性哈希的一些缺点:
- 增加了复杂性。
- 级联故障。
- 负载分布仍然可能不均匀。
- 当节点暂时故障时,密钥管理可能代价很高。
例子
让我们看一些使用一致性哈希的例子:
Apache Cassandra中的数据分区。
在 Amazon DynamoDB 中跨多个存储主机的负载分布。