Redis简介四(分区cluster,一致性hash)

今天是哀悼日,向伟大的祖国,以及广大医护人员、罹难者致敬。


在简介的第二章,我们讲了Redis单机的两个问题,一个是单点问题,这个我们已经简单探讨过了,还有一个问题,即单台机器容量有限的问题,本章,我们简单探讨一下。

纵向扩展

因为Redis是内存操作的,其内存容量是受OS限制的,32bit的OS,可用寻址大小在0-2^32,换算下来大概在4G左右,64bit的OS,最大大概支持128G内存,如果一个业务线需要缓存1T的数据,单机器是完全无法支撑的。

上一章是水平扩展Redis,这一章,我们纵向扩展Redis,即将Redis数据进行拆分:
在这里插入图片描述
举个例子:将原先4G的数据,分别拆成1G大小,落到4台Redis机器上。
结合我们上一讲的内容,Redis的集群架构图如下:
在这里插入图片描述
我给这张图片取个名字,就叫贴图1吧,下面还会用到,记住这张图。

数据该如何拆分?

  • client端拆分
    如果是手动拆分,这个得结合具体使用场景了,比如现在有一个电商模块,需要缓存用户信息,商品信息,购物车信息,订单信息等等,这些譬如按照业务模块进行拆分,由client决定,应该哪一台Redis服务器写数据。但是这种方式由很多弊端,比如不够灵活,client需要知道服务器的运行以及部署情况,再比如一台Redis发生灾备后,client还怎么建立新连接发送数据等,会很不方便。
  • Redis服务端拆分
    Redis服务器是不会了解我们的业务逻辑的,它知道的只有一个key,一个value,相信很多人肯定能够想到根据key的hash值进行拆分。优势很明显,client不关心业务,只管提供key,只管写数据,Redis服务端也不需要关心业务,只关心key,只关心存。

hash散列

hash是将任意长度的输入,通过散列算法,得到一个固定长度的输出。其本质是一种压缩映射。所以根据定义,hash必定是与一个固定长度的东西建立映射关系,在Redis这块,我们可以理解为是Redis服务器数量。

假设我们现在有5台机器,先有一个key,其hash散列之后得到的值是11,则其在5台机器上的散列对应为11%5=1,对应的是第一台机器,现在Redis将数据存入了第一台机器。过了几天之后,因为业务需求扩展,需要对Redis集群进行扩容,增加了一台机器,变成了6台。当client根据当前key来redis获取缓存结果的时候,同样的key散列值为11,现在计算映射为11%6=5,对应的是6台机器中的第5台,这时候去第5台机器上用key去获取value,取不到数据,因为数据存储不在第5台机器上,而是在第1台机器上。

由此,可以得出结论,单纯使用hash与redis集群建立映射关系,后期是比较不容易维护的,一旦新增或者删除了一台机器,都会造成缓存失效。

解决方案

下面,把聚光灯打到这边,对对,就是这儿:

  • 一致性hash
  • hash槽

一致性hash

现在我们回想下上面那个实例,它为什么会有问题呢?聪明如你,一定发现了问题的关键,没错,就是映射表的长度是可变的,由开始的5变成了后来的6,再后来可能变成7,也可能变成4。

现在我们已经找到问题的关键点了,那就很好解决这个问题了,要将这个映射表的长度固定下来,如果设置的很小,那很容易hash冲突,如果设置某一个值,比如20,也是有问题的,如果哪天我们真实的机器扩容到了21台,实际最后一台的机器是没法建立映射的,因为表里不会有它,那就只剩一种选择,把它设置的很大,一般为2^32。

现在,映射表已经确定了,下面的逻辑就很简单了,将Redis和key都映射到这张表里去。想象现在有一个hash环,index从0到2^32-1,上面有具体的key,也有部分Redis机器。
至于Redis机器怎么映射上去呢?可以是机器的IP,也可以是机器的Mac地址,只要是唯一确定一台机器的都可以:
在这里插入图片描述
额,画得好像一只螃蟹,可别看饿了哈。
言归正传,假设现在有三个redis机器,以及4个key已经映射到了我们的hash环上,下面我们该如何决定每个key的数据,应该落在哪一台机器上呢?

我们从某一个具体的key开始,以顺时针方向依次寻找机器,找到的第一台机器,即为应当落值的机器。比如,key4,key1最靠近的机器为redis1,它们的数据应该落在redis1上,key2最近的机器为redis2,其值落到redis2上。

我们现在绕了这么大圈,费了这么大劲,虚拟出这么个环,有什么作用呢?现在我们回到刚才的问题。啥问题来着?对了,新增一台机器后,当我们尝试去读取缓存的时候,发现缓存已经不可用。假设我们现在也对集群进行扩容,新加入一台机器redis4,映射后的位置如下:
在这里插入图片描述
此时,我们可以发现,当我们从redis集群中获取key4,key1的value时,没有问题,能正确找到机器redis1,key2也没有问题,能找到机器redis2,但是,但是,但是,重要事情说三遍,当我们尝试用key3获取value时,找到的机器是redis4,而不是存数据时的redis3。

通过这个简单的小演示,可以发现,一致性hash算法,在很大程度上能够解决我们的问题,但是,没有完全解决,针对key3这种情况,我们该怎么解决了?

  • 如果我们redis后面还接着数据库,可以将请求发送到数据库,直接到数据库中进行查找,但是(虽然不想说但是,但是,我还是不得不说),这里面有个可能的雷点,如果我们同时将大量的请求打到数据库,很有可能会将数据库game over掉,即典型的缓存击穿。
    关于缓存的雪崩,击穿,穿透,后面有机会再写,这边不介绍了。
  • 做一个比较笨的补偿,如果第一次没有取到数据,则再向后找一台机器,尝试获取数据,如果还是没有,再将请求打到数据库

一致性hash的负载均衡

我们来看下这种情况:
在这里插入图片描述
按照一致性hash算法,只有key5会落redis1,其他的所有key,都是落在key4上,两台redis服务器,承载的工作量是1:4,这种很明显是不合理的。怎么办呢?
首先我们需要搞明白,现在的问题点是什么。是什么呢?没错,是两台redis太散列了,我们hash环上一共有4294967296个坑位,而我们的两台redis只占了两个坑位,严重失调啊。

怎么办呢,毕竟实际的机器只有2台。是的,没有,那就虚拟几个出来。
以使用ip计算hash为例,可以虚拟出无数台机器:
假设redis1机器ip:1.1.1.1,我们可以虚拟出地址为1.1.1.1-1,1.1.1.1-2,1.1.1.1-3,1.1.1.1-4乃至无穷的机器出来,他们关联的真实物理机器,其实只有redis1一台:

在这里插入图片描述

基于一致性hash的客户端连接

还记得上面的贴图1吗?密密麻麻的连接线,还记得不:
在这里插入图片描述
因为我们数据是分散在多个redis机器上的,一个client如果想获取所有数据,则需要对每一太机器都建立连接,每一次会话都是会占用资源的,所以这块需要进行简化:
在这里插入图片描述
理想的情况,应该是client无需决定应该与那一台机器建立连接,具体根据key找寻机器的事情,应该由代理根据一致性hash规则找到具体的机器。

几种实现redis代理的工具:

  • https://github.com/twitter/twemproxy
  • https://github.com/joyieldInc/predixy

怎么样,看到这里,是不是已经很激动了呢?是不是已经很清楚明白的了解了Redis是如何工作的呢?
很抱歉的告诉你,Redis没有使用一致性hash算法
哈哈哈哈哈,请让我笑一会儿=====

是不是想打我了呢?嘿嘿。

hash槽

Redis没有使用一致性hash,而是引入了hash槽的概念。
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽。其中16384个槽位是写死的。
Redis是怎么玩转的呢?
16384这个数字太大了,我们简化成10,即假设Redis预设了10个槽位(要记清实际是16384个

列举一下:
假设现在:1. 槽位10个,2.现有2台Redis机器

在现有条件下,每一个机器预分配获得5个槽位:
在这里插入图片描述
每次新增key或者查询key,根据取模的结果定位到一个具体的槽位,然后再去相应的机器上,按照槽位去 取/写 值。
这里与一致性hash算法的区别:
一致性hash算法,其取模结果,关联是hash环上的一个点,然后用这个点去关联最近的机器,
hash槽,其取模结果,是关联某一个槽位,而每个槽位是直接关联机器的,可以认为取模的结果,可以直接定位到一台机器。

下面,我们需要解决一下同样的一个问题,当我新增一台机器后,我还能通过key正常取到数据吗?
现在已经有三台机器了,假设新增一台机器后,重新计算槽位,,结果是第三台机器,获得槽位4,7,9,如图:
在这里插入图片描述
按照我们上面的介绍,每一个具体的槽位,是实际具体关联到数据的,通过具体的槽位,可以直接找到相应的数据,
比如:当只有两台机器的时候,槽位4有数据key1,key2,槽位7有数据key3,key4,槽位9有数控key5
当第三台机器Redis3加入,并且将槽位4,7,9划分给Redis3,实际只是将Redis映射表中的关系改了一下而已,同时可以很简单的知道槽位4,7,9上具体有哪些数据,通过同步操作,可以很简单的将redis1上原本槽位为4,redis2上原本槽位为7,9的数据,也就是key1,key2,key3,key4,key5直接copy到redis3上。
ok,现在我们再来查找key3,通过取模拿到的槽位还是7,然后去到redis3上找到槽位7,再找到key3,就可以get到数据了。完美解决。

基于hash槽的客户端连接

首先,我现在想要获取key的value,还需要与每一个机器建立连接吗?答案是不需要了,因为我们根据key,就可以计算出这个key具体落到哪台机器上。
在这里插入图片描述

我们还是以槽位7上的key3为例进行说明。

在Redis3还没加入集群之前,client假设已经与redis2建立了连接。ok,现在redis3加入集群,数据也已经完成转移,此时client再次给redis2建立连接发送请求。此时,根据上面的过程,槽位7已经转到到了redis3上,redis2上是不会查询到数据的,是不是本次查询就返回无结果了呢?
不是,每个redis根据槽位与机器的映射关系,是可以知道每个槽位在哪台机器上,而一个key又可以计算出其对应的槽位,所以当请求到达redis2之后,就可以计算得出当前的key在redis3上,会给client返回相应信息,现在client可以知道应该与redis3建立连接再去获取数据了。

怎么接入代理redis

太累了,不写了,后面空了再写~~~有兴趣的自己百度吧。

发布了6 篇原创文章 · 获赞 4 · 访问量 631

猜你喜欢

转载自blog.csdn.net/weixin_36510400/article/details/105309657