我试图了解一致哈希的工作原理。这篇文章我试图follow,但无法遵循,从我的问题开始:
据我所知,服务器被映射到哈希码的范围,数据分布更加固定,外观变得容易。但是,如何解决集群中添加新节点的问题?
示例java代码无效,任何基于 java 的简单一致哈希的建议。
更新
答案 0 :(得分:1)
我将回答你问题的第一部分。首先,该代码中存在一些错误,因此我会寻找更好的示例。
此处使用缓存服务器作为示例。
当您考虑一致哈希时,您应该将其视为圆环,就像您链接的文章一样。添加新服务器时,它将无法启动数据。当客户端获取应该在该服务器上但未找到该数据的数据时,将发生缓存未命中。然后程序应该填写新节点上的数据,因此将来的请求将是缓存命中。从缓存的角度来看,就是这样。
答案 1 :(得分:1)
对于python实现,请参阅我的github repo
最简单的解释 什么是普通哈希?
假设我们必须将以下键值对存储在像redis这样的分布式内存中。
假设我们有一个哈希函数f(id),它接受上面的id并从中创建哈希。 假设我们有3个服务器-(s1,s2和s3)
我们可以通过服务器的数量(即3)对哈希进行模运算,以将每个密钥映射到服务器,然后剩下以下内容。
我们可以使用f()通过简单的查找来检索键的值。说出密钥Jackson,f(“ Jackson”)%(服务器数量)=> 1211 * 3 = 2(节点2)。
这看起来很完美,是的,但不是雪茄!
但是,如果服务器说节点1出现故障怎么办?为用户Jackson应用相同的公式,即f(id)%(服务器数量),1211%2 = 1 也就是说,当上表中将实际密钥散列到node-2时,我们得到了node-1。
我们可以在这里进行重新映射,如果我们有十亿个键,那该怎么办呢?我们必须重新映射大量的键,这很乏味:(
这是传统哈希技术的主要流程。
什么是一致性哈希?
在一致性哈希中,我们可视化圆环中所有节点的列表。(基本上是一个排序数组)
start func
For each node:
Find f(node) where f is the hash function
Append each f(node) to a sorted array
For any key
Compute the hash f(key)
Find the first f(node)>f(key)
map it
end func
例如,如果必须对密钥史密斯进行哈希处理,我们计算哈希值1123,找到哈希值> 1123的直接节点,即哈希值1500的节点3
现在,如果我们松开服务器,比如说松开节点2,该怎么办?所有键都可以映射到下一个服务器节点3 :) 是的,我们只需要重新映射节点2的键
答案 2 :(得分:0)
我写了blog来说明一致性哈希的工作原理,这里回答了那些原始问题,以下是快速摘要。
一致性哈希通常用于数据分区,我们通常在类似的组件中看到它
要回答问题,下面将介绍
让我们在这里使用一个简单的示例负载均衡器,负载均衡器将2个原始节点(负载均衡器后面的服务器)和传入的请求映射到同一哈希环圈子中(比如说哈希环)范围是[0,255]。
对于服务器节点,我们有一个表:
对于任何传入请求,我们都应用相同的哈希函数,然后假设我们为该请求获取了一个哈希代码,该哈希代码为 hashcode = 120 ,现在从表中找到 next ,因此,节点2 是目标节点。
类似地,如果我们收到哈希码= 220的请求怎么办?由于哈希环范围是一个圆,因此我们将返回第一个节点。
现在让我们在集群中再添加一个节点:节点3(哈希码150),然后我们的表将更新为:
然后,我们在查找节点部分中使用相同的算法来查找下一个最近的节点。假设哈希码= 120的请求现在将被路由到node-3。
删除也很简单,只需从表中删除条目
然后使用以下所有请求
下面是一个简单的c ++版本(启用了虚拟节点),它与Java非常相似。
#define HASH_RING_SZ 256
struct Node {
int id_;
int repl_;
int hashCode_;
Node(int id, int replica) : id_(id), repl_(replica) {
hashCode_ =
hash<string>{} (to_string(id_) + "_" + to_string(repl_)) % HASH_RING_SZ;
}
};
class ConsistentHashing {
private:
unordered_map<int/*nodeId*/, vector<Node*>> id2node;
map<int/*hash code*/, Node*> ring;
public:
ConsistentHashing() {}
// Allow dynamically assign the node replicas
// O(Rlog(RN)) time
void addNode(int id, int replica) {
for (int i = 0; i < replica; i++) {
Node* repl = new Node(id, replica);
id2node[id].push_back(repl);
ring.insert({node->hashCode_, repl});
}
}
// O(Rlog(RN)) time
void removeNode(int id) {
auto repls = id2node[id];
if (repls.empty()) return;
for (auto* repl : repls) {
ring.erase(repl->hashCode_);
}
id2node.erase(id);
}
// Return the nodeId
// O(log(RN)) time
int findNode(const string& key) {
int h = hash<string>{}(key) % HASH_RING_SZ;
auto it = ring.lower_bound(h);
if (it == ring.end()) it == ring.begin();
return it->second->id;
}
};
如果我正确理解了这个问题,那么这个问题就是要问用于数据分区的一致性哈希的任何替代方案。实际上有很多,根据实际用例,我们可以选择不同的方法,例如:
特别是在负载平衡领域,还有一些方法,例如:
以上所有方法都有其优缺点,没有最佳解决方案,我们必须相应地进行权衡。
在上面对原始问题进行简要总结,以供进一步阅读,例如:
我已经在blog中介绍了它们,以下是快捷方式:
玩得开心:)