引入

在业务开发中,我们常把数据持久化到数据库中。如果需要读取这些数据,除了直接从数据库中读取外,为了减轻数据库的访问压力以及提高访问速度,我们更多地引入缓存来对数据进行存取。读取数据的过程一般为:


图1:加入缓存的数据读取过程

对于分布式缓存,不同机器上存储不同对象的数据。为了实现这些缓存机器的负载均衡,可以使用式子1来定位对象缓存的存储机器:

m = hash(o) mod n ——式子1

其中,o为对象的名称,n为机器的数量,m为机器的编号,hash为一hash函数。图2中的负载均衡器(load balancer)正是使用式子1来将客户端对不同对象的请求分派到不同的机器上执行,例如,对于对象o,经过式子1的计算,得到m的值为3,那么所有对对象o的读取和存储的请求都被发往机器3执行。


图2:如何利用Hash取模实现负载均衡

式子1在大部分时候都可以工作得很好,然而,当机器需要扩容或者机器出现宕机的情况下,事情就比较棘手了。

当机器扩容,需要增加一台缓存机器时,负载均衡器使用的式子变成:

m = hash(o) mod (n + 1) ——式子2

当机器宕机,机器数量减少一台时,负载均衡器使用的式子变成:

m = hash(o) mod (n - 1) ——式子3

我们以机器扩容的情况为例,说明简单的取模方法会导致什么问题。假设机器由3台变成4台,对象o1由式子1计算得到的m值为2,由式子2计算得到的m值却可能为0,1,2,3(一个 3t + 2的整数对4取模,其值可能为0,1,2,3,读者可以自行验证),大约有75%(3/4)的可能性出现缓存访问不命中的现象。随着机器集群规模的扩大,这个比例线性上升。当99台机器再加入1台机器时,不命中的概率是99%(99/100)。这样的结果显然是不能接受的,因为这会导致数据库访问的压力陡增,严重情况,还可能导致数据库宕机。

一致性hash算法正是为了解决此类问题的方法,它可以保证当机器增加或者减少时,对缓存访问命中的概率影响减至很小。下面我们来详细说一下一致性hash算法的具体过程。

一致性Hash环

一致性hash算法通过一个叫作一致性hash环的数据结构实现。这个环的起点是0,终点是2^32 - 1,并且起点与终点连接,环的中间的整数按逆时针分布,故这个环的整数分布范围是[0, 2^32-1],如下图3所示:


图3:一致性Hash环

将对象放置到Hash环

假设现在我们有4个对象,分别为o1,o2,o3,o4,使用hash函数计算这4个对象的hash值(范围为0 ~ 2^32-1):

hash(o1) = m1
hash(o2) = m2
hash(o3) = m3
hash(o4) = m4

把m1,m2,m3,m4这4个值放置到hash环上,得到如下图4:


图4:放置了对象的一致性Hash环

将机器放置到Hash环

使用同样的hash函数,我们将机器也放置到hash环上。假设我们有三台缓存机器,分别为 c1,c2,c3,使用hash函数计算这3台机器的hash值:

hash(c1) = t1
hash(c2) = t2
hash(c3) = t3

把t1,t2,t3 这3个值放置到hash环上,得到如下图5:


图5:放置了机器的一致性Hash环

为对象选择机器

将对象和机器都放置到同一个hash环后,在hash环上顺时针查找距离这个对象的hash值最近的机器,即是这个对象所属的机器。
例如,对于对象o2,顺序针找到最近的机器是c1,故机器c1会缓存对象o2。而机器c2则缓存o3,o4,机器c3则缓存对象o1。


图6:在一致性Hash环上为对象选择机器

处理机器增减的情况

对于线上的业务,增加或者减少一台机器的部署是常有的事情。
例如,增加机器c4的部署并将机器c4加入到hash环的机器c3与c2之间。这时,只有机器c3与c4之间的对象需要重新分配新的机器。对于我们的例子,只有对象o4被重新分配到了c4,其他对象仍在原有机器上。如图7所示:


图7:增加机器后的一致性Hash环的结构

如上文前面所述,使用简单的求模方法,当新添加机器后会导致大部分缓存失效的情况,使用一致性hash算法后这种情况则会得到大大的改善。前面提到3台机器变成4台机器后,缓存命中率只有25%(不命中率75%)。而使用一致性hash算法,理想情况下缓存命中率则有75%,而且,随着机器规模的增加,命中率会进一步提高,99台机器增加一台后,命中率达到99%,这大大减轻了增加缓存机器带来的数据库访问的压力。

再例如,将机器c1下线(当然,也有可能是机器c1宕机),这时,只有原有被分配到机器c1对象需要被重新分配到新的机器。对于我们的例子,只有对象o2被重新分配到机器c3,其他对象仍在原有机器上。如图8所示:


图8:减少机器后的一致性Hash环的结构

虚拟节点

上面提到的过程基本上就是一致性hash的基本原理了,不过还有一个小小的问题。新加入的机器c4只分担了机器c2的负载,机器c1与c3的负载并没有因为机器c4的加入而减少负载压力。如果4台机器的性能是一样的,那么这种结果并不是我们想要的。
为此,我们引入虚拟节点来解决负载不均衡的问题。
将每台物理机器虚拟为一组虚拟机器,将虚拟机器放置到hash环上,如果需要确定对象的机器,先确定对象的虚拟机器,再由虚拟机器确定物理机器。
说得有点复杂,其实过程也很简单。

还是使用上面的例子,假如开始时存在缓存机器c1,c2,c3,对于每个缓存机器,都有3个虚拟节点对应,其一致性hash环结构如图9所示:


图9:机器c1,c2,c3的一致性Hash环结构

假设对于对象o1,其对应的虚拟节点为c11,而虚拟节点c11对象缓存机器c1,故对象o1被分配到机器c1中。

新加入缓存机器c4,其对应的虚拟节点为c41,c42,c43,将这三个虚拟节点添加到hash环中,得到的hash环结构如图10所示:


图10:机器c1,c2,c3,c4的一致性Hash环结构

新加入的缓存机器c4对应一组虚拟节点c41,c42,c43,加入到hash环后,影响的虚拟节点包括c31,c22,c11(顺时针查找到第一个节点),而这3个虚拟节点分别对应机器c3,c2,c1。即新加入的一台机器,同时影响到原有的3台机器。理想情况下,新加入的机器平等地分担了原有机器的负载,这正是虚拟节点带来的好处。而且新加入机器c4后,只影响25%(1/4)对象分配,也就是说,命中率仍然有75%,这跟没有使用虚拟节点的一致性hash算法得到的结果是相同的。

总结

一致性hash算法解决了分布式环境下机器增加或者减少时,简单的取模运算无法获取较高命中率的问题。通过虚拟节点的使用,一致性hash算法可以均匀分担机器的负载,使得这一算法更具现实的意义。正因如此,一致性hash算法被广泛应用于分布式系统中。

参考资料

    1. https://en.wikipedia.org/wiki/Consistent_hashing

    2. https://www.codeproject.com/articles/56138/consistent-hashing

    3. 《大型网站技术架构——核心原理与安全分析》,李智慧著,电子工业出版社

注:转载自 https://leehao.me/

一致性Hash(Consistent Hashing)原理剖析的更多相关文章

  1. Consistent Hashing原理与实现

    原理介绍: consistent hashing原理介绍来自博客:http://blog.csdn.net/sparkliang/article/details/5279393, 多谢博主的分享 co ...

  2. 分方式缓存常用的一致性hash是什么原理

    分方式缓存常用的一致性hash是什么原理 一致性hash是用来解决什么问题的?先看一个场景有n个cache服务器,一个对象object映射到哪个cache上呢?可以采用通用方法计算object的has ...

  3. 一致性Hash算法的原理与实现(分布式映射算法)

    一致性Hash算法解决的问题: 解决分布式系统中的负载均衡问题 背景问题:有N台服务器提供缓存服务,需要对服务器进行负载均衡,将请求平均发到每台服务器上,每台服务器负载1/N的服务 硬Hash映射:将 ...

  4. 分布式一致性算法之Paxos原理剖析

    概述 Zookeeper集群中,只有一个节点是leader节点,其它节点都是follower节点(实际上还有observer节点,不参与选举投票,在这里我们先忽略,下同).所有更新操作,必须经过lea ...

  5. 探索c#之一致性Hash详解

    阅读目录: 使用场景 算法原理 虚拟节点 代码示例 使用场景 以Redis为例,当系统需要缓存的内容超过单机内存大小时,例如要缓存100G数据,单机内存仅有16G时.这时候就需要考虑进行缓存数据分片, ...

  6. 【转】分布式存储和一致性hash

    本文我将对一致性算法作介绍,同时谈谈自己对一致性hash和一般意义上的hash算法的区别 hash是什么 hash即hash算法,又称为散列算法,百度百科的定义是 哈希算法将任意长度的二进制值映射为较 ...

  7. 一致性Hash的原理与实现

    应用场景 在了解一致性Hash之前,我们先了解一下一致性Hash适用于什么场景,能解决什么问题?这里先放一下我自己认为适用的场景.一致性Hash适用于服务器动态扩展且需要负载均衡的场景 试想以下场景, ...

  8. Consistent Hashing算法-搜索/负载均衡

    在做服务器负载均衡时候可供选择的负载均衡的算法有很多,包括:  轮循算法(Round Robin).哈希算法(HASH).最少连接算法(Least Connection).响应速度算法(Respons ...

  9. 提取jedis源码的一致性hash代码作为通用工具类

    一致性Hash热点 一致性Hash算法是来解决热点问题,如果虚拟节点设置过小热点问题仍旧存在. 关于一致性Hash算法的原理我就不说了,网上有很多人提供自己编写的一致性Hash算法的代码示例,我在跑网 ...

随机推荐

  1. StreamSets学习系列之StreamSets支持多种安装方式【Core Tarball、Cloudera Parcel 、Full Tarball 、Full RPM 、Docker Image和Source Code 】(图文详解)

    不多说,直接上干货! Streamsets的官网 https://streamsets.com/ 得到 https://streamsets.com/opensource/ StreamSets支持多 ...

  2. php交叉合并数组

    如数组 array1 = array('a', 'b', 'c');     array2 = array('1', '2', '3'); 希望能实现结果 array3 = array('a', '1 ...

  3. Go的基本类型与变量

    基本类型 布尔型:bool 长度:1字节 取值范围:true,false 注意:不可以用数字代表true或false 整型:int/uint 根据运行平台可能为32或64位 8位整型:int8/uin ...

  4. Log4j和Log4j2的区别

    Log4j是Apache的一个开源项目,我们不去考究它的起源时间,但是据我了解,log4j 1已经不再更新了. 下面我就以列举的方式来浅谈log4j和log4j 2的不同之处. 一.配置文件类型 lo ...

  5. SSM整合Shiro 身份验证及密码加密简单实现

    1.导入maven的相关依赖 <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId&g ...

  6. 一个div中有一个a标签,点击a标签的时候不进入div的点击事件

    //阻止冒泡上层clickevent.stopPropagation();

  7. Fibonacci(矩阵)

    Fibonacci Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u   Descrip ...

  8. Java - "JUC" ReentrantReadWriteLock

    Java多线程系列--“JUC锁”08之 共享锁和ReentrantReadWriteLock ReadWriteLock 和 ReentrantReadWriteLock介绍 ReadWriteLo ...

  9. php静态变量与方法与phar的使用

    本节用类与静态变量改造之前的例子:php根据命令行参数生成配置文件 ghostinit.php: <?php class ghostinit{ static $version = 'ghost ...

  10. Java 基础:变量 与 字符串

    变量 Java中没有初始化的变量是不能直接使用的 局部变量 String msg; System.out.print(msg); 就会提示错误,我们必须显式的为变量指定一个初值如null.刚开始学Ja ...