【Redis】--集群
文章目录
- 1. Redis集群
- 1.1 基本概念
- 1.2 数据分片算法
- 1.2.1 哈希求余
- 1.2.2 一致性哈希算法
- 1.2.3 哈希槽分区算法(Redis使用的分片算法)
- 1.3 主节点宕机处理流程
- 1.3.1 故障判定(识别出某个主节点是否是挂了)
- 1.3.2 故障迁移
1. Redis集群
1.1 基本概念
广义上的集群:只要是多个机器,构成了分布式系统,都可以被称为一个集群。
狭义上的集群:主要是解决存储空间不足的问题。
采用Redis集群的方式就是引入多台机器,每台机器存储一部分数据。
1.2 数据分片算法
1.2.1 哈希求余
借助哈希函数,吧一个key映射到整数,再针对数组的长度求余,就可以得到一个数据下标。
设有 N 个分片, 使⽤ [0, N-1] 这样序号进行编号.
针对某个给定的 key, 先计算 hash 值, 再把得到的结果 % N, 得到的结果即为分片编号.
后续如果要取某个 key 的记录, 也是针对 key 计算 hash , 再对 N 求余, 就可以找到对应的分片编号了.
优点: 简单高效,数据分布均匀。
缺点: 一旦需要进行扩容,原有的映射规则就被破坏了,需要搬运大量的数据,开销极大。
这种方式往往是不能直接在生产环境上操作的,只能通过替换的方式来实现扩容。
1.2.2 一致性哈希算法
上面的哈希求余的方法判断key是属于哪个分片的,是交替出现的,导致搬运成本大。在一致性哈希中把交替出现改成了连续出现。
- 把 0 -> 2^32-1 这个数据空间, 映射到⼀个圆环上. 数据按照顺时针⽅向增长.
- 假设当前存在三个分⽚, 就把分⽚放到圆环的某个位置上.
- 假定有⼀个 key, 计算得到 hash 值 H, 那么这个 key 映射到哪个分片呢? 规则很简单, 就是从 H 所在位置, 顺时针往下找, 找到的第⼀个分片, 即为该 key 所从属的分片.
扩容:
原有分片在环上的位置不动,在环上新安排一个分片位置即可。
上面新添加了一个3号分片,只需要把0号分片上的部分数据搬运给3号分片即可。
缺点: 几个分片上的数据不均匀了。
1.2.3 哈希槽分区算法(Redis使用的分片算法)
哈希槽分区算法本质上就是把一致性哈希和哈希求余结合一下。
hash_slot = crc16(key) % 16384
16384 = 214
crc16是一种hash算法
hash_slot:哈希槽
相当于是把整个哈希值映射到16384个槽位上,然后再把这些槽位比较均匀的分配给每个分片。每个分片都记录着自己持有哪些分片。
假设当前有三个分⽚, ⼀种可能的分配⽅式:
> 0 号分⽚: [0, 5461], 共 5462 个槽位
> 1 号分⽚: [5462, 10923], 共 5462 个槽位
> 2 号分⽚: [10924, 16383], 共 5460 个槽位
虽然不是严格意义上的均匀,但是差异非常小。这里只是一种可能的分片方式,实际的分片是非常灵活的,每个分片持有的槽位号,可能是连续的,也 可能真连续的。
每个分片都会使用位图这样的数据结构表示当前有多少个槽位。
16384个bit位,每一个bit位用0/1来区分自己整个分片当前是否持有 该槽位。
16384个bit位 = 2048个字节 = 大约2KB。
扩容
如果需要进行扩容, 比如新增⼀个 3 号分片, 就可以针对原有的槽位进行重新分配.
比如可以把之前每个分片持有的槽位, 各拿出⼀点, 分给新分片.
⼀种可能的分配方式:
• 0 号分片: [0, 4095], 共 4096 个槽位
• 1 号分片: [5462, 9557], 共 4096 个槽位
• 2 号分片: [10924, 15019], 共 4096 个槽位
• 3 号分片: [4096, 5461] + [9558, 10923] + [15019, 16383], 共 4096 个槽位
Reds中,当前某个分片包含哪些槽位都是可以手动配置的。
问题1:Redis集群是最多有16384个分片吗?
key是先映射到槽位上,再映射到分片上的。这样的话每个分片上只有一个槽位,那么就很难保证数据再各个分片上的均衡性。
如果每个分片包含的槽位比较多,如果槽位个数相当,就可以认为包含的key数量相当。
如果每个分片包含的槽位非常少,槽位个数不一定能直观的反应到key的数目。
问题2:为什么是16384个槽位
节点之间通过心跳包通信,心跳包中包含了该节点持有哪些slots.这个是使⽤位图这样的数据结构表示的. 表示 16384 (16k) 个 slots, 需要的位图大小是 2KB. 如果给定的 slots 数更多了, 比如 65536 个了, 此时就需要消耗更多的空间, 8 KB 位图表示了. 8 KB, 对于内存来说不算什么, 但是在频繁的网络心跳包中, 还是⼀个不小的开销的.
另⼀⽅⾯, Redis 集群⼀般不建议超过 1000 个分⽚. 所以 16k 对于最⼤ 1000 个分⽚来说是⾜够⽤的, 同时也会使对应的槽位配置位图体积不⾄于很⼤.
1.3 主节点宕机处理流程
1.3.1 故障判定(识别出某个主节点是否是挂了)
集群中所以接待你都会周期性的使用心跳包进行通信。
- 节点 A 给 节点 B 发送 ping 包, B 就会给 A 返回⼀个 pong 包. ping 和 pong 除了 message type属性之外, 其他部分都是⼀样的. 这⾥包含了集群的配置信息(该节点的id, 该节点从属于哪个分⽚, 是主节点还是从节点, 从属于谁, 持有哪些 slots 的位图…).
- 每个节点, 每秒钟, 都会给⼀些随机的节点发起 ping 包, ⽽不是全发⼀遍. 这样设定是为了避免在节点很多的时候, ⼼跳包也⾮常多(⽐如有 9 个节点, 如果全发, 就是 9 * 8 有 72 组⼼跳了, ⽽且这是按照 N^2 这样的级别增⻓的).
- 当节点 A 给节点 B 发起 ping 包, B 不能如期回应的时候, 此时 A 就会尝试重置和 B 的 tcp 连接, 看能否连接成功. 如果仍然连接失败, A 就会把 B 设为 PFAIL 状态(相当于主观下线).
- A 判定 B 为 PFAIL 之后, 会通过 redis 内置的 Gossip 协议, 和其他节点进⾏沟通, 向其他节点确认 B 的状态. (每个节点都会维护⼀个⾃⼰的 “下线列表”, 由于视⻆不同, 每个节点的下线列表也不⼀定相同).
- 此时 A 发现其他很多节点, 也认为 B 为 PFAIL, 并且数⽬超过总集群个数的⼀半, 那么 A 就会把 B 标记成 FAIL (相当于客观下线), 并且把这个消息同步给其他节点(其他节点收到之后, 也会把 B 标记成 FAIL).
⾄此, B 就彻底被判定为故障节点了.
1.3.2 故障迁移
上后面的B如果是从节点,那么就不需要进行故障迁移了。
如果是主节点,那么就需要进行故障迁移了。
故障迁移就是把从节点提拔成主节点(Raft算法),继续给整个Redis集群提供支持。
-
从节点判定⾃⼰是否具有参选资格. 如果从节点和主节点已经太久没通信(此时认为从节点的数据和主节点差异太⼤了), 时间超过阈值, 就失去竞选资格.
-
具有资格的节点, ⽐如 C 和 D, 就会先休眠⼀定时间. 休眠时间 = 500ms 基础时间 + [0, 500ms] 随机时间 + 排名 * 1000ms. offset 的值越⼤, 则排名越靠前(越⼩).
-
⽐如 C 的休眠时间到了, C 就会给其他所有集群中的节点, 进⾏拉票操作. 但是只有主节点才有投票资格.
-
主节点就会把⾃⼰的票投给 C (每个主节点只有 1 票). 当 C 收到的票数超过主节点数⽬的⼀半, C 就会晋升成主节点. (C ⾃⼰负责执⾏ slaveof no one, 并且让 D 执⾏ slaveof C).
-
同时, C 还会把⾃⼰成为主节点的消息, 同步给其他集群的节点. ⼤家也都会更新⾃⼰保存的集群结构
信息.
注意这里和哨兵是不一样的。哨兵是先选出一个leader,,leader负责找出一个从节点升级成主节点;而集群是直接投票选出一个新的主节点。