我应该如何使用Guava的Hashing#consistentHash?

时间:2012-09-07 13:55:50

标签: java guava consistent-hashing

我正在研究在我正在编写的一些java代码中使用consistent hash算法。番石榴哈希库有consistentHash(HashCode, int)方法,但the documentation相当缺乏。我最初的希望是,我可以使用consistentHash()来实现简单的会话关联,从而有效地在一组后端服务器上分配负载。

有没有人有如何使用此方法的真实示例?特别是我关心的是管理从目标范围中移除铲斗。

例如:

@Test
public void testConsistentHash() {
    List<String> servers = Lists.newArrayList("server1", "server2", "server3", "server4", "server5");

    int bucket = Hashing.consistentHash(Hashing.md5().hashString("someId"), servers.size());
    System.out.println("First time routed to: " + servers.get(bucket));

    // one of the back end servers is removed from the (middle of the) pool
    servers.remove(1);

    bucket = Hashing.consistentHash(Hashing.md5().hashString("blah"), servers.size());
    System.out.println("Second time routed to: " + servers.get(bucket));
}

导致输出:

First time routed to: server4
Second time routed to: server5

我想要的是在清除列表中较早的服务器之后将该标识符(“someId”)映射到同一服务器。所以在上面的示例中,删除后我想我想要桶0映射到“server1”,桶1映射到“server3”,桶2映射到“server4”,桶3映射到“server5”。

我是否应该维护一个单独的(比列表更复杂)数据结构来管理存储桶删除和添加?我想我可能已经设想了一个更复杂的Hashing API,可以在为我添加和删除特定存储桶后管理重新映射。

注意:我知道示例代码使用的是小输入和存储区集。我尝试了100个桶中的1000个输入,结果是一样的。当我将buckets更改为99并且存储桶99分布在剩余的99个存储桶中时,映射到存储桶0-98的输入保持不变。

4 个答案:

答案 0 :(得分:4)

我认为目前没有一个好方法可以做到这一点。当前形式的consistentHash仅在简单的情况下有用 - 基本上,你有一个旋钮来增加或减少服务器的数量......但总是通过在末尾添加和删除。

正在进行一些工作来添加这样的课程:

public final class WeightedConsistentHash<B, I> {
  /** Initially, all buckets have weight zero. */
  public static <B, I> WeightedConsistentHash<B, I> create(
      Funnel<B> bucketFunnel, Funnel<I> inputFunnel);

  /**
   * Sets the weight of bucket "bucketId" to "weight".
   * Requires "weight" >= 0.0.
   */
  public void setBucketWeight(B bucketId, double weight);

  /**
   * Returns the bucket id that "input" maps to.
   * Requires that at least one bucket has a non-zero weight.
   */
  public B hash(I input);
}

然后你会写:

WeightedConsistentHash<String, String> serverChooser =
    WeightedConsistentHash.create(stringFunnel(), stringFunnel());
serverChooser.setBucketWeight("server1", 1);
serverChooser.setBucketWeight("server2", 1);
// etc.

System.out.println("First time routed to: " + serverChooser.hash("someId"));

// one of the back end servers is removed from the (middle of the) pool
serverChooser.setBucketWeight("server2", 0);

System.out.println("Second time routed to: " + serverChooser.hash("someId"));

每次都应该获得相同的服务器。该API看起来合适吗?

答案 1 :(得分:3)

我担心没有数据结构可以用当前consistentHash做到这一点。由于该方法仅接受列表大小,因此可以支持从末尾添加和删除。目前,最好的解决方案可能包括替换

servers.remove(n)

通过

server.set(n, servers.get(servers.size() - 1);
servers.remove(servers.size() - 1);

这样就可以交换失败的服务器和最后一台服务器。这看起来很糟糕,因为它使两个交换服务器的分配错误。这个问题只有其中一个失败的一半。但这是有道理的,因为在删除最后一个列表元素之后,一切都很好,除了对失败的服务器和之前的最后一个服务器的分配。

所需的分配数量增加了两倍。不是最佳的,但希望可用吗?

答案 2 :(得分:2)

guava API不了解您的服务器列表。它只能保证这一点:

int bucket1 = Hashing.consistentHash(Hashing.md5().hashString("server1"),N);    
int bucket2 = Hashing.consistentHash(Hashing.md5().hashString("server1"),N-1);

assertThat(bucket1,is(equalTo(bucket2))); iff bucket1==bucket2!=N-1 

您需要自己将存储桶管理到服务器列表

答案 3 :(得分:0)

问题中提出的答案是正确的:

  

我是否应该维护一个单独的(比列表更复杂)的数据结构来管理存储桶的删除和添加?

Guava正在散列成具有序数的环。从这些序号到服务器ID的映射必须在外部维护:

  • 最初给N台服务器-每个序号0..N-1到服务器ID A..K( 0 -> A 1 -> B ,.., N-1 -> K )。还需要从服务器ID到其序号的反向映射( A -> 0 B -> 1 ,..)。

  • 在删除服务器时-序数空间缩小1。需要将已删除服务器的所有以序号开头的序号重新映射到下一个服务器(移位一)。

    因此,例如,在初始映射之后,说服务器C(对应于序号2)被删除。现在新的映射变为:( 0 -> A 1 -> B 2- > D 3 -> E ,.., N-2 -> K

  • 在添加服务器L(例如,从N到N + 1个服务器)时,可以从N-> L添加新的映射。

我们在这里所做的是模仿节点在添加和删除时如何成环运动。尽管节点的顺序保持不变,但它们的序号(Guava运行所在的序号)可以随着节点的进出而改变。