什么是哈希
Hash,一般翻译做散列、杂凑,或音译为哈希,是指把任意长度的输入(又叫做预映射pre-image)通过哈希算法变换成固定长度的输出的过程,该输出就是哈希值。
哈希算法(哈希函数)
哈希算法指的是能把任意长度的输入变换成固定长度输出的一系列算法。大家都知道数学中的函数如y=x+1, 通过改变x的值,我们可以得到对应的y。哈希函数其实也一样,通过传入不同的输入得到对应的输出,不同的是哈希函数的输入不仅限于数学中的数字,并且它的输出是固定长度的,具有一定规则的。常见的哈希算法有MD5, SHA-1, SHA-2, UUID1, UUID…
哈希冲突
理论上哈希算法输入跟输出的映射关系应该是一对一的,但是会有一定概率出现多个输入对应一个输出,即不同的x值得到了同样的y值,这种情况叫哈希冲突。解决哈希冲突也有许多常见的方法,比如拉链法,线性探查法…可以自行去了解
哈希取模
哈希取模是指对hash结果取余。假设我们现在需要对数据库进行分表分库。需要把user表分成10个表,这时我们只需对user_id进行哈希取模,即user_id.hash() % 10, 假设user_id.hash()等于12, 则把该记录存入第2(12 % 10 = 2)个表中。取值的时候重复上面步骤即可知道从哪个表查询我们想要的数据了。但是假设我们的用户有激增了,我们需要从10个表分成20个表,这个时候我们通过user_id.hash()%20去查数据肯定查不到之前的数据了,应为之前是通过user_id.hash() % 10去存储的。要解决这个问题,我们就得把所有数据重新hash,但是这样的代价是比较大的。为了解决这个问题,于是就有了一致性哈希
一致性哈希
一致性哈希
一致性 Hash 算法也是使用取模的思想,只是,刚才描述的取模法是对节点数量进行取模,而一致性Hash算法是对 2^32 取模,什么意思呢?简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希环如下,从 0 ~ 2^32-1 代表的分别是一个个的节点,这个环也叫哈希环
然后我们将我们的节点进行一次哈希,按照一定的规则,比如按照 ip 地址的哈希值,让节点落在哈希环上。比如此时我们可能得到了如下图的环:
然后就是需要通过数据 key 找到对应的服务器然后存储了,我们约定,通过数据 key 的哈希值落在哈希环上的节点,如果命中了机器节点就落在这个机器上,否则落在顺时针直到碰到第一个机器。如下图所示 : A 的哈希值落在了 D2 节点的前面,往下找落在了 D2 机器上,D的哈希值 在 D1 节点的前面,往下找到了 D1 机器,B的哈希值刚好落在了D1 节点上,依次~~~
一致性哈希的分析
一致性哈希主要就是解决当机器减少或增加的时候,大面积的数据重新哈希的问题,主要从下面 2 个方向去考虑的,当节点宕机时,数据记录会被定位到下一个节点上,当新增节点的时候 ,相关区间内的数据记录就需要重新哈希。
某节点宕机
我们假设上图中的 节点 D2 因为一些原因宕机了,可以看到,只有数据 A 的记录需要重新重新定位存储到节点 D1 上,因为 D1 是 D2 的下一个节点,其它的数据都没有被影响到,此时被影响的仅仅是 图中的 D0-D2 这段区间的记录,也就是之前落在 D2 上的数据现在都要落到 D1 上面了。如下图
新增节点
我们假设我们需要增加一台机器,也就是增加一个节点D4,如下图所示,这个节点落在 D2-D1 之间,按照上述的哈希环上的哈希值落在节点的规则,那么此时之前落在 D2 到 D4 之间的数据都需要重新定位到新的节点上面了,而其它位置的数据是不需要有改变的。
一致性哈希的数据倾斜问题
一致性Hash算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题。比如只有 2 台机器,这 2 台机器离的很近,那么顺时针第一个机器节点上将存在大量的数据,第二个机器节点上数据会很少。如下图所示,D0 机器承载了绝大多数的数据
虚拟节点解决数据倾斜问题
为了避免出现数据倾斜问题,一致性 Hash 算法引入了虚拟节点的机制,也就是每个机器节点会进行多次哈希,最终每个机器节点在哈希环上会有多个虚拟节点存在,使用这种方式来大大削弱甚至避免数据倾斜问题。同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射,例如定位到“D1#1”、“D1#2”、“D1#3”三个虚拟节点的数据均定位到 D1 上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中,通常将虚拟节点数设置为32甚至更大,因此即使很少的服务节点也能做到相对均匀的数据分布。这也是 Dubbo 负载均衡中有一种一致性哈希负载均衡的实现思想。
一致性哈希的应用案例
一致性哈希用到的地方很多,特别是中间件里面,比如 Dubbo 的负载均衡也有一种策略是一致性哈希策略,使用的就是虚拟节点实现的。Redis 集群中也用到了相关思想但是没有用它而是根据实际情况改进了一下。而对于存储数据的节点水平切分的时候它的作用就更不可代替了。and so on···
Redis 集群分槽的实现
Redis 集群并没有直接使用一致性哈希,而是使用了哈希槽 (slot) 的概念,Redis 没有直接使用哈希算法 hash(),而是使用了crc16校验算法。槽位其实就是一个个的空间的单位。其实哈希槽的本质和一致性哈希算法非常相似,不同点就是对于哈希空间的定义。一致性哈希的空间是一个圆环,节点分布是基于圆环的,无法很好的控制数据分布,可能会产生数据倾斜问题。而 Redis 的槽位空间是自定义分配的,类似于Windows盘分区的概念。这种分区是可以自定义大小,自定义位置的。Redis 集群包含了 16384 个哈希槽,每个 Key 经过计算后会落在一个具体的槽位上,而槽位具体在哪个机器上是用户自己根据自己机器的情况配置的,机器硬盘小的可以分配少一点槽位,硬盘大的可以分配多一点。如果节点硬盘都差不多则可以平均分配。所以哈希槽这种概念很好地解决了一致性哈希的弊端。
另外在容错性和扩展性上与一致性哈希一样,都是对受影响的数据进行转移而不影响其它的数据。而哈希槽本质上是对槽位的转移,把故障节点负责的槽位转移到其他正常的节点上。扩展节点也是一样,把其他节点上的槽位转移到新的节点上。
需要注意的是,对于槽位的转移和分派,Redis集群是不会自动进行的,而是需要人工配置的。所以Redis集群的高可用是依赖于节点的主从复制与主从间的自动故障转移。
参考链接:
链接:https://www.jianshu.com/p/735a3d4789fc