一致性Hash算法的理解

最近在使用集团内部的TimeTunnel时,想到了中间件的订阅调度策略,可能用到一致性Hash技术,所以查阅了网上其相关的博客与资料,顺便说说自己的一些粗浅理解。
1. 应用场景
     如果从十几年前的文章标题”Consistent Hashing and Random Trees“中,可以看出一致性Hash算法的最初提出,是为了解决Web服务页面访问的Cache热点问题而引入的。其实一致性Hash算法已经广泛地应用在现在的web服务中,例如:
 a. 外层CDN架构里的Nginx代理服务: 全球各地的用户外部访问打到后端的哪台缓存服务节点上进行请求?
   b. 中间层用到的些消息中间件时: 同一个Topic的消息通常会被划分成多个Partition存在多个节点上,同时又被下游多个消费者Consumer所订阅消费,那同一个Topic的多个Partition节点如何分配给多个Consumer去消费?
   c. 内层的Redis缓存服务器: 多个的缓存数据节点如何被分摊调度服务给所有不同数据的请求?

个人总结:通常在有状态的集群服务里,在满足具有Partition与Replication特征的同时,就必须得面对着多对多的分配调度策略问题;而在解决分配调度策略问题时,就可能会需要用到一致性Hash算法。

2. 动态分配调度策略特点
     当面对外部用户访问时, 数据热点需求是在实时变化的; 当而面对内部服务器运营时,集群机器节点的坏掉与扩容也是随时存在的。所以直觉上我们希望调度策略具有较强的弹性,在面对任何的变化时: 
    a. 数据分布的单调性
       当发生数据节点变动时,对于相同的数据始终映射到相同的缓冲节点中或者新增加的缓冲节点中,这样可以避免缓冲节点的数据被击穿。
    b. 数据分布的稳定性
        当出现节点坏掉或热点访问而需要动态扩容时,尽量减少数据的移动,在最坏情况下有可能出现所有缓存节点被击穿而溯源。
    c. 数据分布的均衡性
        尽量保证所有被访问节点中的缓存数据均匀分布,被充分利用,这样保证资源最大利用率。

3. 传统Hash映射缺点
     在传统数据映射关系中,会用通用的式子:hashKey(Data) % DataNodeCount。从式子可以看出数据映射结果是强依赖于缓存数据总的节点个数的,当数据节点总数发生变化时,所有Data映射的结果分布可能会发生全局的变动,难以满足上述的数据分布的单调性和稳定性的特征,例如:hash(object)%12,当添加或删除服务器时,公式就会变成hash(object) % 11 或者hash(object) % 13,几乎所有的对象都会受影响。

4. 一致性Hash算法
     我们希望一种算法能够解决由于少量数据节点的更新,避免出现数据节点全局”震荡“的现象。
 4.1 原理
     其实在实际应用场景里面,对于一个给定的Key,通常我们不用去直接关心它映射到哪些数据机器上,由于数据节点可能动态变化的;但是我们可以限制每台机器服务的Key值范围,这样可以保证:当服务机器数放生变化时,只会影响一个局部Key值区间的数据分布,而不至于影响全局数据。
     一致性Hash算法其大致思路是:将数据分布与机器节点分布尽量按同一种Hash函数映射到指定的数值区间上。这样我们可以把待访问的数据分布与数据机器的分布,两者易变的因子之间通过”稳定的Hash范围值区间“这个中介来进行解耦,降低相互依赖关系,当它们按各自的维度变化时。
 4.2 映射关系

a. 数据节点映射关系

public void add(T node) {
      circle.put(hashFunction.hash(node.toString()), node);
}

b. 数据映射关系

public T get(Object key) {
   if (circle.isEmpty()) {
    return null;
  }
  int hash = hashFunction.hash(key);
  if (!circle.containsKey(hash)) {
    SortedMap<Integer, T> tailMap =
    circle.tailMap(hash);
    hash = tailMap.isEmpty() ?
    circle.firstKey() : tailMap.firstKey();
  }
  return circle.get(hash);
} 

类似通过就近原则,数据通过Hash始终找离它最近Hash值的数据服务器节点。例如,我们把所有数据节点划分为固定的12等分,假设你顺时针最近的数字的服务器挂掉了,就继续顺时针找下一个服务器。当有一台服务器挂掉的时候只有大约1/12的对象受到影响;当需要扩容添加服务器时,受到影响的对象也只有添加服务器逆时针到最近的服务器之间的对象受到影响。

c. 虚拟节点
     通过上面的映射关系仅仅只能满足数据分布的单调与稳定性特征。由于Hash值是不均衡的,没法保证所有数据节点均衡散落在Hash的所有区间范围内,没法满足数据分布的均衡性特征。
     我们把每个物理的数据节点服务器replica成多份通过相同的Hash映射到Hash各个区间范围,尽量保证所有数据节点服务器相互参插散落在各个Hash区间格子上。各个数据节点服务器上的数据分布标差与 replica的个数盗用关系图如下:  
     数据节点的映射关系变成如下:

public void add(T node) {
  for (int i = 0; i < numberOfReplicas; i++) {
    circle.put(hashFunction.hash(node.toString() + i), node);
  }
}

5. 源码

参考www.tom-e-white.com源码如下:

import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;

public class ConsistentHash<T> {

  private final HashFunction hashFunction;
  private final int numberOfReplicas;
  private final SortedMap<Integer, T> circle =
    new TreeMap<Integer, T>();

  public ConsistentHash(HashFunction hashFunction,
    int numberOfReplicas, Collection<T> 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<Integer, T> tailMap =
        circle.tailMap(hash);
      hash = tailMap.isEmpty() ?
             circle.firstKey() : tailMap.firstKey();
    }
    return circle.get(hash);
  } 

}

参考:
 1. http://blog.csdn.net/cywosp/article/details/23397179
   2. https://www.akamai.com/es/es/multimedia/documents/technical-publication/consistent-hashing-and-random-trees-distributed-caching-protocols-for-relieving-hot-spots-on-the-world-wide-web-technical-publication.pdf
   3. http://www.tom-e-white.com//2007/11/consistent-hashing.html
   4. http://www.martinbroadhurst.com/Consistent-Hash-Ring.html

时间: 2024-10-31 09:38:50

一致性Hash算法的理解的相关文章

分布式缓存一致性hash算法理解

今天阅读了一下大型网络技术架构这本苏中的分布式缓存一致性hash算法这一节,针对大型分布式系统来说,缓存在该系统中必不可少,分布式集群环境中,会出现添加缓存节点的需求,这样需要保障缓存服务器中对缓存的命中率,就有很大的要求了: 采用普通方法,将key值进行取hash后对分布式缓存机器数目进行取余,以集群3台分布式缓存为例子: 对于数据进行取hash值然后对3其进行取余,余数为0则进入node 0,余数位1则进入node1,余数位2则进入node2. 如果增加一个节点则对4进行取余,则会将node

分布式memcached学习(四)&mdash;&mdash; 一致性hash算法原理

    分布式一致性hash算法简介 当你看到"分布式一致性hash算法"这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几个概念. 分布式 分布式(distributed)是指在多台不同的服务器中部署不同的服务模块,通过远程调用协同工作,对外提供服务. 以一个航班订票系统为例,这个航班订票系统有航班预定.网上值机.旅客信息管理.订单管理.运价计算等服务模块.现在要以集中式(集群,cluster)和分布

分布式算法(一致性Hash算法)

一.分布式算法 在做服务器负载均衡时候可供选择的负载均衡的算法有很多,包括: 轮循算法(Round Robin).哈希算法(HASH).最少连接算法(Least Connection).响应速度算法(Response Time).加权法(Weighted )等.其中哈希算法是最为常用的算法. 典型的应用场景是: 有N台服务器提供缓存服务,需要对服务器进行负载均衡,将请求平均分发到每台服务器上,每台机器负责1/N的服务. 常用的算法是对hash结果取余数 (hash() mod N ):对机器编号

对一致性Hash算法,Java代码实现的深入研究

一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法和一致性Hash算法的算法原理做了详细的解读. 算法的具体原理这里再次贴上: 先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0, 232-1]),接着在

OpenStack_Swift源代码分析——Ring基本原理及一致性Hash算法

1.Ring的基本概念 Ring是swfit中最重要的组件.用于记录存储对象与物理位置之间的映射关系,当用户须要对Account.Container.Object操作时,就须要查询相应的Ring文件(Account.Container.Object都有自己相应的Ring),Ring 使用Region(近期几个版本号中新增加的).Zone.Device.Partition和Replica来维护这些信息,对于每个对象,依据你在部署swift设置的Replica数量,集群中会存有Replica个对象.

【转载】对一致性Hash算法,Java代码实现的深入研究

原文地址:http://www.cnblogs.com/xrq730/p/5186728.html 一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性Hash算法的算法原理做了详细的解读. 算法的具体原理这里再次贴上: 先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这

OpenStack_Swift源码分析——Ring基本原理及一致性Hash算法

1.Ring的基本概念 Ring是swfit中最重要的组件,用于记录存储对象与物理位置之间的映射关系,当用户需要对Account.Container.Object操作时,就需要查询对应的Ring文件(Account.Container.Object都有自己对应的Ring),Ring 使用Region(最近几个版本中新加入的).Zone.Device.Partition和Replica来维护这些信息,对于每一个对象,根据你在部署swift设置的Replica数量,集群中会存有Replica个对象.

jedis中的一致性hash算法

[http://my.oschina.net/u/866190/blog/192286] jredis是redis的java客户端,通过sharde实现负载路由,一直很好奇jredis的sharde如何实现,翻开jredis源码研究了一番,所谓sharde其实就是一致性hash算法.其实,通过其源码可以看出一致性hash算法实现还是比较简单的.主要实现类是redis.clients.util.Sharded<R, S>,关键的地方添加了注释: 1 2 3 4 5 6 7 8 9 10 11 1

集群扩容的常规解决:一致性hash算法

写这篇博客是因为之前面试的一个问题: 如果memcached集群需要增加机器或者减少机器,那么其他机器上的数据怎么办? 最后了解到使用一致性hash算法可以解决,下面一起来学习下吧. 声明与致谢: 本文转载于朱双印博主的个人日志<白话解析:一致性哈希算法 consistent hashing>一文. 一. 引子 在了解一致性哈希算法之前,最好先了解一下缓存中的一个应用场景,了解了这个应用场景之后,再来理解一致性哈希算法,就容易多了,也更能体现出一致性哈希算法的优点,那么,我们先来描述一下这个经