白话分布式系统中的一致性哈希算法

本文首发于:白话分布式系统中的一致性哈希算法
微信公众号:后端技术指南针
持续输出干货 欢迎关注!前端

经过本文将了解到如下内容:node

  1. 分布式系统的概念和做用
  2. 分布式系统经常使用负责均衡策略
  3. 普通哈希取模策略优缺点
  4. 一致性哈希算法的定义和思想
  5. 一致性哈希的基本过程
  6. Redis集群中一致性哈希的实现

1.分布式系统的基本概念算法

  • 分布式系统与高并发高可用

当今高并发和海量数据处理等场景愈来愈多,实现服务应用的高可用、易扩展、短延时等成为必然。后端

在此状况下分布式系统应运而生,互联网的场景无外乎存储和计算,所以分布式系统能够简单地分为:缓存

  1. 分布式存储
  2. 分布式计算

所谓分布式系统就是一批计算机组合起来共同对外提供服务,对于用户来讲具体有多少规模的计算机完成了此次请求,彻底是无感知的。服务器

分布式系统中的计算机越多,意味着计算和存储资源等也就越多,可以处理的并发访问量也就越大,响应速度也越快。微信

如图为简单总体架构图:网络

  1. 大前端 主要实现了服务应用对应的全部流量的接入,好比xyz域名下可能有N个子服务,这一层涉及不少网络流量的处理,也颇有挑战,像百度的BFE(百度统一前端)接入了百度的大部分流量,每日转发1万亿次,峰值QPS1000w。
  2. 中间层 完成了各个服务的调度和分发,粒度相比大前端接入层更细致一些,这一层实现了用户的无感知体验,能够简单理解为反向代理层。
  3. 业务层 完成了数据存储、数据计算、数据缓存等,各个业务环节高度解耦,而且基于集群化来实现。
  • 集群和分布式的区别与联系

集群是从原来的单机演变来的,单台机器扛不住就加机器,直到服务负载、稳定性、延时等指标都知足,架构

集群中的N台机器上部署同样的程序,就像一台机器被复制多份同样,这种形式就是集群化。并发

分布式是将一个完整的系统,按照业务功能拆分红一个个独立的子系统,这些服务之间使用更高效的通讯协议好比RPC来完成调度,

各个子服务就像在一台机器上同样,实现了业务解耦,同时提升了并发能力确实不赖。

一个大的分布式系统能够理解拆分以后的子服务使用集群化,一个个子服务之间使用相似于RPC的协议串联,组成一个庞大的存储和计算网络。

如图为简单的分布式系统结构:

注:图片来自网络 详见参考1

2.分布式系统的分发

  • 经常使用负载均衡策略

以分布式系统中的存储和缓存为例,若是存储集群中有5台机器,若是这时有请求,就须要考虑从哪台机器获取数据,通常有几种方法:

  1. 随机访问
  2. 轮询策略
  3. 权重轮询策略
  4. Hash取模策略
  5. 一致性哈希策略

各类方法都有各自的优缺点:

随机访问可能形成服务器负载压力不均衡;轮询策略请求均匀分配,但当服务器有性能差别,没法按性能分发;

权值须要静态配置,没法自动调节;哈希取模若是机器动态变化会致使路由产生变化,数据产生大量迁移。

  • Hash取模策略

Hash取模策略是其中经常使用的一种作法,它能够保证相同请求相同机器处理,这是一种并行转串行的方法,工程中很是常见。

笔者在刚毕业作流量分析时就是按照报文的五元组信息作key来决定服务内的业务线程id,这样确保相同的TCP/HTTP链接被相同的线程处理,

避免了线程间的通讯和同步,实现了无锁化处理,因此仍是颇有用的。

1 index = hash_fun(key) % N

从上面的普通hash取模公式能够看到,若是N不变或者能够本身主动控制,就能够实现数据的负载均衡和无锁化处理,可是一旦N的变化不被控制,那么就会出现问题。

  • Hash取模的弊端

阶段一:

目前有N=4台机器S1-S4,请求拼接key经过hash函数%N,获取指定的机器序号,并将请求转发至该机器。

阶段二:

S3机器由于磁盘故障而宕机,这时代理层得到故障报警调整N=3,这时就出现了问题,若是做为缓存使用,

S3机器上的全部key都将出现CacheMiss,原来存放在S1的key=abc使用新的N,被调整到S4,

这样abc也出现CacheMiss,由于在S4上找不到abc的数据,这种场景就是牵一发而动全身,在缓存场景中会形成缓存击穿,若是量很大会形成缓存雪崩。

阶段三:

因为S3宕机了,所以管理员增长了一台机器S5,代理层再次调整N=4,

此时又将出现相似于阶段二的数据迁移,仍然会出现CacheMiss的状况。

3.一致性哈希算法

  • 一致性哈希的定义

In computer science, consistent hashing is a special kind of hashing such that when a hash table is resized,

only K/n keys need to be remapped on average,

where K is the number of keys, and n is the number of slots.

维基百科-Consistent hashing

翻译一下:

一致哈希 是一种特殊的哈希算法。
在使用一致哈希算法后,哈希表槽位数(大小)的改变平均只须要对K/n 个关键字从新映射,其中 K是关键字的数量,n是槽位数量。
在传统的哈希表中,添加或删除一个槽位的几乎须要对全部关键字进行从新映射。维基百科-一致性哈希

从定义能够知道,一致性哈希是一种特殊的哈希算法,区别于哈希取模,这种特殊的哈希算法实现了少许数据的迁移,

避免了几乎所有数据的移动,这样就解决了普通hash取模的动态调整带来的全量数据变更。

  • 解决思路是什么

先不看算法的具体实现,先想一想普通hash取模的问题根源是什么?

  • N的变更

没错!根源就在于N的变更,那么若是N被固定住呢?而且让N很大,那么每次被移动的key数就是K_all/Slot_n,也就是有槽位的概念,或者说是小分片的概念,

直白一点就是鸡蛋放到了不少不少的固定数量的篮子里:

1 Key_all 存储的所有key的数量
2 Slot_n 总的槽位或者分片数
3 Min_Change 为最小移动数量 
4 Min_change = Key_all/Slot_n
5 Min_change 也是数据的最小分片Shard
  • 小分片的归属

这里还有一个问题要解决,将N固定且设置很大以后,数据分片shard变得很是小了,这时就有shard的所属问题,

也就是好比N=100w,此时集群有10台,那么每台机器上理论上平均有10w,固然能够根据机器的性能来作差别化的归属配置,

性能强的多分一些shard,性能差的少分一些shard。

问题到这里,基本上已经守得云开见月明了,不过仍是来看看1997年麻省理工发明的一致性哈希算法原理吧。

  • Karger的一致性哈希算法

一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分相似。一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT能够在P2P环境中真正获得应用。一致性哈希算法

  • 数据在哈希环上的分片

正如咱们前面的思考,Karger的一致性哈希算法将N设置为2^32,造成了一个0~(2^32-1)的哈希环,也就是至关于普通Hash取模时N=2^32。

注:图片来自网络 详见参考2在将数据key进行hash计算时就落在了0~(2^32-1的哈希环上,

若是总的key数量为Sum,那么单个哈希环的最小单位上的key数就是:

1 Unit_keys = Sum/2^32

因为N很是大因此哈希环最小单位的数据量unit_keys小了不少。

  • 服务器节点和哈希环分片的分配

注:图片来自网络 详见参考2

将服务器结点也做为一种key分发到哈希环上:

1 con_hash(ip_key)%2^32

一致性哈希算法使用顺时针方法实现结点对哈希环shard的归属,可是因为服务器结点的数量相比2^32会少很是多,

所以很稀疏,就像宇宙空间中的天体,你觉得天体不少,可是相比浩渺的宇宙仍是空空如也。

实体服务器结点少许相比哈希环分片数据不多,这种特性决定了一致性哈希的数据倾斜,

因为数量少致使服务节点分布不均,形成机器负载失衡,如图所示,服务器1的负载远大于其余机器:

注:图片来自网络 详见参考2

虚拟节点的引入:

这个说白了服务器结点不够,就让服务器的磁盘、内存、CPU全去占位置,

现实生活中也这样:12306出来以前,火车站连夜排队买票,这时什么书包、水杯、眼镜都表明了张3、李4、王二麻子。

一样的道理,将服务器结点根据某种规则来虚拟出更多结点,可是这些虚拟节点就至关于服务器的分身。

好比采用以下规则在ip后缀增长#index,来实现虚拟节点的定位:

1 vnode_A_index = con_hash(ip_key_#A)%2^32
2 vnode_B_index = con_hash(ip_key_#B)%2^32
3 ...
4 vnode_k_index = con_hash(ip_key_#k)%2^32

注:图片来自网络 详见参考2

这是因为引入了虚拟节点,所以虚拟节点的分片都要实际归属到真实的服务节点上,所以在实际中涉及到虚拟节点和实体结点的映射问题。

  • 新增服务器结点

注:图片来自网络 详见参考2

当管理员新增了服务器4时,原来在服务器3和服务器1之间分布的哈希环单元上的数据,将有一部分迁移到服务器4,

固然因为虚拟节点的引入,这部分数据迁移不会很大,并非服务器4和服务器1之间的数据都要顺时针迁移,所以这样就实现了增长机器时,只移动少许数据便可。

  • 删除服务器结点

注:图片来自网络 详见参考2

当服务器结点2发生宕机,此时须要被摘除进行故障转移,原来S2以及其虚拟节点上的数据都将进行顺时针迁移到下一个实体结点或者虚拟结点。

4.Redis的一致性哈希实现

Redis cluster 拥有固定的16384个slot,slot是虚拟的且被分布到各个master中,当key 映射到某个master 负责slot时,就由对应的master为key 提供服务。

每一个Master节点都维护着一个位序列bitmap为16384/8字节,也就是Master使用bitmap的原理来表征slot的下标,

Master 节点经过 bit 来标识哪些槽本身是否拥有,好比对于编号为1的槽,Master只要判断序列的第二位是否是为1便可。

这样就创建了分片和服务结点的所属关系,因此整个过程也是两个原则,符合上文的一致性哈希的思想。

1 hash_slot_index =CRC16(key) mod 16384

注:图片来自网络 详见参考4

5.总结和思考

  • 一致性哈希算法的两个关键点

一致性哈希算法是一种特殊的哈希算法,特殊之处在于将普通哈希取模的N进行固定,从而确保了相同的key必然是相同的位置,从而避免了牵一发而动全身的问题,这是理解一致性哈希的关键。

一致性哈希算法的另一个要点就是将N固定且设置很大以后,实际上就是进行数据分片Sharding,分布的小片就要和实际的机器产生关联关系,也就是哪台机器负责哪些小分片。

可是一致性哈希算法并非从完全解决了因为动态调整服务器数据产生的数据迁移问题,

而是将原来普通哈希取模形成的几乎所有迁移,下降为小部分数据的移动,是一种很是大的优化,在工程上基本上能够知足要求。

一致性哈希算法的关键有两点:

  1. 大量固定数量的小数据块的分片
  2. 小分片的服务器归属问题
  • 一致性哈希算法的其余工程版本

像Redis并无使用2^32这种哈希环,而是采用了16384个固定slot来实现的,而后每一个服务器Master使用bitmap来肯定本身的管辖slot,

管理员能够根据机器的配置和负载状况进行slot的动态调整,基本上解决了最开始的几种负载均衡策略的不足。

因此假如让你设计一个一致性哈希算法,只要把握两个原则便可,并非只有麻省理工Karger的一种哈希算法,它只是提供了一种思想和方向。

  • 天马行空

一直有个疑问问什么要用"一致性哈希算法" 这个名字,让我总和分布式系统中的一致性协议(eg最终一致性)混淆。

英文原文是Consistent hashing,其中Consistent译为"一致的,连贯的",我以为连贯的更贴切一些,

觉得这种特殊的哈希算法实现了普通哈希取模算法的平滑连贯版本,称为连贯性哈希算法,好像更合适,一点愚见,水平有限,看看就完事了。

5.参考资料

6.推荐阅读

白话布隆过滤器BloomFilter
理解缓存系统的三个问题
几种高性能网络模型
二叉树及其四大遍历
理解Redis单线程运行模式
Linux中各类锁及其基本原理
理解Redis持久化
深刻理解IO复用之epoll
深刻理解跳跃链表[一]
理解堆和堆排序
理解堆和优先队列

7.关于本公众号

开号不久做者力争持续输出原创干货,若是文章有帮助到你,

但愿朋友们多多转发和分享,做者会更加有动力推出更好的文章,共同进步。

微信公众号:后端技术指南针

相关文章
相关标签/搜索