一致性哈希 —— 底层原理

前言

再讨论一致性哈希之前,我们先来回顾一下缓存的演化历史:

当我们的系统还是一个非常小的时候,对于用户的请求我们直接访问数据库就好了。当访问量到了一定数量的时候,我们使用缓存服务器,缓存一部分热数据来减轻数据库的压力,这便是最原始的阶段。
在这里插入图片描述
网站发展到一定规模后,并发量上来了,我们通过使用nginx的反向代理对我们的应用服务器做负载均衡,同时需要缓存服务的高可用,以免防止出现 缓存雪崩 等问题,造成数据库宕机,从而影响整个系统的可用性。这时候我们可以采用主从复制,或者是读写分离来提升缓存服务的可用性。

缓存雪崩:
由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

在这里插入图片描述

但是,当我们的系统越来越大,需要缓存的数据越来越多的时候,就会遇到缓存不够用,被撑爆的情况。同时如果我们无脑的通过扩充内存容量(甚至TB级别以上)来解决缓存不够用的情况,可能会出现性能越来越慢的情况。此时我们就需要采用分库分表策略,把我们的缓存数据,均分到不同的缓存服务器,从而避免单节点服务器内存过大的情况。
在这里插入图片描述

一、一致性哈希的产生

Redis集群,使用Hash的集群。为了系统高可用,构建了redis主从复制;为了提升响应速率,对redis进行了分库分表。

假设现在有8台redis服务,4台master,4台slave,构成的redis集群(如上图)。我们现在我们要缓存一个用户,key是用户id,value是用户的信息。假设用户id=1234,1234%4=2我们就把这条用户的信息存放到第二台服务器上面。当我们需要读取缓存的时候,同样根据用户id=1232求一次哈希值,得到结果2,从第二台服务器上面获取数据。

这样似乎解决问题了,但是又有新的问题产生了。当我们需要扩容我们的集群,或者缩容的时候,我们想一下,我们以前id=1234的用户信息在第二台服务器上存着,当服务器增加到5台的时候,我们获取用户信息求哈希值1234%5=4,这时候就出大问题了,因为哈希因子的改变,我们id=1234的用户信息明明存放在第二台服务器,但是现在变成第四台了,我们所有节点存放的数据都没用了,自己手写过HashMap的人肯定明白,这时候需要对所有的数据重新求一次哈希值。但是我们线上缓存系统可不能这么玩,让缓存服务重新计算哈希值,肯定需要花费一定时间,少了缓存系统,大量的请求到了数据库,就会发生缓存雪崩。

所以为了不让这种事情发生,一致性哈希算法就诞生啦!

结合场景:

假设有N台机器,每个机器上几个数据,这时读取数据是按照机器数量取模读取。
公式:h = Hash(key) % N
但是现在增加/减少一台无数据的机器,必然会导致数据读取错乱,因分母发生了变更。
问题点很明显,就是分母。
如果我将分母固定,那是不是就没有问题了,这就引入了虚拟节点的概念。
我先虚拟出1000个节点,某几个节点在一台机器上,如果有一台机器宕机,就将当前机器上的节点分配到其他机器上。整个过程中分母不会变,也就达到了一致性的效果。

二、HashMap

  • 当使用HashMap时,key会被均匀的映射到hashMap内部数组entry中,映射方法利用key的hash值做移位运算,和entry数组的长度(Length-1)做与运算(和hashtable不同,(key.hashcode() & 0x7FFFFFFF) % table.length,使数据分散均匀分布)。

  • put操作时,当底层数组数据量超过LoadFactor(默认0.75)* len时,HashMap会按照2倍扩容的方式扩大底层数组的大小。put操作引进的新数据会按照上面提及的方法进行映射,但是之前已经映射的数据怎么办呢?HashMap源码中显示,resize()操作,每次扩容都会把之前已经映射的key重新映射。

  • 所以对于HashMap而言,想要获得较好的性能必须要提前估计所放数据集合的大小,以设计合适的初始容量和负载因子。

  • 但不是每个场景都像HashMap这么简单,比如在大型的p2p网络中存在成百上千台server,资源和server的关系是以key的形式映射而成的,也就是说是一个大的HashMap,维护着每个资源在哪个server上,如果有新的节点加入或者退出该网络cluster,跟HashMap一样,也会导致映射关系发生变化,显然不可能吧所有的key和server的映射关系重新调整一遍,那会导致服务大量的未命中,直接导致服务不可用。这就需要一种方法,在新的server加入或者server的宕机,不需要调整所有的节点,而达到继续维护哈希映射的关系。

三、一致性哈希的定义

一致性哈希定义为:

“Consistent hashing is schema that provides hash table functionlity in a way that the addition or removal of one slot does not significantly change the mapping of keys to the slots.”

就是说,“一致性哈希,就是提供一个hashtable,它能在节点的加入和离开时不会导致映射关系的重大变化”。

四、一致性哈希的实现

实现思想:

  1. 假定所有的资源以key的单向哈希(sha-1,sha-2,md5)均匀的分布在一个环上;

  2. 假定所有的节点(server)均匀的分布在该环上(以server的ip,port单向散列);

  3. 每个节点(server)只负责一部分Key,当节点加入、退出时只影响加入退出的节点和其邻居节点或者其他节点只有少量的Key受影响。

特性

1. 平衡性(Balance):

平衡性是指哈希的结果能够尽可能分布到所有的节点中去,这样可以使得所有的节点空间都得到利用。很多哈希算法都能够满足这一条件。

2. 单调性(Monotonicity):

单调性是指如果已经有一些内容通过哈希分派到了相应的节点中,又有新的节点加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的节点中去,而不会被映射到旧的节点集合中的其他节点上。

3. 分散性(Spread):

在分布式环境中,终端有可能看不到所有的缓存,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓存上时,由于不同终端所见的缓存范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓存区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓存中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。

4. 负载(Load):

负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

数据结构

(1)构造环形Hash空间:

一致性Hash也是基于模运算,只是和刚才的模运算有所不同。不是针对节点数量进行取模,而是针对232-1 进行取模,就是将整个哈希空间想象成一个虚拟圆环,圆环顺时针从0开始到2^32-1结束,我们称这个圆环为哈希环,如下图:
在这里插入图片描述

(2)哈希取模

对服务器IP或者主机名作为关键字进行哈希取模,这样就能在哈希环上定位每台机器的位置;
在这里插入图片描述

(3)分配服务器

现在我们要插入一个Key-Value键值对,首先求出key的hash code然后进行模运算,这样就能定位这个key在哈希环上的位置,如果当前位置没有node服务器,那么我们就顺时针寻找,遇到的第一个节点,就是我们数据所对应的服务器。
在这里插入图片描述

容错性

现在我们假设我们的node1挂掉了,但是我们的node2,node3,node4并不会收到影响,而key1只会被重新定位到node2上面。所以对于一致性哈希算法来说,如果其中一个节点出现故障,受影响的只是该节点的后一个节点node2。
在这里插入图片描述

扩展性

如果我们要新添加一台服务器nodeX,计算出哈希值后,发现它的位置在node2和node3之间。那么受影响的只有新增节点的前一个节点node3(我们需要重新增加缓存到新的nodeX)而其他的节点都不会受影响。
在这里插入图片描述

哈希环数据倾斜

上面的4个节点都是相对完美的情况下,把哈希节点较为均等的分成了4等分。但是如果求出的哈希值不均等,某些节点的位置隔得很近,大量的数据就会集中到某一台服务器,如下图:
在这里插入图片描述

虚拟节点解决数据倾斜

这种情况下,大量的节点定位到了node4这台服务器上面,极少的数据定位到node2或node3,造成了数据数据像node4上倾斜。一致性哈希算法为了数据倾斜,引入了虚拟节点,针对每个节点计算出多个哈希节点,均匀的分布到哈希环上。

比如现在的node1,node2,node3,node4,我们可以将关键字(服务器IP或者主机名)后面加上版本号。例如nodeA#1、nodeA#2、nodeB#1、nodeB#2意思类推。定位算法不变,只是多了异步虚拟节点映射,虽然定位到nodeA#1或nodeA#2节点,但是最终会映射到node1。
在这里插入图片描述

  • “虚拟节点”(virtual node)是实际节点(机器)在hash空间的复制品(replica),一实际节点(机器)对应了若干个“虚拟节点”,这个对应个数也成为“复制个数”,“虚拟节点”在hash空间中以hash值排列。

  • "虚拟节点"的引入意义在于实际节点少而导致大片未被映射从而导致数据分布不均匀。假设每个server映射N个节点(N在100~200时较优),但key的hash映射到这N个节点实际上都有由该server托管。

对象从hash到虚拟节点到实际节点的转换如下图:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Whitemeen太白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值