一致性hash 之 [翻译]Consistent Hash By Tom White

http://ptsolmyr.com/2010/07/30/consistent_hash_by_tom_white 

http://sandaobusi.iteye.com/blog/964368   推荐 java例子

http://martinbroadhurst.com/Consistent-Hash-Ring.html   推荐C++实现

http://www.yeeach.com/2009/10/02/consistent-hashing%E7%AE%97%E6%B3%95/

 

  

 

Tom White是<Hadoop: The Definitive Guide>的作者,目前在云计算大牛公司cloudera就职。本篇博客是原文翻译了Tom White的一篇介绍一致性哈希的文章,本人英语,汉语都很悲剧,有不当的地方尽可拍砖。

原文 link: http://www.lexemetech.com/2007/11/consistent-hashing.html

—————————————————–

我最近一段时间在研究 consistent hash。介绍它的paper(Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web by David Karger et al) 十年前就出现了,不过直到最近才悄悄的有越来越多的service开始使用consistent hash,这些service包括Amazon’s Dynamo,以及memcached (向Last.fm敬礼)。那么到底什么是consistent hash呢?大家为什么要关注它呢?

consistent hash的需求来自于运行一个cache集群(例如web cache)时遇到的一些限制。如果你拥有一个由n台cache机器组成的集群,那么最普通的load balance方式就是把进来的对象o放在编号为hash(o) mod n的那一台上。你会觉得这个方案简介优美,直到有一天,由于种种原因你不得不增加或者移除一些cache机器,这时,集群的机器数目n变了,每个对象都被hash求余到了新的机器。这将是一场灾难,因为真正存放内容的server会被来自于cache集群的request拖垮。这时整个系统看起来就像没有cache一样。这就是大家为什么关心consistent hash,因为大家需要使用它来避免系统被拖垮。

情况如果是这样的就好了:当集群添加了一台cache机器,该机器只从其他cache机器中读取应得的那些对象;相应的,当一个cache机器从集群中移除,最好是它cache住的对象被分配给其他的cache机器(而没有更多的数据移动)。这种理想的情境就是consistent hash所追求并实现的:如果可能的话,始终将同一组对象分配给相同的机器。

consistent hash算法背后最基础的思想就是:对object和cache machine使用相同的hash函数。这样做的好处是能够把cache机器映射到一段interval上,而这段interval就会包含一定数目的对象的hash值。如果某台cache机器被移除了,那么它映射到的interval被和它相邻的一个cache机器托管,其他所有的cache机器都不用变。

描述

让我们来更深入的来了解一下consistent hash。Hash的作用就是把object和cache映射到一个数值范围上。Java程序员对hash应该很熟悉了–每个对象的hashCode方法会返回一个在[-231, 231-1]的int型整数。我们把这个数值范围首尾相接的映射到一个环上。下图描述了一组object(1, 2, 3, 4)和一组cache(A, B, C)分别映射在Hash环上。(图片源于Web Caching with Consistent Hashing by David Karger et al)

图1

要确定某个object会缓存在哪个cache,我们从这个object开始顺时针前进,知道我们遇到一个cache点。这样,从上图的例子我们看到object 1和4 归cache A,object 2归cache B,而cache C缓存的事object 3。考虑一下,当cache C被移除了,会发生什么?在这种情况下,object 3被cache A缓存住,所有其他object都不用移动。如果如图2,cache集群添加了cache D,那么D会缓存object 3和4,把object 1留给A。

图2

一切都很好,除了一点:指派给每个cache的间距大小太随机了,这样就会object的分配也极度的不均匀。 为了解决这个问题,我们引入”virtual nodes”这个概念:即每个cache在hash环上有多个副本,也就是说,每当我们加入一个cache,在环上都会为这个cache增加多个点。

我下面的代码做了一个仿真实验,将10,000个object存入10个cache,你会在下面的plot图中看到virtual nodes的影响。x轴上是每个cache的副本数(对数刻度)。当x值较小时,我们看到objects在caches中的分布是不平衡的(y轴是以百分比形式表示objects在caches中分布的标准差)。随着cache的replica的增加,objects的分布趋向于更加平衡。这个实验说明了每个cache大概100-200的replica能够使object的分布较为平衡(标准差在5%-10%)

experiment result

实现

下面是使用Java的一个简单实现。要使consistent hash的效果明显,很重要的一点是使用一个mix的很好的hash函数。Java中object的hashCode方法的大多数实现都没有提供很好的mix性能。所以我们提供一个HashFunction接口,以便于定制使用的hash函数。在这里推荐MD5.

import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;
 
public class ConsistentHash {
 
  
private final HashFunction hashFunction;
  
private final int numberOfReplicas;
  
private final SortedMap circle =
    
new TreeMap();
 
  
public ConsistentHash(HashFunction hashFunction,
    
int numberOfReplicas, Collection nodes) {
 
    
this.hashFunction = hashFunction;
    
this.numberOfReplicas = numberOfReplicas;
 
    
for (T node : nodes) {
      add(node);
    }
  }
 
  
public void add(T node) {
    
for (int i = 0; i < numberOfReplicas; i++) {
      circle.put(hashFunction.hash(node.toString() 
+ i),
        node);
    }
  }
 
  
public void remove(T node) {
    
for (int i = 0; i < numberOfReplicas; i++) {
      circle.remove(hashFunction.hash(node.toString() 
+ i));
    }
  }
 
  
public T get(Object key) {
    
if (circle.isEmpty()) {
      
return null;
    }
    
int hash = hashFunction.hash(key);
    
if (!circle.containsKey(hash)) {
      SortedMap tailMap 
=
        circle.tailMap(hash);
      hash 
= tailMap.isEmpty() ?
             circle.firstKey() : tailMap.firstKey();
    }
    
return circle.get(hash);
  } 
 

} 

上面的代码用一个integer的sorted map来表示hash circle。当ConsistentHash创建时,每个node都被添加到circle map中(添加的次数由numberOfReplicas控制)。每个replica的位置,由node的名字加上一个数字后缀所对应的hash值来决定。
要为一个object找到它应该去的node(get方法),我们把object的hash值放入map中查找。大多数情况下,不会恰好有一个node和这个object重合(即使每个node都有一定量的replica,hash的值空间也比node数要多得多),所以用tailMap方法找到map中的下一个key。如果tail map为空,那么我们转一圈,找到circle中的第一个key。

使用

那么你应该如何使用consistent hash呢?一般情况下,你可以使用一些library,而不是自己去写代码。例如上面提到的memcached–一个分布式的内存cache系统,现在已经有了支持consisitent hash的client。由Last.fm的Richard Jones实现的ketama是第一个,现在是有Dustin Sallings贡献的Java实现。很有趣的是只有客户端需要实现consisitent hash算法,server端的代码不需要任何改变。其他使用consisitent hash的系统有Chord,一个分布式hash表的实现,和Amazon的Dynamo,一个key-value存储系统。(没有开源) 

posted on 2011-08-25 18:28  阿笨猫  阅读(1139)  评论(0编辑  收藏  举报