Redis Cluster 是 Redis 3.0 提供的一种分布式解决方案, 允许数据在多个节点之间分散存储, 从而实现高可用性和可扩展性。
特点:
- 分片: Redis Cluster 将数据分散到多个节点, 通过哈希槽 (hash slots) 机制将键映射到不同的节点上。总共有 16384 个哈希槽, 每个节点负责一部分槽。
- 高可用性: Redis Cluster 支持主从复制, 每个主节点可以有多个从节点, 从节点可以在主节点故障时自动提升为主节点。
- 自动故障转移: 当主节点宕机时, Cluster 会自动检测并选择从节点提升为新的主节点, 保持服务的可用性。
- 无中心化: Redis Cluster 是去中心化的, 没有单点故障。所有节点都可以处理请求, 提升了系统的健壮性。
解决的问题:
- 数据存储限制: 单个 Redis 实例的内存限制使其在处理大规模数据时受到约束, Redis Cluster 通过分片技术克服了这一限制, 支持更大规模的数据存储。
- 高可用性需求: 在传统的 Redis 配置中, 主节点故障会导致服务不可用。Redis Cluster 通过主从复制和自动故障转移确保服务的连续性, 满足高可用性的需求。
- 负载均衡: 随着数据量和访问量的增加, Redis Cluster 可以通过增加节点和分配哈希槽实现负载均衡, 避免某一节点过载。
1 Redis Cluster 的搭建
Redis Cluster 可以看成是由多个 Redis 实例组成的数据集合。
客户端不需要关注数据的子集到底存储在哪个节点, 只需要关注这个集合整体。
那么如何搭建一个这样的集群呢?
注: 这里用一台集群搭建集群, Ip 地址为 192.169.10.10, 7000/7001/7002 为主节点的端口, 8000/8001/8002 为对应的从节点的端口。
1.1 修改配置
修改 redis.conf 配置文件中的 3 个参数
- cluster-enabled yes
- cluster-config-file “node-7000.conf”
- cluster-node-timeout 5000
其他的参数和单个 Redis 实例的一样。
cluster-enabled yes
: Redis 实例可以分为单机模式 (standalone) 和集群模式 (cluster)。 yes 开启为集群模式。
cluster-config-file
: 该参数指定了集群配置文件的位置。每个节点在运行过程中, 会维护一份集群配置文件; 每当集群信息发生变化时 (如增减节点), 集群内所有节点会将最新信息更新到自己维护的配置文件。
当节点重启后, 会重新读取该配置文件, 获取集群信息, 可以方便的重新加入到集群中。
也就是说当 Redis 节点以集群模式启动时, 会首先寻找是否有集群配置文件, 如果有则使用文件中的配置启动, 如果没有, 则初始化配置并将配置保存到文件中。 集群配置文件由 Redis 节点维护, 不需要人工修改。
cluster-node-timeout
: 节点之间心跳超时时间
cluster-require-full-coverage
: 默认值为 yes, 将其修改为 no, 表示 Redis 节点的槽没有完全分配时,集群仍可以上线。
1.2 启动节点
通过 redis-server redis.conf
配置文件启动 Redis。
可以通过 cluster nodes 查看当前的节点集群信息。
1.3 节点握手
执行 redis-cli –cluster create 命令
节点启动以后是相互独立的,并不知道其他节点存在, 需要进行节点握手。
将独立的节点组成一个网络。注下面的操作, 不能使用 localhost 和 127.0.0.1, 需要使用局域网 Ip 或 公网 Ip。
redis-cli --cluster create 192.169.10.10:7000 192.169.10.10:7001 192.169.10.10:7002 192.169.10.10:8000 192.169.10.10:8001 192.169.10.10:8002 --cluster-replicas 1
–cluster-replicas 1 表示每个主节点有 1 个从节点, 后面的多个 {ip:port} 表示节点地址。(默认: 所有节点平均分成 2 组, 前面一组为主节点, 后面一组为从节点)
执行创建命令后, Redis 会给出一个预计的方案, 对 6 个节点分配 3 主 3 从, 如果认为没有问题,输入 yes 确认
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.169.10.10:8000 to 192.169.10.10:7000
Adding replica 192.169.10.10:8001 to 192.169.10.10:7001
Adding replica 192.169.10.10:8002 to 192.169.10.10:7002
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c 192.169.10.10:7000
slots:[0-5460] (5461 slots) master
M: 8c878b45905bba3d7366c89ec51bd0cd7ce959f8 192.169.10.10:7001
slots:[5461-10922] (5462 slots) master
M: aeeb7d7076d9b25a7805ac6f508497b43887e599 192.169.10.10:7002
slots:[10923-16383] (5461 slots) master
S: ebc479e609ff8f6ca9283947530919c559a08f80 192.169.10.10:8000
replicates aeeb7d7076d9b25a7805ac6f508497b43887e599
S: 49385ed6e58469ef900ec48e5912e5f7b7505f6e 192.169.10.10:8001
replicates dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c
S: 8d6227aefc4830065624ff6c1dd795d2d5ad094a 192.169.10.10:8002
replicates 8c878b45905bba3d7366c89ec51bd0cd7ce959f8
Can I set the above configuration? (type 'yes' to accept):
输入 yes 后
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
..
>>> Performing Cluster Check (using node 192.169.10.10:7000)
M: dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c 192.169.10.10:7000
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 8c878b45905bba3d7366c89ec51bd0cd7ce959f8 192.169.10.10:7001
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: ebc479e609ff8f6ca9283947530919c559a08f80 192.169.10.10:8002
slots: (0 slots) slave
replicates aeeb7d7076d9b25a7805ac6f508497b43887e599
S: 49385ed6e58469ef900ec48e5912e5f7b7505f6e 192.169.10.10:8000
slots: (0 slots) slave
replicates dfdc9c0589219f727e4fd0ad8dafaf7e0cfb4f1c
M: aeeb7d7076d9b25a7805ac6f508497b43887e599 192.169.10.10:7002
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 8d6227aefc4830065624ff6c1dd795d2d5ad094a 192.169.10.10:8001
slots: (0 slots) slave
replicates 8c878b45905bba3d7366c89ec51bd0cd7ce959f8
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
上面的操作是 Redis 提供的脚步式搭建, 隐藏了一些细节。如果想要体验完整的搭建过程, 可以参考 Redis 超详细的手动搭建Cluster集群步骤, 完全的通过命令的方式搭建集群。
1.4 Redis Cluster 相关的命令
集群命令
命令 | 效果 |
---|---|
cluster info | 打印集群的信息 |
cluster nodes | 列出集群当前已知的所有节点 (node), 以及这些节点的相关信息 |
cluster meet |
将 ip 和 port 所指定的节点添加到集群当中, 让它成为集群的一份子, 这时候没有主从关系 |
cluster forget |
从集群中移除 node_id 指定的节点 (保证空槽道) |
cluster replicate |
将当前节点设置为 node_id 指定的节点的从节点 |
cluster saveconfig | 将节点的配置文件保存到硬盘里面 |
槽slot命令
命令 | 效果 |
---|---|
cluster addslots [slot …] | 将一个或多个槽 (slot) 指派 (assign) 给当前节点 |
cluster delslots [slot …] | 移除一个或多个槽对当前节点的指派 |
cluster flushslots | 移除指派给当前节点的所有槽, 让当前节点变成一个没有指派任何槽的节点 |
cluster setslot node |
将槽 slot 指派给 node_id 指定的节点, 如果槽已经指派给另一个节点, 那么先让另一个节点删除该槽, 然后再进行指派 |
cluster setslot migrating |
将本节点的槽 slot 迁移到 node_id 指定的节点中 |
cluster setslot importing |
从 node_id 指定的节点中导入槽 slot 到本节点 |
cluster setslot stable | 取消对槽 slot 的导入 (imort) 或者迁移 (migrate) |
键命令
命令 | 效果 |
---|---|
cluster keyslot |
计算键 key 应该被放置在哪个槽上 |
cluster countkeysinslot |
返回槽 slot 目前包含的键值对数量 |
cluster getkeysinslot |
返回 count 个 slot 槽中的键 |
2 故障转移
集群的实现与哨兵思路类似: 通过定时任务发送 PING 消息检测其他节点状态。节点下线分为主观下线和客观下线, 客观下线后选取从节点进行故障转移。
与哨兵一样, 集群只实现了主节点的故障转移, 从节点故障时只会被下线, 不会进行故障转移。因此, 使用集群时, 应谨慎使用读写分离技术, 因为从节点故障会导致读服务不可用, 可用性变差。
大体是:
- slave 发现自己的 maste 变为 Fail 状态, 偏尝试进行 Failover, 以期成为新的 master
- slave 将自己记录的集群 currentEpoch + 1, 然后广播 FAILOVER_AUTH_REQUEST 信息
- 其他节点收到改消息后, 只有 master 节点会进行响应, 判断请求这的合法性, 并发送 FAILOVER_AUTH_ACK, 对每一个 epoch 只发送一次 ack
- 尝试 Failover 的 slave 收集 FAILOVER_AUTH_ACK
- 超过半数后变成新的 master
- 广播 Pong 通知其他集群节点
节点数量: 在故障转移阶段, 需要由主节点投票选出哪个从节点成为新的主节点, 从节点选举胜出需要的票数为 N/2+1, 其中 N 为主节点数量 (包括故障主节点), 但故障主节点实际上不能投票。
因此为了能够在故障发生时顺利选出从节点, 集群中至少需要3个主节点 (且部署在不同的物理机上)。
故障转移时间: 从主节点故障发生到完成转移, 所需要的时间主要消耗在主观下线识别、主观下线传播、选举延迟等几个环节。具体时间与参数 cluster-node-timeout 有关, 一般来说:
故障转移时间(毫秒) ≤ 1.5 * cluster-node-timeout + 1000
cluster-node-timeout 的默认值为 15000ms (15s), 因此故障转移时间会在 20s 量级
3 Hash Tag
有些 multi key 操作是不能跨阶段的, 如果要让某些数据统一分配到同一个节点上, 可以借助 Hast Tag 功能。
Hash Tag 原理是: 当一个 key 包含 {} 的时候, 不对整个 key 做 hash, 而仅对 {} 包括的字符串做 hash。
Hash Tag 可以让不同的 key 拥有相同的 hash 值, 从而分配在同一个槽里, 这样针对不同 key 的批量操作 (mget/mset等), 以及事务、Lua 脚本等都可以支持。
Hash Tag 可能会带来数据分配不均的问题, 这时可以
- 调整不同节点中槽的数量,使数据分布尽量均匀
- 避免对热点数据使用 Hash Tag, 导致请求分布不均
4 Redis Cluster 的不足
- Client 实现复杂, 驱动要求实现 Smart Client, 缓存 slots mapping 信息并及时更新, 提高了开发难度, 客户端的不成熟影响业务的稳定性
- 节点会因为某些原因发生阻塞 (阻塞时间大于 cluster-node-timeout), 被判断下线, 这种 failover 是没必要的
- 数据通过异步复制, 不保证数据的强一致性
- 多个业务使用同一套集群时, 无法根据统计区分冷热数据, 资源隔离性较差, 容易出现相互影响的情况