以前一直听说过一致性hash,今天处理bug需要用到一致性hash,所以就看下:
keykey 的所有读写请求都必须一致地分配给同一个后端节点。 同时,分配的负载应该尽量均衡。
换句话讲, 我们需要寻找一种映射函数, 把随机到来的字符串 keykey ,一致地映射到 nn 个槽中。 此外,我们也希望,这个映射要做到尽量平均。
Mod-N哈希的扩容问题
下面,我们看下在前面提到的Mod-N哈希法的情况下, 新加一个节点或者移除一个故障节点会发生什么。
N4N4 时, 我们看到原本分配到 N2N2 的 kk 在扩容后会分配到 N1N1。 对于一个kvdb来说,这意味着我们需要在扩容后把 kk 从 N2N2 迁移到 N1N1 才能继续提供服务。 否则的话,扩容后的读请求将映射到新的节点 N1N1, 而导致读不到数据。
新增一个节点,会导致新映射和老映射的不一致
一致性哈希算法
我们希望构造一种函数 f(k,n)→mf(k,n)→m 把字符串映射到 nn 个槽上:
- 它的输入是随机到来的字符串 kk 和 槽的个数 nn.
- 输出是映射到的槽的标号 mm , m<nm<n.
这个函数需要有这样的性质:
- 映射均匀: 对随机到来的输入 k, 函数返回任一个 m的概率都应该是 1/n 。
- 一致性:
- 相同的 k, n 输入, 一定会有相同的输出。
- 当槽的数目增减时, 映射结果和之前不一致的字符串的数量要尽量少。
一致性哈希算法
我们希望构造一种函数 f(k,n)→mf(k,n)→m 把字符串映射到 nn 个槽上:
- 它的输入是随机到来的字符串 kk 和 槽的个数 nn.
- 输出是映射到的槽的标号 mm , m<nm<n.
这个函数需要有这样的性质:
- 映射均匀: 对随机到来的输入 kk, 函数返回任一个 mm 的概率都应该是 1/n1/n 。
- 一致性:
- 相同的 kk, nn 输入, 一定会有相同的输出。
- 当槽的数目增减时, 映射结果和之前不一致的字符串的数量要尽量少。
这个算法的关键特征在于, 不要导致全局重新映射, 而是要做增量的重新映射
将介绍几种一致性哈希算法:
- 哈希环法
- 跳跃一致性哈希法
一致性哈希环算法
具体的算法:
- 设 hash(key)hash(key) 是映射到区间 [0,232][0,232] 上的一个哈希函数。 把区间首尾相连,形成一个顺时针增长的哈希环(如图5.1) 。
- 将所有槽位(或者节点) N0,..,Nn−1N0,..,Nn−1 的标号 0,…,n−10,…,n−1 依次作为 hashhash 函数的输入进行哈希, 把结果分别标记在环上。
- 对于关于 kk 的映射,求出 z=hash(k)z=hash(k) , 标记在环上:
- 如果 zz 正好落在槽位上,返回这个槽位的标号。
- 否则, 顺时针沿着环寻找离 zz 最近的槽位,返回槽位标号
哈希环做到了在槽位数量变化前后的增量式的重新映射, 避免了全量的重新映射。
假设整体的 kk 的数量是 KK , 由于哈希映射的均匀性, 所以,添加或者删除一个槽位,总会只影响一个槽位的映射量,也就是 1/K1/K , 因此,哈希环做到了最小化重新映射(minimum disruption),做到了完全的一致性
哈希环法的复杂度分析
在技术实现上,实现哈希环的方法一般叫做 ketama 或 hash ring。 核心的逻辑在于如何在环上找一个和目标值 zz 相近的槽位, 我们把环拉开成一个自然数轴, 所有的槽位在环上的哈希值组成一个有序表。 在有序表里做查找, 这是二分查找可以解决的事情, 所以哈希环的映射函数的时间复杂度是 O(logn)O(logn)。
带权重的一致性哈希环
实际应用中, 还可以对槽位(节点)添加权重。 大概的逻辑是构建很多指向真实节点的虚拟节点, 也叫影子节点。 影子节点之间是平权的,选中影子节点,就代表选中了背后的真实节点。 权重越大的节点,影子节点越多, 被选中的概率就越大。
下面的图6.2是一个例子, 其中 N0,N1,N2,N3的权重比是 1:2:3:2。 选中一个影子节点如 V(N2)就是选中了 N2 。
一致性哈希环下的热扩容和容灾
回到kvdb的例子上来, 对于增删节点的情况,哈希环法做到了增量式的重新映射, 不再需要全量数据迁移的工作。 但仍然有部分数据出现了变更前后映射不一致, 技术运营上仍然存在如下问题:
- 扩容:当增加节点时,新节点需要对齐下一节点的数据后才可以正常服务。
- 缩容:当删除节点时,需要先把数据备份到下一节点才可以停服移除。
- 故障:节点突然故障不得不移除时,面临数据丢失风险。
如果我们要实现动态扩容和缩容,即所谓的热扩容,不停止服务对系统进行增删节点, 可以这样做:
- 数据备份(双写): 数据写入到某个节点时,同时写一个备份(replica)到顺时针的邻居节点。
- 请求中继(代理): 新节点刚加入后,数据没有同步完成时,对读取不到的数据,可以把请求中继(replay)到顺时针方向的邻居节点。
结论:
实现了最小化的重新映射。
时间复杂度是 O(log(n))O(log(n)) , 空间复杂度是 O(n)O(n), 实际根据影子节点数量而乘上相应倍数。 映射均匀性不是很优秀。 热扩容和容灾的方式比较直观。
跳跃一致性哈希法
跳跃一致性哈希 ( Jump Consistent Hash ) 是 Google 于2014年发布的一个极简的、快速的一致性哈希算法[1]。 这个算法精简到可以用几行代码来描述, 下面的就是 Google 原论文中的算法的 C++ 表示:
int32_t JumpConsistentHash(uint64_t key, int32_t num_buckets) {
int64_t b = -1, j = 0;
while (j < num_buckets) {
b = j;
key = key * 2862933555777941757ULL + 1;
j = (b + 1) * (double(1LL << 31) / double((key >> 33) + 1));
}
return b;
}
函数 JumpConsistentHash
是一个一致性哈希函数, 它把一个 key
一致性地映射到给定几个槽位中的一个上, 输入 key
和槽位数量 num_buckets
, 输出映射到的槽位标号
参考:https://writings.sh/post/consistent-hashing-algorithms-part-3-jump-consistent-hash
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子