哈希与一致性哈希

0. 为什么写本文

有个朋友是做分布式存储的,有一次聊天他问我一些问题:什么是一致性哈希?一般如何实现?有什么优点?

对于这个问题,我的脑海中只是闪现几个词汇:md5、hash函数、哈希环。

在我看来,哈希就是一种算法。一句话概括就是:把无限的数据映射到有限的集合中的一种算法。

朋友说:你这段话很官方,但是等于没说。

哈哈,身为某厂高级开发工程师的我,此刻无地自容。惭愧、惭愧,请允许我做一个悲伤的表情。

1. 哈希

对于哈希,日常开发中在很多场景都会用到,比如:

md5 之类的哈希函数分库、分表时,使用某个字段的 hash 值对固定数值取模,来确定对应库表一个大量数据的集合,根据某个字段作为拆分键,对数据进行打散处理PHP的 HashTable、Go的 map、Python 的 dict 等数据结构实现Redis 分片时使用 crc16 对key进行哈希,然后对 16384 取模来确定分片等等 ......除上面场景,还有很多地方会用到 hash,而他们都是哈希的一种实现方式。

hash函数

1.1 哈希碰撞

无限个原始数据在经过Hash函数运算之后,得到的哈希结果会有一定的概率相同。那么,这些不同的原始数据得到相同哈希值的情况,就是哈希碰撞

例如下图所示:c、d在经过某个哈希函数计算之后得到相同的哈希值10,那么c、d 就发生了哈希碰撞。

哈希碰撞

需要了解的:

哈希碰撞无法避免 (因为哈希结果值域是有限的,原始数据是无限的)

哈希值域越大,碰撞概率一般越低

好的哈希函数除了运算速度外,还需要尽量小的哈希碰撞概率

1.2 针对哈希碰撞的两种方案

在出现哈希碰撞情况下常用的方案有:

开放地址法拉链法开放地址法一般不常使用,读者可以自行查阅相关资料。

拉链法则在很多场景、甚至开源系统都会用到。

例如:PHP的 HashTable(PHP5使用双向链表、PHP7使用数组)、以及Go的map底层实现。

拉链法

如图所示,c、d的哈希结果都为10,在存储的时候使用链表来把他们串在一起(就像拉了一条锁链一样)。

其中,bucket 为在某一时刻大小固定的数组,下标为哈希值对固定数值取模之后得到。bucket 的大小一般会在某种临界状态下进行自动的扩容、缩容。

查找的时候,根据计算的哈希值先定位到bucket对应位置,然后再遍历链表查找对应数据。

注意:

原始数据经过Hash计算一般会得到比较大的哈希值,此时需用哈希值对bucket大小取模来确定数据存储位置

理想情况下,不会有哈希碰撞,数值落在bucket的不同位置,查找时间复杂度为 O(1)

糟糕情况下,数据全部哈希碰撞,数值都落在bucket同一个位置,查找时间复杂度为 O(n)

1.3 为什么用哈希

笔者以前接手过一个项目,每天数据量2亿多条,这些数据需要落盘。建表的话,如果存在一张表里面,那将会是一个灾难。当时笔者建了10张表,使用用户uid对10取模来确定当前数据落在哪一张表里面。

取模

其中,uid%10 相当于hash算法,这样的话就把2亿多条的数量分拆在不同的表里面,减少了单表数据量,好处的话:可以提升查询速度、数据在同步时效率提升等等。

在这种情况之下,使用哈希对大量数据进行拆分再合适不过了。

1.4 普通哈希的缺点

优点说了一堆,那么接下来说一下不好的地方。

假设有这样一种场景:原来使用了10张表存储数据,完全没有问题。突然有一天,业务要求现在使用20张表或者5张表存储数据,那该怎么办?

由于表的个数发生变化,此时的hash函数 uid%10 就应该变为 uid%20 或者 uid%5。

此时老的数据就需要进行处理,怎么办?rehash!

对全量数据进行rehash,使用新的hash函数重新计算所有数据,再把这些数据存储在新的表中。

实际开发中出现 rehash 的场景会非常多,所以就需要提前做一些预案。

如果数据量非常大的话,一般有两种方案:

停服维护,在维护期间进行数据 rehash 迁移异步迁移,写数据的时候,使用新的哈希函数确认落在哪一张表里面。查询的时候,如果发现数据没有迁移完成,则需要同时使用多个hash函数,从多张表中读取数据(假设还牵扯分页,则会更加麻烦)。在提供服务的同时,对老数据进行rehash迁移。方案1需要停服,这就要看产品、公司业务是否允许。允许的情况之下,是最优方案。

方案2不停服进行迁移,相当于边开飞机边换轮胎。风险高、逻辑处理复杂。

还有就是,对于数据量大的情况之下,rehash可能会是个漫长的过程

那么,有没有其他好的解决办法呢?

办法是有,解决问题的角度从迁移全量数据变成了迁移部分数据。它就是:一致性哈希。

2. 一致性哈希

维基百科告诉我们:

一致哈希 是一种特殊的哈希算法。在使用一致哈希算法后,哈希表槽位数(大小)的改变平均只需要对K/n 个关键字重新映射,其中 K是关键字的数量,n是槽位数量。然而在传统的哈希表中,添加或删除一个槽位的几乎需要对所有关键字进行重新映射。

一致哈希由MIT的Karger及其合作者提出,现在这一思想已经扩展到其它领域。在这篇1997年发表的学术论文中介绍了“一致哈希”如何应用于用户易变的分布式Web服务中。哈希表中的每一个代表分布式系统中一个节点,在系统添加或删除节点只需要移动 K/n项。

一致哈希也可用于实现健壮缓存来减少大型Web应用中系统部分失效带来的负面影响。

一致哈希的概念还被应用于分布式散列表(DHT)的设计。DHT使用一致哈希来划分分布式系统的节点。所有关键字都可以通过一个连接所有节点的覆盖网络高效地定位到某个节点。

David Karger及其合作者列出了使得一致哈希在互联网分布式缓存中非常有用的几个特性:

冗余少负载均衡过渡平滑存储均衡关键词单调

2.1 实现方式 - 哈希环

一致哈希将每个对象映射到圆环边上的一个点,系统再将可用的节点机器映射到圆环的不同位置。查找某个对象对应的机器时,需要用一致哈希算法计算得到对象对应圆环边上位置,沿着圆环边上查找直到遇到某个节点机器,这台机器即为对象应该保存的位置。

当删除一台节点机器时,这台机器上保存的所有对象都要移动到下一台机器。

添加一台机器到圆环边上某个点时,这个点的下一台机器需要将这个节点前对应的对象移动到新机器上。

更改对象在节点机器上的分布可以通过调整节点机器的位置来实现。

假设有一个环形结构,上面有很多节点,一般为 2的32次方。

哈希环

我们需要做的事情大致如下:

对不同节点服务器的某些参数(mac地址、IP地址等)进行hash计算,用hash值对2^32取模,确定当前服务器落在环某一个节点上数据存储时,对指定的key进行hash计算,然后用hash值对2^32取模,确定数据落在环的哪一个节点上,得到环的节点值之后,顺时针方向找到遇到的第一台服务器,这台服务器就是存储当前数据的地方。

普通哈希环

从图中可以看到,有三台服务器分别落在哈希环的不同节点位置。数据A、B、C、D、E也落在环的不同位置。根据一致性哈希要求,数据在计算得到自己的环中节点之后,顺时针找到第一个服务器节点,那台服务器就是数据的存储位置。

那样的话,可知:

数据D、E、A存储在服务器1数据B、C存储在服务器2没有数据存储在服务器3

2.2 场景复现

场景1(缩容)

假设,服务器2发生故障,存在上面的数据都需要迁移

那么,此时只需要迁移服务器1与服务器2之间的数据B、C到服务器3即可。

场景2(扩容)

假设,在数据B、C之间添加服务器4,那么只需要迁移存储在服务器2上的数据B到服务器4即可。

通过上面两个场景可以看出,无论是扩容还是缩容,相对于传统的hash方式,在发生扩、缩容时,只需要迁移一部分数据。大大简化了数据的迁移量,也会大大降低发生问题的概率。

2.3 优化版本的哈希环

通过上面例子可以看出:

数据D、E、A存储在服务器1数据B、C存储在服务器2没有数据存储在服务器3不知道你发现没有:服务器3没有存储数据,服务器1却存储最多的数据,此时就发生了数据倾斜

那么,有什么办法来解决数据倾斜吗?

办法就是需要对负载策略进行优化,引入虚拟服务器节点。

原来的一台服务器,在哈希环上只能拥有一个节点。那么,此时我们对每一台服务器进行虚拟。例如:原来的服务器1,现在虚拟为2台,服务器1-A、服务器1-B,此时这2台虚拟服务器会在哈希环上拥有不同的2个节点(但是它们实际映射到同一台真实的服务器上)。此时,哈希环就发生了变化。

具有虚拟节点的哈希环

此时,服务器节点由原来的3个节点变为了6个节点。

根据一致性哈希要求,数据存储的位置变为:

数据A存储在服务器1-A数据B存储在服务器3-A数据C存储在服务器2-A数据D存储在服务器1-B数据E存储在服务器2-B由于,上面的服务器节点为虚拟服务器节点,最终数据存储在的真实位置:

数据A、D存储在服务器1数据C、E存储在服务器2数据 B 存储在服务器3由此可见,通过引入服务器虚拟节点,数据的存储变得比较均衡。

3. 总结

通过一系列的场景分析,我们认识了哈希、哈希碰撞、哈希碰撞的解决办法,并抛出了普通哈希存在的数据全量迁移问题。

同时,也找到了解决全量数据迁移的办法——一致性哈希,通过对一致性哈希的认识,了解到它所拥有的巨大潜力。但是,面对大数据量存储的场景,可能会出现数据倾斜,造成某些服务器的高负载。在引入了服务器虚拟节点之后,对一致性哈希的负载进行了优化,从而达到了一种各个服务器均衡的状态。

实际场景中,面对不同的业务或许会有些许差异。但是,大致逻辑类似。

4. 扩展阅读

https://www.toutiao.com/a6996832155624063521 一文读懂一致性哈希

https://zh.wikipedia.org/wiki/%E4%B8%80%E8%87%B4%E5%93%88%E5%B8%8C 一致性哈希

原文地址:https://www.toutiao.com/article/7001103919707619853/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340