小白debug

redis集群模式是什么?架构是怎么样的?

你是一个程序员,你知道 redis 本质上是个单机服务,一旦崩了,服务就不可用了,加几个从节点当做备胎,时刻同步主节点数据,一旦主节点崩了。

redis主节点崩溃
redis主节点崩溃

某个觊觎主节点位置的从节点,就会成为新的主节点,继续对外提供服务,这样可以保证系统高可用。

redis从节点在主节点崩溃后顶上
redis从节点在主节点崩溃后顶上

可问题就来了,redis 是将数据放内存中的,从节点的数据跟主节点是一样的,存的都是全量数据,但单机服务器内存总有上限。加再多从节点也无法突破单机瓶颈。如果想要存储更多数据,该怎么办呢?

每个节点都是全量数据
每个节点都是全量数据

这就需要聊聊 redis 集群模式了。

Redis集群模式
Redis集群模式

看之前,你点赞了吗?关注了吗?谢谢!

数据切分

单机内存有限,而数据无限,拿有限的单机内存去支持无限的数据,明显不合理,所以我们换个思路。

既然数据无限,那我们就对数据进行切分,变成多份,将多份切片数据放到多个 redis 节点上,数据量变大后,再根据需要增加 redis 节点,也就做到了所谓的高可扩展。

切分数据
切分数据

那怎么切分数据呢?

我们知道 redis 全称 Remote Dictionary Server,远程的字典服务。字典你熟啊,在 python 里叫 dict,在 java 里叫 map, 它们都有 Key 和 value。

map的key和value
map的key和value

所以很容易想到,我们可以对多个 redis 节点 进行编号,再将 key 的值跟 redis 的节点总数进行求余,就像这样。计算得到的 redis_num 就是这个 key 对应的数据应该放到哪个 redis 节点上。

对crc16(key)求余
对crc16(key)求余
redis_num = (CRC16(key)) mod redis_all_num

但它有个很大的问题,如果我们前期没规划好容量,后期想要调整 redis 节点个数,那上面公式里的 redis_all_num 就会改变,对应放数据的 redis 节点也会被改变,那大概率所有数据都需要重新迁移。

这对线上服务来说,成本和风险都太高。

有更好的方案吗?有!

引入哈希槽

既然担心公式发生变化会导致最终的分片结果改变,那我们就想办法让公式里用于求余的值固定下来。

将求余公式里的部分固定下来
将求余公式里的部分固定下来

怎么操作呢?

没有什么是加一层中间层不能解决的,如果有,那就再加一层。

我们可以在数据的 key 和 redis 节点之间,加一层长度固定的数组层。

比如长度为16384的数组,数组里每一槽位就是所谓的哈希槽,slot。

将原来的数据分片公式改造成这样:

slot_num = (CRC16(key)) mod 16384
Image

再让每个 redis 节点负责管理一部分哈希槽,比如节点 A 负责前面 1w 个槽,节点 B 负责后面的 6384 个。

把哈希槽分配给多个节点
把哈希槽分配给多个节点

数据的 key 经过公式分片后会落到某个 哈希槽 上,再由管理这个 槽 的 redis 节点来读写这个数据。

通过这个方式将key数据分散到多个哈希槽,也就是多个 redis 节点上。

并且每个 redis 节点 都维护了「其他 redis 管理了哪些槽」这一信息。有了这些信息,任意一个 redis 节点只要拿到 key,就能知道它该存到哪个 redis 节点上。

现在如果我们想要增加 redis 节点个数,由于哈希槽这一层的存在,16384 固定不变,那计算出的 slot_num 也不会变化。

我们只需要定义新增的 redis 负责哪部分 哈希槽,然后迁移那一小部分 哈希槽 里的数据就好了。

只需要迁移部分数据
只需要迁移部分数据

比起以前要全部数据重新迁移,现在的工作量大大减小。

看爽了,就来个一键三连吧。

重定向

引入哈希槽这一概念后,我们就能用多个 redis 节点来切分存储数据了。

但这也引入了另外一个问题,哈希槽信息是维护在redis里的,客户端没有这个信息,读写数据时,怎么知道这些 key对应哪个哈希槽 和 redis 节点。

有解法吗?

有!redis提供一个「CLUSTER SLOTS」命令,可以让客户端获取到最新的哈希槽分配信息。

但如果redis节点数量有变化。客户端没有及时更新哈希槽信息,是不是就会出错?

完全不用担心,客户端只管对任意一个redis节点读写就行。

接收到数据的 redis 节点会根据最新维护的哈希槽信息,判断这个 key 属于哪个 redis 节点,如果恰好属于它自己,则直接完成读写。否则,redis 节点就返回一个错误,并告诉客户端该去哪个 redis 节点读写数据就好,这个过程也叫重定向。

重定向是什么
重定向是什么

不过你也不需要自己写代码去实现这个重定向功能,其他大佬已经帮你实现好相关的客户端代码库,用就完事了。

数据迁移

结合重定向,我们再来看下数据迁移时的一个问题。

如果我调整了 redis 个数,数据正在发生迁移行为,客户端查某个 redis 节点没数据,我怎么知道它是真没有这个 key,还是没来得及迁移过来呢?

好办,redis 内部会对 redis 的哈希槽,维护一个是否正在迁移数据的状态。

维护一个迁移状态
维护一个迁移状态

如果正在迁移中,redis 节点会返回一个重定向指令,告诉客户端接下来该去哪个节点读这个数据,客户端根据重定向指令的提示,重新访问另一个 redis 节点,如果这时候还是没有数据,那就是真没数据。

redis 集群模式

通过数据切分,现在每个 redis 都维护了一部分数据,为了防止崩溃导致的部分数据无法访问,还可以为每个 redis 节点加几个从节点。

这样一个将数据分片到多个 redis 节点上,同时为每个节点加入从节点的架构,就是所谓的集群模式。

Redis集群
Redis集群

到这里,我们就已经让原来的单机服务 redis ,变得具备高可用和高可扩展的特性了。

接下来,我们用一个具体的例子了解下它的工作原理。

读写 redis 集群

假设 Redis 集群有 3 个主节点(A、B、C)

Redis集群节点
Redis集群节点
  • • 节点 A负责哈希槽 0~5460
  • • 节点 B负责哈希槽 5461~10922
  • • 节点 C负责哈希槽 10923~16383

写操作:

假设客户端要写入的 key 是 user:1001,value 是"小白 debug"。

这时候客户端会随机访问其中一个 redis 节点,比如 节点 A ,发送 set key value 的操作。

注意: 如果客户端有slot信息,则不是「随机连」

Redis 会计算这个 key 对应的哈希槽的值,比如 1234。A 节点发现 1234 在哈希槽 0~5460范围内,归它自己管, 所以这个Key会被写入到 节点 A。

但如果得到的哈希槽值是 5462, 属于 B 节点管,那就会返回一个重定向 命令,告诉客户端,这个应该写到 B,客户端会自动重定向到节点 B,成功完成写入。

读操作:

假设客户端要读取的依然是 user:1001。

客户端还是随机连到某一节点,比如节点 A,发送 get key 的操作。节点A 计算 key 的值发现 user:1001 的哈希槽的值是 1234,归节点 A 自己管,那么就会返回对应的值"小白 debug"。

但如果计算得到的哈希槽的值是 5462,属于节点 B。那节点 A 会返回一个重定向指令,告诉客户端,这个key在节点 B。

客户端会自动重定向到节点 B,完成数据读取。

现在大家通了吗?

好啦,如果你觉得这期视频对你有帮助,记得转发给你那不成器的兄弟。记得关注!我们下期见!