一致哈希是如何工作的?

时间:2010-09-29 02:44:45

标签: java dht consistent-hashing

我试图了解一致哈希的工作原理。这篇文章我试图follow,但无法遵循,从我的问题开始:

  1. 据我所知,服务器被映射到哈希码的范围,数据分布更加固定,外观变得容易。但是,如何解决集群中添加新节点的问题?

  2. 示例java代码无效,任何基于 java 的简单一致哈希的建议。

  3. 更新

    1. 一致哈希的替代方法吗?

3 个答案:

答案 0 :(得分:1)

我将回答你问题的第一部分。首先,该代码中存在一些错误,因此我会寻找更好的示例。

此处使用缓存服务器作为示例。

当您考虑一致哈希时,您应该将其视为圆环,就像您链接的文章一样。添加新服务器时,它将无法启动数据。当客户端获取应该在该服务器上但未找到该数据的数据时,将发生缓存未命中。然后程序应该填写新节点上的数据,因此将来的请求将是缓存命中。从缓存的角度来看,就是这样。

答案 1 :(得分:1)

对于python实现,请参阅我的github repo

最简单的解释 什么是普通哈希?

假设我们必须将以下键值对存储在像redis这样的分布式内存中。

enter image description here

假设我们有一个哈希函数f(id),它接受上面的id并从中创建哈希。 假设我们有3个服务器-(s1,s2和s3)

我们可以通过服务器的数量(即3)对哈希进行模运算,以将每个密钥映射到服务器,然后剩下以下内容。

enter image description here

我们可以使用f()通过简单的查找来检索键的值。说出密钥Jackson,f(“ Jackson”)%(服务器数量)=> 1211 * 3 = 2(节点2)。

这看起来很完美,是的,但不是雪茄!

但是,如果服务器说节点1出现故障怎么办?为用户Jackson应用相同的公式,即f(id)%(服务器数量),1211%2 = 1 也就是说,当上表中将实际密钥散列到node-2时,我们得到了node-1。

我们可以在这里进行重新映射,如果我们有十亿个键,那该怎么办呢?我们必须重新映射大量的键,这很乏味:(

这是传统哈希技术的主要流程。

什么是一致性哈希?

在一致性哈希中,我们可视化圆环中所有节点的列表。(基本上是一个排序数组)

ConsistantHashingRing

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的键

enter image description here

答案 2 :(得分:0)

概述

我写了blog来说明一致性哈希的工作原理,这里回答了那些原始问题,以下是快速摘要。

一致性哈希通常用于数据分区,我们通常在类似的组件中看到它

  • 负载均衡器
  • API网关
  • ...

要回答问题,下面将介绍

  1. 工作原理
  2. 如何添加/查找/删除服务器节点
  3. 如何实施
  4. 一致性哈希的其他替代方法吗?

让我们在这里使用一个简单的示例负载均衡器,负载均衡器将2个原始节点(负载均衡器后面的服务器)和传入的请求映射到同一哈希环圈子中(比如说哈希环)范围是[0,255]。

初始状态

对于服务器节点,我们有一个表:

enter image description here

查找节点

对于任何传入请求,我们都应用相同的哈希函数,然后假设我们为该请求获取了一个哈希代码,该哈希代码为 hashcode = 120 ,现在从表中找到 next ,因此,节点2 是目标节点。

类似地,如果我们收到哈希码= 220的请求怎么办?由于哈希环范围是一个圆,因此我们将返回第一个节点。

添加节点

现在让我们在集群中再添加一个节点:节点3(哈希码150),然后我们的表将更新为:

enter image description here

然后,我们在查找节点部分中使用相同的算法来查找下一个最近的节点。假设哈希码= 120的请求现在将被路由到node-3。

删除节点

删除也很简单,只需从表中删除条目,假设我们删除了node-1,则表将更新为:

enter image description here

然后使用以下所有请求

  • [201,255]和[0,150]中的哈希码将被路由到节点3
  • [151,200]中的哈希码将被路由到节点2

实施

下面是一个简单的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中介绍了它们,以下是快捷方式:

玩得开心:)