为什么需要 Redis Cluster
为什么需要 Redis Cluster?
- 需要存储的数据量上来后,单机很难支持那么高的内存容量,比如 100T 内存的服务器不现实
- 需要支持的并发量上来了
- 数据量在 25GB,RDB 耗时就快到秒级别了。RDB fork 影响到 Redis 操作慢。
通常情况下,更建议使用 Redis 切片集群这种方案,更能满足高并发场景下分布式缓存的要求。
Redis 切片集群就是部署多台 Redis 主节点(master),这些节点之间平等,并没有主从之说,同时对外提供读/写服务。缓存的数据库相对均匀地分布在这些 Redis 实例上,客户端的请求通过路由规则转发到目标 master 上。
Redis Cluster 通过分片(Sharding) 来进行数据管理,提供主从复制 (Master-Slave Replication)、故障转移(Failover) 等开箱即用的功能,可以非常方便地帮助我们解决 Redis 大数据量缓存以及 Redis 服务高可用的问题。
Redis Cluster 这种方案可以很方便地进行横向拓展(Scale Out),内置了开箱即用的解决方案。当 Redis Cluster 的处理能力达到瓶颈无法满足系统要求的时候,直接动态添加 Redis 节点到集群中即可。根据官方文档中的介绍,Redis Cluster 支持扩展到 1000 个节点。反之,当 Redis Cluster 的处理能力远远满足系统要求,同样可以动态删除集群中 Redis 节点,节省资源。
总结一下 Redis Cluster 的主要优势:
- 可以横向扩展缓解写压力和存储压力,支持动态扩容和缩容;
- 具备主从复制、故障转移(内置了 Sentinel 机制,无需单独部署 Sentinel 集群)等开箱即用的功能。
架构形式
Redis Cluster 至少需要 3 个 master 以及 3 个 slave,也就是说每个 master 必须有 1 个 slave。master 和 slave 之间做主从复制,slave 会实时同步 master 上的数据。
不同于普通的 Redis 主从架构,这里的 slave 不对外提供读服务,主要用来保障 master 的高可用,当 master 出现故障的时候替代它。
master 宕机。若一个 slave 直接替代,若多个 slave,Redis Cluster 总是希望数据最完整的 slave 被提升为新的 master。
Redis Cluster 是去中心化的(各个节点基于 Gossip 进行通信),任何一个 master 出现故障,其它的 master 节点不受影响,因为 key 找的是哈希槽而不是 Redis 节点。不过,Redis Cluster 至少要保证宕机的 master 有一个 slave 可用。
如果宕机的 master 无 slave 的话,为了保障集群的完整性,保证所有的哈希槽都指派给了可用的 master ,整个集群将不可用。这种情况下,还是想让集群保持可用的话,可以将 cluster-require-full-coverage 这个参数设置成 no, cl uster-require-full-coverage 表示需要 16384 个 slot 都正常被分配的时候 Redis Cluster 才可以对外提供服务。
如果我们想要添加新的节点比如 master4、master5 进入 Redis Cluster 也非常方便,只需要重新分配哈希槽即可。
如果我们想要移除某个 master 节点的话,需要先将该节点的哈希槽移动到其他节点上,这样才可以进行删除,不然会报错。
数据分片方式
Redis Cluster 采用哈希槽(Hash Slot) 来处理数据和实例之间的映射关系。一个集群共有 16384 个哈希槽,每个 key 会基于 CRC16 算法计算出一个 16bit 值,然后再用 16bit 值对 16384 取模,对应哈希槽中。
在部署 Redis Cluster 时,可以使用 cluster create 命令创建集群,Redis 会自动将槽平均分布到集群实例上。例如 N 个实例,每个实例有 16384/N 个槽。也可以使用 cluster meet 命令手动创建实例间的连接形成集群,再使用 cluster addslots 命令指定每个实例上的哈希槽数目。在手动分配哈希槽时,需要把 16384 个槽都分配完,否则 Redis 集群无法正常工作。
节点通信方式
Redis Cluster 是一个典型的分布式系统,分布式系统中的各个节点需要互相通信。既然要相互通信就要遵循一致的通信协议,Redis Cluster 中的各个节点基于 Gossip 协议来进行通信共享信息,每个 Redis 节点都维护了一份集群的状态信息。
Redis Cluster 的节点之间会相互发送多种 Gossip 消息:
- MEET: 在 Redis Cluster 中的某个 Redis 节点上执行 CLUSTER MEET ip port 命令,可以向指定的 Redis 节点发送一条 MEET 信息,用于将其添加进 Redis Cluster 成为新的 Redis 节点。
- PING/PONG: Redis Cluster 中的节点都会定时地向其他节点发送 PING 消息,来交换各个节点状态信息,检查各个节点状态,包括在线状态、疑似下线状态 PFAIL 和已下线状态 FAIL。
- FAIL: Redis Cluster 中的节点 A 发现 B 节点 PFAIL ,并且在下线报告的有效期限内集群中半数以上的节点将 B 节点标记为 PFAIL,节点 A 就会向集群广播一条 FAIL 消息,通知其他节点将故障节点 B 标记为 FAIL 。
- …
有了 Redis Cluster 之后,不需要专⻔部署 Sentinel 集群服务了。Redis Cluster 相当于是内置了 Sentinel 机制,Redis Cluster 内部的各个 Redis 节点通过 Gossip 协议互相探测健康状态,在故障时可以自动切换。
cluster.h 文件中定义了所有的消息类型(源码地址: https://github.com/redis/redis/blob/7.0/src/cluster.h )。Redis 3.0 版本的时候只有 9 种消息类型,到了 7.0 版本的时候已经有 11 种消息类型了。
clusterMsgData 是一个联合体(union),可以为 PING,MEET,PONG、 FAIL 等消息类型。当消息为 PING、MEET 和 PONG 类型时,都是 ping 字段是被赋值的,这也就解释了为什么我们上面说 PING 、 PONG 和 MEET 实际上是同一种消息。
客户端如何定位数据?
- Redis 实例会将自己的哈希槽信息发送给相连接的其他实例来完成哈希槽分配信息的扩散。即 gossip 协议
- 客户端与集群实例建立连接后,实例会将哈希槽点分配情况发送给客户端。
- 客户端将哈希槽信息存在本地,当请求键值对时,会先计算哈希槽,再向相应实例发送请求。
客户端如何感知哈希槽变动?
- 当哈希槽已经变动完成:客户端将请求发送给 Redis 时,会收到
MOVED重定向指令,如MOVED 13320 172.16.19.5:6379告知 13320 哈希槽在另一个 IP 地址上。客户端会重新发送并更新本地哈希槽映射。 - 当哈希槽正在变动中:客户端将 GET 请求发送给 Redis 时,会收到一个
ASK重定向指令,如ASK 13320 172.16.19.5:6379,代表哈希槽正在迁移到新 IP 地址上。客户端需要向新地址发送ASKING命令,要求实例执行接下来的命令,然后再发送 GET 命令。但是 ASK 并不会要求客户端更新本地哈希槽映射。
为什么哈希槽是 16384 个?
CRC16 算法产生的校验码有 16 位,理论上可以产生 65536(2^16,0 ~ 65535)个值。为什么 Redis Cluster 的哈希槽偏偏选择的是 16384(2^14)个呢?
2015 年的时候,在 Redis 项目的 issues 区,已经有人提了类似的问题,地址: https://github.com/redis/redis/issues/2576 。Redis 作者 antirez 巨佬本人专 ⻔对这个问题进行了回复。
antirez 认为哈希槽是 16384(2 的 14 次方) 个的原因是:
- 正常的心跳包会携带一个节点的完整配置,它会以幂等的方式更新旧的配置,这意味着心跳包会附带当前节点的负责的哈希槽的信息。假设哈希槽采用 16384 ,则占空间 2k(16384/8)。假设哈希槽采用 65536,则占空间 8k(65536/8),这是令人难以接受的内存占用。
- 由于其他设计上的权衡,Redis Cluster 不太可能扩展到超过 1000 个主节点。
也就是说,65536 个固然可以确保每个主节点有足够的哈希槽,但其占用的空间太大。而且,Redis Cluster 的主节点通常不会扩展太多,16384 个哈希槽完全足够用了。
最后,总结一下 Redis Cluster 的哈希槽的数量选择 16384 而不是 65536 的主要原因:
- 哈希槽太大会导致心跳包太大,消耗太多带宽;
- 哈希槽总数越少,对存储哈希槽信息的 bitmap 压缩效果越好;
- Redis Cluster 的主节点通常不会扩展太多,16384 个哈希槽已经足够用了。
分配哈希槽的命令
CLUSTER ADDSLOTS slot [slot ...]: 把一组 hash slots 分配给接收命令的节点,时间复杂度为 O(N),其中 N 是 hash slot 的总数;CLUSTER ADDSLOTSRANGE start-slot end-slot [start-slot end-slot ...](Redis 7.0 后新加的命令):把指定范围的 hash slots 分配给接收命令的节点,类似于ADDSLOTS命令,时间复杂度为 O(N),其中 N 是起始 hash slot 和结束 hash slot 之间的 hash slot 的总数。CLUSTER DELSLOTS slot [slot ...]:从接收命令的节点中删除一组 hash slots;CLUSTER FLUSHSLOTS:移除接受命令的节点中的所有 hash slot;CLUSTER SETSLOT sLot MIGRATING node-id: 迁移接受命令的节点的指定 hash slot 到目标节点(node_id 指定)中;CLUSTER SETSLOT slot IMPORTING node-id:将目标节点 (node_id 指定)中的指定 hash slot 迁移到接受命令的节点中;