Redis 集群 - 数据分片算法

前言

广义的集群:只要是多个机器构成了一个分布式系统,都可以被称为集群。

狭义的集群:redis 的集群模式,这个集群模式下,主要是解决存储空间不足的问题。

Redis 集群

redis 采用主从结构,可以提高系统的可用性,但是并不能解决存储空间不足的问题,因为主节点和从节点存储的都是全量数据,随着业务功能的逐步使用,存储数据的逐步增加,redis 由于内存的限制肯定会在某一时刻到达极限,无法存储过多的数据。

我们要如何解决 redis 存储空间不足的问题呢?加机器即可!所谓 "⼤数据" 的核⼼, 其实就是⼀台机器搞不定了,⽤多台机器来搞,Redis 集群就是在上述的思路之下,引⼊多组 Master / Slave , 每⼀组 Master / Slave 存储数据全集的 ⼀部分, 从⽽构成⼀个更⼤的整体, 称为 Redis 集群。

假定整个数据全集是 1 TB, 引⼊三组 Master / Slave 来存储. 那么每⼀组机器只需要存储整个 数据全集的 1/3 即可.

在上述图中

• Master1 和 Slave11 和 Slave12 保存的是同样的数据.占总数据的 1/3

• Master2 和 Slave21 和 Slave2 保存的是同样的数据.占总数据的 1/3

• Master3 和 Slave31 和 Slave32 保存的是同样的数据.占总数据的 1/3

这三组机器存储的数据都是不同的.

每个 Slave 都是对应 Master 的备份(当 Master 挂了, 对应的 Slave 会补位成 Master).每个红框部分都可以称为是⼀个 分⽚ (Sharding).如果全量数据进⼀步增加, 只要再增加更多的分⽚,即可解决.

数据分片算法

Redis cluster 的核⼼思路是⽤多组机器来存数据的每个部分.那么接下来的核⼼问题就是,给定⼀个数据(⼀个具体的 key), 那么这个数据应该存储在哪个分⽚上?读取的时候⼜应该去哪个分⽚读取?

围绕这个问题,业界有三种⽐较主流的实现⽅式.

哈希求余

借鉴了哈希表的基本思想,借助 hash 函数,将数据的 key 映射成整数,再针对分片片数求余,得到了一个下标,就可以把数据保存到该下标对应的分片上了,

ps:MD5 就是一个普遍用来将字符串映射成整数的哈希函数

比如现在我们拥有 3 片 redis 分片,此时来了一个字符串,字符串映射为整数是 10,那么就需要将该数据保存到 10%3=1 号分片上,后续要获取该 key 所对应的数值时,也用同样的方法获取分片编号,再去编号对应的 redis 分片上获取数据即可。

优缺点

优点:简单⾼效, 数据分配均匀

缺点不方便扩容,⼀旦需要进⾏扩容, N (分片数)改变了,原有的映射规则被破坏,就需要让节点之间的数据相互传输,重新排列,以满⾜新的映射规则.此时需要搬运的数据量是⽐较多的,开销较⼤.

ps:分片数改变以后,计算哪个数据保存到哪个分片的公式就改变了,按照新的公式,此时许多分片中现有的数据都在错误的位置,就要进行大批量的数据移动,这是非常消耗资源的行为。

用一个例子来直观的看待这个问题:

如下图,N 为 3 的时候, [100, 120] 这 21 个 hash 值的分布 (此处假定计算出的 hash 值是⼀个简单的整数,⽅便⾁眼观察)

当引⼊⼀个新的分⽚, N 从 3 => 4 时, ⼤量的 key 都需要重新映射.(某个key % 3 和 % 4 的结果不⼀样,就映射到不同机器上了).

如上图可以看到,整个扩容⼀共 21 个 key, 只有 3 个 key 没有经过搬运,其他的 key 都是搬运过的,很显然搬运数据是个非常庞大的工程

⼀致性哈希算法

为了降低上述的搬运开销,能够更⾼效扩容,业界提出了"⼀致性哈希算法". key 映射到分⽚序号的过程不再是简单求余了,⽽是改成以下过程:

第⼀步,把 0 -> 2^32-1 这个数据空间, 映射到⼀个圆环上.数据按照顺时针⽅向增⻓.

第⼆步,假设当前存在三个分⽚,就把分⽚放到圆环的某个位置上.

第三步,假定有⼀个 key, 计算得到 hash 值 H, 那么这个 key 映射到哪个分⽚呢? 规则很简单, 就是从 H 所在位置,顺时针往下找,找到的第⼀个分⽚,即为该 key 所从属的分⽚.

这就相当于, N 个分⽚的位置, 把整个圆环分成了 N 个管辖区间. Key 的 hash 值落在某个区间内, 就归对应区间管理.

在这个情况下, 如果扩容⼀个分⽚, 如何处理呢? 原有分⽚在环上的位置不动, 只要在环上新安排⼀个分⽚位置即可.

此时,只需要把 0 号分片上的部分数据,搬运给 3 号分⽚即可.1 号分⽚和 2 号分⽚管理的区间都是不变的.

但可以明显看出,我们只是减少了 0 号分片的压力,1 号分片和 2 号分片中存储的数据多于 0 号和 3 号分片。所以该方法存在数据分配不均匀的问题

优缺点

优点:⼤⼤降低了扩容时数据搬运的规模,提⾼了扩容操作的效率

缺点:数据分配不均匀(有的多有的少,数据倾斜).

哈希槽分区算法(Redis 使用)

为了解决上述问题(搬运成本⾼和数据分配不均匀),Redis cluster 引⼊了哈希槽(hash slots) 算法

获取哈希槽编号

复制代码
hash_slot = crc16(key) % 16384

其中 crc16 也是⼀种 hash 算法.

相当于是把整个哈希值, 映射到 16384 个槽位上,也就是 [0, 16383].然后再把这些槽位⽐较均匀的分配给每个分片.每个分⽚的节点都需要记录⾃⼰持有哪些哈希槽

假设当前有三个分⽚,⼀种可能的分配⽅式:

• 0 号分⽚: [0, 5461], 共 5462 个槽位

• 1 号分⽚: [5462, 10923], 共 5462 个槽位

• 2 号分⽚: [10924,16383],共 5460 个槽位

这⾥的分⽚规则是很灵活的.每个分⽚持有的槽位也不⼀定连续.每个分⽚的节点使⽤位图来表⽰⾃⼰持有哪些槽位.对于 16384 个槽位来说,需要 2048 个字节(2KB) ⼤⼩的内存空间表⽰.

⼀种可能的分配⽅式:

• 0 号分⽚: [0,4095],共 4096 个槽位

• 1 号分⽚:[5462,9557], 共 4096 个槽位

• 2 号分⽚: [10924, 15019], 共 4096 个槽位

• 3 号分⽚: [4096, 5461] + [9558, 10923] + [15019, 16383],共 4096 个槽位

我们在实际使⽤ Redis 集群分⽚的时候, 不需要⼿动指定哪些槽位分配给某个分⽚,只需要告 诉某个分⽚应该持有多少个槽位即可,Redis 会⾃动完成后续的槽位分配, 以及对应的 key 搬运的⼯作.

此处还有两个问题:

问题⼀: Redis 集群是最多有 16384 个分⽚吗?

并⾮如此. 如果⼀个分⽚只有⼀个槽位, 这对于集群的数据均匀其实是难以保证的. 实际上 Redis 的作者建议集群分⽚数不应该超过 1000

⽽且, 16000 这么⼤规模的集群, 本⾝的可⽤性也是⼀个⼤问题.⼀个系统越复杂,出现故障的概率是越⾼的.

问题⼆:为什么是 16384 个槽位?

• 节点之间通过⼼跳包通信.⼼跳包中包含了该节点持有哪些 slots.这个是使⽤位图这样的数据结构 表⽰的.表⽰ 16384 (16k) 个 slots,需要的位图⼤⼩是 2KB. 如果给定的 slots 数更多了,⽐如 65536 个了,此时就需要消耗更多的空间,8 KB 位图表⽰了. 8 KB,对于内存来说不算什么,但是在频繁的⽹络⼼跳包中,还是⼀个不⼩的开销的

• 另⼀⽅⾯, Redis 集群⼀般不建议超过 1000 个分⽚.所以 16k 对于最⼤ 1000 个分⽚来说是⾜够⽤ 的,同时也会使对应的槽位配置位图体积不⾄于很⼤.

相关推荐
喵手11 分钟前
如何利用Java的Stream API提高代码的简洁度和效率?
java·后端·java ee
-Xie-12 分钟前
Maven(二)
java·开发语言·maven
IT利刃出鞘25 分钟前
Java线程的6种状态和JVM状态打印
java·开发语言·jvm
薛晓刚29 分钟前
当MySQL的int不够用了
数据库
SelectDB技术团队1 小时前
Apache Doris 在菜鸟的大规模湖仓业务场景落地实践
数据库·数据仓库·数据分析·apache doris·菜鸟技术
星空下的曙光1 小时前
mysql 命令语法操作篇 数据库约束有哪些 怎么使用
数据库·mysql
小楓12011 小时前
MySQL數據庫開發教學(一) 基本架構
数据库·后端·mysql
天天摸鱼的java工程师1 小时前
Java 解析 JSON 文件:八年老开发的实战总结(从业务到代码)
java·后端·面试
白仑色1 小时前
Spring Boot 全局异常处理
java·spring boot·后端·全局异常处理·统一返回格式
染落林间色1 小时前
达梦数据库-实时主备集群部署详解(附图文)手工搭建一主一备数据守护集群DW
数据库·sql