一致性哈希算法是分布式系统中常用的负载均衡算法,特别适合动态变化的服务节点场景。它的核心思想是将服务节点和数据映射到一个虚拟的哈希环上,通过哈希值定位数据所属的节点。当节点增加或减少时,一致性哈希算法能够最小化数据迁移的影响。
算法设计:
1.数据结构:
-
哈希环 :使用
map(有序map)
/unordered_map(键无序map)
来存储虚拟节点和真实节点的映射关系。 -
虚拟节点:为了提高负载均衡的均匀性,为每个真实节点创建多个虚拟节点。
-
哈希函数:选择MD5作为哈希函数,将节点和 key 映射到一个 32 位的哈希值。
2.代码实现:
cpp
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <openssl/md5.h> // 使用 MD5 作为哈希函数
class ConsistentHash {
public:
// 添加节点
void AddNode(const std::string& node, int virtualNodeCount) {
for (int i = 0; i < virtualNodeCount; ++i) {
std::string virtualNode = node + "#" + std::to_string(i);
size_t hash = Hash(virtualNode);
ring_[hash] = node;
}
}
// 删除节点
void RemoveNode(const std::string& node) {
auto it = ring_.begin();
while (it != ring_.end()) {
if (it->second == node) {
it = ring_.erase(it);
} else {
++it;
}
}
}
// 根据 key 获取目标节点
std::string GetNode(const std::string& key) {
if (ring_.empty()) {
throw std::runtime_error("No nodes available in the ring.");
}
size_t hash = Hash(key);
auto it = ring_.lower_bound(hash); // 找到第一个大于等于 hash 的节点
if (it == ring_.end()) {
// 如果没找到,则选择环中的第一个节点
it = ring_.begin();
}
return it->second;
}
private:
// 哈希函数(使用 MD5)
size_t Hash(const std::string& key) {
unsigned char digest[MD5_DIGEST_LENGTH];
MD5((const unsigned char*)key.c_str(), key.size(), digest);
// 将 MD5 哈希值转换为 size_t
size_t hash = 0;
for (int i = 0; i < 4; ++i) { // 简化 故只取前 4 个字节作为哈希值
hash = (hash << 8) | digest[i];
}
return hash;
}
// 哈希环,存储哈希值到节点的映射
std::map<size_t, std::string> ring_;
};
代码说明:
-
虚拟节点:
-
每个真实节点对应多个虚拟节点(通过
AddNode
方法的virtualNodeCount
参数控制)。 -
虚拟节点的数量越多,负载分布越均匀,但也会增加内存开销。
-
优化分析:虚拟节点的引入是为了解决一致性哈希算法在节点数量较少时负载不均衡的问题。可以进一步优化虚拟节点的生成方式,可以根据节点的权重动态调整虚拟节点的数量
-
-
哈希环:
- 使用
map
存储哈希值到节点的映射,map
本身是有序的,便于快速查找。
- 使用
-
节点查找:
-
使用
lower_bound
方法在哈希环中查找第一个大于等于 key 哈希值的节点。 -
如果没找到(即 key 的哈希值大于所有节点的哈希值),则选择环中的第一个节点。
-
-
动态节点变化:
-
通过
AddNode
和RemoveNode
方法支持节点的动态添加和删除。 -
当节点变化时,只有部分 key 需要重新映射,其他 key 的映射关系保持不变
-
优化分析:
可以进一步优化节点变化的处理效率,在删除节点时,批量移除虚拟节点,减少遍历哈希环的次数。在添加节点时,预分配虚拟节点的哈希值,减少哈希计算的开销
-
测试:
cpp
int main() {
ConsistentHash ch;
// 添加节点,假设每个节点有 3 个虚拟节点
ch.AddNode("Node1", 3);
ch.AddNode("Node2", 3);
ch.AddNode("Node3", 3);
// 测试 key 的路由
std::vector<std::string> keys = {"Key1", "Key2", "Key3", "Key4", "Key5"};
for (const auto& key : keys) {
std::string node = ch.GetNode(key);
std::cout << "Key: " << key << " -> Node: " << node << std::endl;
}
// 删除一个节点
ch.RemoveNode("Node2");
std::cout << "\n 删除Node2:\n";
// 再次测试 key 的路由
for (const auto& key : keys) {
std::string node = ch.GetNode(key);
std::cout << "Key: " << key << " -> Node: " << node << std::endl;
}
return 0;
}
结果:
cpp
Key: Key1 -> Node: Node1
Key: Key2 -> Node: Node3
Key: Key3 -> Node: Node2
Key: Key4 -> Node: Node1
Key: Key5 -> Node: Node3
删除 Node2:
Key: Key1 -> Node: Node1
Key: Key2 -> Node: Node3
Key: Key3 -> Node: Node1
Key: Key4 -> Node: Node1
Key: Key5 -> Node: Node3
当与具体场景的负载均衡结合:
-
服务注册 :当服务节点注册时,调用
AddNode
方法将其添加到哈希环中。 -
服务发现 :当客户端发起请求时,调用
GetNode
方法根据请求的 key 找到目标节点。 -
动态变化 :当服务节点下线时,调用
RemoveNode
方法将其从哈希环中移除。