前言
互联网公司中,绝大部分都没有马爸爸系列的公司那样财大气粗,他们即没有强劲的服务器、也没有钱去购买昂贵的海量数据库。那他们是怎么应对大数据量高并发的业务场景的呢?
这个和当前的开源技术、海量数据架构都有着不可分割的关系。比如通过mysql、nginx等开源软件,通过架构和低成本的服务器搭建千万级别的用户访问系统。
怎么样搭建一个好的系统架构,这个话题我们能聊上个七天七夜。这里我主要结合Redis集群来讲一下一致性Hash的相关问题。
Redis集群的使用
我们在使用Redis的过程中,为了保证Redis的高可用,我们一般会对Redis做主从复制,组成Master-Master或者Master-Slave的形式,进行数据的读写分离,如下图1-1所示:
![753be3db234c423bff847e1b97be7a59.png](https://i-blog.csdnimg.cn/blog_migrate/d911bcc327cbc70157a63a7e4f000c5e.jpeg)
图1-1:Master-Slave模式
当缓存数据量超过一定的数量时,我们就要对Redis集群做分库分表的操作。
来个栗子,我们有一个电商平台,需要使用Redis存储商品的图片资源,存储的格式为键值对,key值为图片名称,Value为该图片所在的文件服务器的路径,我们需要根据文件名,查找到文件所在的文件服务器上的路径,我们的图片数量大概在3000w左右,按照我们的规则进行分库,规则就是随机分配的,我们以每台服务器存500w的数量,部署12台缓存服务器,并且进行主从复制,架构图如下图1-2所示:
![b1e88b547afb8418605d473c2ac5cd46.png](https://i-blog.csdnimg.cn/blog_migrate/50070e8c896f054e89bd9e33aa95af43.jpeg)
图1-2:Redis分库分表
由于我们定义的规则是随机的,所以我们的数据有可能存储在任何一组Redis中,比如我们需要查询"product.png"的图片,由于规则的随机性,我们需要遍历所有Redis服务器,才能查询得到。这样的结果显然不是我们所需要的。所以我们会想到按某一个字段值进行Hash值、取模。所以我们就看看使用Hash的方式是怎么进行的。
使用Hash的Redis集群
如果我们使用Hash的方式,每一张图片在进行分库的时候都可以定位到特定的服务器,示意图如图1-3所示:
![f46f4c58f07e29c770310c316f6d8aea.png](https://i-blog.csdnimg.cn/blog_migrate/3f2ca516bccd0e944a895550101dcd13.jpeg)
图1-3:使用Hash方式的命中缓存
从上图中,我们需要查询的是图product.png,由于我们有6台主服务器,所以计算的公式为:hash(product.png) % 6 = 5, 我们就可以定位到是5号主从,这们就省去了遍历所有服务器的时间,从而大大提升了性能。
使用Hash时遇到的问题
在上述hash取模的过程中,我们虽然不需要对所有Redis服务器进行遍历而提升了性能。但是,使用Hash算法缓存时会出现一些问题,Redis服务器变动时,所有缓存的位置都会发生改变。
比如,现在我们的Redis缓存服务器增加到了8台,我们计算的公式从hash(product.png) % 6 = 5变成了hash(product.png) % 8 = ? 结果肯定不是原来的5了。
再者,6台的服务器集群中,当某个主从群出现故障时,无法进行缓存,那我们需要把故障机器移除,所以取模数又会从6变成了5。我们计算的公式也会变化。
由于上面hash算法是使用取模来进行缓存的,为了规避上述情况,Hash一致性算法就诞生了~~
一致性Hash算法原理
一致性Hash算法也是使用取模的方法,不过,上述的取模方法是对服务器的数量进行取模,而一致性的Hash算法是对2的32方取模。即,一致性Hash算法将整个Hash空间组织成一个虚拟的圆环,Hash函数的值空间为0 ~ 2^32 - 1(一个32位无符号整型),整个哈希环如下:
![8ff17148e9430cfc5a9395034242d44e.png](https://i-blog.csdnimg.cn/blog_migrate/b7b7984e83d29ac24a235142de3ffd0c.jpeg)
图1-4:Hash圆环
整个圆环以顺时针方向组织,圆环正上方的点代表0,0点右侧的第一个点代表1,以此类推。
第二步,我们将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台服务器就确定在了哈希环的一个位置上,比如我们有三台机器,使用IP地址哈希后在环空间的位置如图1-4所示:
![e6bc9f8a2450e9204bf9926c3d9ab363.png](https://i-blog.csdnimg.cn/blog_migrate/3db34346d4be989fb80b7ff344bf9779.jpeg)
图1-4:服务器在哈希环上的位置
现在,我们使用以下算法定位数据访问到相应的服务器:
将数据Key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针查找,遇到的服务器就是其应该定位到的服务器。
例如,现在有ObjectA,ObjectB,ObjectC三个数据对象,经过哈希计算后,在环空间上的位置如下:
![ba14845e3d163860b696e881109bc016.png](https://i-blog.csdnimg.cn/blog_migrate/18c796cd6eb50ec1a881d6efb57ddb49.jpeg)
图1-5:数据对象在环上的位置
根据一致性算法,Object -> NodeA,ObjectB -> NodeB, ObjectC -> NodeC
一致性Hash算法的容错性和可扩展性
现在,假设我们的Node C宕机了,我们从图中可以看到,A、B不会受到影响,只有Object C对象被重新定位到Node A。所以我们发现,在一致性Hash算法中,如果一台服务器不可用,受影响的数据仅仅是此服务器到其环空间前一台服务器之间的数据(这里为Node C到Node B之间的数据),其他不会受到影响。如图1-6所示:
![85949f49084b6605a0dfdf655856540b.png](https://i-blog.csdnimg.cn/blog_migrate/495edf253d8a0ac803afed77f04b9e14.jpeg)
图1-6:C节点宕机情况,数据移到节点A上
另外一种情况,现在我们系统增加了一台服务器Node X,如图1-7所示:
![0681c9f3efcbcc8ee2a8a598d8d7fad3.png](https://i-blog.csdnimg.cn/blog_migrate/e39299ac7e0f33d373f3826a8cbcdbf8.jpeg)
图1-7:增加新的服务器节点X
此时对象ObjectA、ObjectB没有受到影响,只有Object C重新定位到了新的节点X上。
如上所述:
一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,有很好的容错性和可扩展性。
数据倾斜问题
在一致性Hash算法服务节点太少的情况下,容易因为节点分布不均匀面造成数据倾斜(被缓存的对象大部分缓存在某一台服务器上)问题,如图1-8特例:
![bae50bb96f568f0683fd87269c1f7c90.png](https://i-blog.csdnimg.cn/blog_migrate/01570d20a2f5480b5193776d2f798647.jpeg)
图1-8:数据倾斜
这时我们发现有大量数据集中在节点A上,而节点B只有少量数据。为了解决数据倾斜问题,一致性Hash算法引入了虚拟节点机制,即对每一个服务器节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。
具体操作可以为服务器IP或主机名后加入编号来实现,实现如图1-9所示:
![168b6a2b25a1e18a77979d2fd081aa23.png](https://i-blog.csdnimg.cn/blog_migrate/19059c3c945694c6218ab8a0c7a4b145.jpeg)
图1-9:增加虚拟节点情况
数据定位算法不变,只需要增加一步:虚拟节点到实际点的映射。
所以加入虚拟节点之后,即使在服务节点很少的情况下,也能做到数据的均匀分布。
![029a208a87a230928e61a8a0a8e62478.png](https://i-blog.csdnimg.cn/blog_migrate/4af5b24181ac6b3d350b78fd3536425e.jpeg)