I'm Aron

Redis 最佳实践(四):部署架构、Cluster 扩缩容与高可用

3616 字
18 分钟
Redis 最佳实践(四):部署架构、Cluster 扩缩容与高可用

1. 架构选择#

Redis 的部署形态解决不同问题:

架构数据分片自动故障转移容量扩展主要限制
单机垂直扩容单点故障,容量和吞吐受单节点限制
主从无(需人工或外部系统)可扩展部分只读流量异步复制,读可能旧,主库仍是写瓶颈
Sentinel可扩展部分只读流量数据集仍在单主节点,客户端需支持 Sentinel
Redis Cluster16384 槽水平扩容量和写吞吐跨槽多 Key、运维和客户端更复杂

选择原则:

单主节点能否在安全水位承载未来 12~18 个月的数据、写入和带宽?
├─ 能
│ ├─ 是否需要自动高可用?是 → 主从 + Sentinel
│ └─ 否 → 单机/主从,但生产通常仍建议有副本与备份
└─ 不能 → Redis Cluster
├─ 业务是否大量依赖跨 Key 原子操作?
│ ├─ 是 → 重构 Key 边界、Hash Tag,或重新评估架构
│ └─ 否 → 按槽水平拆分
└─ 客户端、监控、迁槽、备份和演练是否已就绪?

不要仅用“Redis 单机能跑多少 QPS”做决策。实际瓶颈可能是内存、BigKey、网络带宽、持久化、命令复杂度、故障恢复时间或单线程 CPU。

2. 主从与 Sentinel 基础#

2.1 异步复制语义#

client → primary:写入并收到确认
primary → replica:异步传播命令

主节点确认写入时,副本可能尚未收到。因此主节点突然故障并切换副本时,少量已确认写可能丢失。WAIT/WAITAOF 可提高特定写的复制/持久化确认程度,但不能把 Redis 变成网络分区下的强一致分布式数据库。

2.2 读副本#

读写分离适合:

  • 允许短暂旧读的查询;
  • 报表、推荐等可容忍复制延迟的流量;
  • 大读取需要与主库隔离。

不适合:

  • 写后必须立即读到新值;
  • 库存、余额、锁等强一致判断;
  • 依赖副本作为零延迟灾备。

应用必须监控复制延迟,并在副本落后时降级或切回主库。主节点的网络和 CPU 仍承担复制成本,增加副本并非免费扩容。

2.3 Sentinel 注意事项#

  • 推荐部署奇数个 Sentinel,并分布在独立故障域;
  • Sentinel 数量和 quorum 不是同一概念;故障确认与选举还涉及多数派;
  • 客户端必须支持 Sentinel 地址发现和主节点切换;
  • 故障转移期间会有短暂不可用,应用需设置有界重试和退避;
  • 旧主恢复后应作为副本重新加入,防止双主写入;
  • Sentinel 不做数据分片,无法解决单主内存或写吞吐上限。

3. Redis Cluster 核心模型#

3.1 16384 个槽#

slot = CRC16(key) mod 16384
master A:slots 0~5460
master B:slots 5461~10922
master C:slots 10923~16383

Key 映射到槽,槽再归属于主节点。扩缩容时移动的是槽及槽中的 Key,而不是简单修改客户端一致性哈希环。

3.2 Hash Tag#

Key 中第一对有效非空 {...} 的内容用于计算槽:

order:{1001}:detail
order:{1001}:items

它们同槽,可用于同槽多 Key 操作、事务或脚本。Hash Tag 应表达最小原子聚合边界,例如订单 ID 或用户购物车 ID。

错误做法:

# 全租户共用一个 tag,导致所有数据落在一个槽
tenant:{t1}:user:1
tenant:{t1}:order:1
tenant:{t1}:product:1

3.3 节点通信#

Cluster 节点之间形成全连接拓扑,并通过集群总线和 gossip 交换状态。增加节点会增加:

  • 节点连接数;
  • 心跳/gossip 流量;
  • 故障检测和配置传播复杂度;
  • 运维、升级、备份和告警对象数量。

官方规范以约 1000 节点作为设计规模量级,不代表实际生产应接近该数字。大多数团队应使用远少于此的节点,并通过压测、故障域和运维能力确定上限。

4. 多 Key、事务和脚本#

原课件中“Cluster 不支持 Lua 和事务”的说法不准确。准确结论是:

  • 单 Key 命令可正常路由;
  • 多 Key 命令在所有 Key 同槽时可执行;
  • MULTI/EXEC、Lua/Function 所访问的 Key 通常必须同槽;
  • 跨槽原子事务不是 Redis Cluster 的通用能力;
  • 客户端将跨槽操作拆组后,只能得到各组独立成功/失败的最终一致语义。

设计方法:

需求方案
同一订单内多个 Key 必须原子{orderId} Hash Tag 共槽,控制订单规模
大量独立 Key 批量读取按槽/节点分组并行,重组结果
跨业务域强事务权威数据库完成事务,Redis 做缓存/派生数据
跨槽最终一致更新消息、任务表、幂等 requestId 和补偿

5. 完整覆盖与部分可用#

cluster-require-full-coverage 决定部分槽不可用时集群如何响应:

配置行为取向适合思路
yes只要存在未覆盖槽,整个集群停止接受普通查询业务要求完整 Key 空间语义,宁可整体不可用
no仍允许访问有可用槽的数据分区业务可独立降级,接受部分 Key 空间不可用

不能统一推荐 no。例如一个请求同时依赖用户、库存和订单三个槽,部分返回可能比整体失败更危险。选择前必须明确:

  • 客户端能否识别并处理部分不可用;
  • 上层接口是否允许返回不完整数据;
  • 是否会把缺失误判为“不存在”;
  • 恢复后是否有对账/补偿。

6. 容量规划#

6.1 单分片安全水位#

按最忙分片而不是集群平均值规划:

分片内存预算
= 数据集
+ Key/对象开销
+ 过期与淘汰波动
+ 客户端/复制/AOF 缓冲
+ allocator 碎片
+ fork COW 峰值
+ 扩缩容迁移余量

“每个实例 4 GB 或 8 GB”只是历史经验,不是限制。实例过大可能导致:

  • fork、RDB/AOF、全量同步和重启更慢;
  • 节点故障迁移的数据更多;
  • COW 峰值更难预留;
  • 迁槽和备份恢复时间更长。

实例过小则会导致节点太多、gossip 和连接开销高、运维复杂。应在故障恢复时间和节点数量之间压测取舍。

6.2 内存倾斜#

槽位数量均匀不等于数据均匀:

节点 A:5461 个槽,但包含多个超大 Key
节点 B:5461 个槽,都是小 Key

因此重平衡需要看:

  • 每节点内存、Key 数、QPS、网络;
  • 每槽 Key 数和内存分布;
  • BigKey/HotKey 所在槽;
  • 写入增长速度;
  • 副本承载能力。

6.3 网络带宽#

Redis 可能先耗尽网络而不是 CPU:

业务响应流量
+ 客户端请求流量
+ 主从复制
+ Cluster bus
+ 迁槽/全量同步
+ 备份流量

扩容和故障恢复期间,复制/迁移流量会与在线业务竞争。必须限速、分批并在低峰执行,同时保留故障切换带宽。

7. 槽位与负载均衡#

7.1 倾斜来源#

  • Hash Tag 粒度过大;
  • 少量 BigKey 占据大量内存;
  • HotKey 将 QPS 集中到单槽;
  • 业务 ID 分布或 Key 命名异常;
  • 扩容后只移动相同数量槽,未按数据/流量再平衡;
  • 单个客户端错误路由或连接池倾斜。

7.2 治理方式#

倾斜处理
内存倾斜拆 BigKey、迁槽、重新分配槽
QPS/网络倾斜拆 HotKey、客户端本地缓存、热点副本、业务分流
大 Tag 倾斜缩小原子边界,重新设计 Tag
节点规格不一致统一规格,或有意识按能力分配槽并纳入自动化

单纯迁移 HotKey 所在槽只是把热点从 A 搬到 B,不会消除单节点瓶颈。

8. 扩容与缩容#

8.1 扩容流程#

1. 校验新节点版本、配置、安全和资源
2. 将新主节点加入 Cluster
3. 为主节点配置副本并跨故障域放置
4. 小批次迁移槽,限制迁移速率
5. 观察 P99、网络、复制、错误和重定向
6. 逐步完成重平衡
7. 校验 16384 槽覆盖、节点状态和数据分布

迁槽期间客户端可能收到 MOVEDASK。必须使用支持 Cluster 拓扑发现、重定向和连接复用的客户端,并验证版本兼容性。

8.2 缩容流程#

1. 确认剩余节点容量与故障余量
2. 把待下线主节点的所有槽迁出
3. 校验该节点不再持有槽/业务 Key
4. 调整或移除副本关系
5. 从 Cluster 忘记并下线节点
6. 更新监控、备份、服务发现和资产记录

缩容最容易忽略故障余量:正常状态能装下数据,不代表再故障一台时仍能运行。

9. 高可用与故障转移#

9.1 故障域#

每个主节点的副本应尽量分布在不同:

  • 物理机;
  • 机架;
  • 可用区;
  • 电源/网络故障域。

“每主一副本”是常见起点,但不是所有 SLA 的答案。需要考虑同时故障、维护窗口和副本迁移。

9.2 数据丢失窗口#

Redis Cluster 使用异步复制。以下情况可能丢失已确认写:

  • 主节点确认后、复制前故障;
  • 客户端与少数派主节点继续通信;
  • 故障检测和网络分区窗口;
  • 故障转移选中的副本落后。

可降低风险的手段:

  • 合理配置 min-replicas-to-writemin-replicas-max-lag
  • 对关键写使用 WAIT/WAITAOF 并理解其边界;
  • 监控复制 offset 和 lag;
  • 把真正不能丢的事务记录在更合适的权威系统;
  • 做网络分区和故障切换演练。

这些手段通常以可用性或延迟为代价。

9.3 脑裂与网络分区#

Cluster 会在多数主节点可达、故障主节点有可用副本时进行故障转移。客户端不应固定写某台 IP;要通过 Cluster-aware 客户端刷新拓扑。分区恢复后,旧主的未同步写可能被丢弃,因此业务不能假设所有返回成功的写最终都保留。

10. 客户端最佳实践#

10.1 必备能力#

  • 支持 MOVEDASK 重定向;
  • 自动发现并定期刷新槽位映射;
  • 对每节点使用有界连接池;
  • 拓扑变化时避免同时大量重连;
  • 超时、重试、退避和最大重定向次数可配置;
  • 多 Key 操作在发送前能检测同槽;
  • 支持 TLS、ACL 用户名/密码和证书轮换;
  • 暴露按节点、命令、异常类型的指标。

10.2 重试原则#

情况建议
MOVED更新槽映射并重试到目标节点
ASK临时向目标节点发送 ASKING 后执行,不永久改映射
连接失败有界重试、指数退避、熔断
超时且写状态未知仅自动重试幂等操作;非幂等写需业务去重
CLUSTERDOWN/槽不可用快速失败或降级,不要无限重试放大故障

10.3 不要缓存过久的拓扑#

扩缩容、故障转移和迁槽会改变槽归属。客户端如果只在启动时加载一次拓扑,会产生大量重定向甚至访问旧节点。应允许定期刷新、被动根据 MOVED 刷新,以及运维触发刷新。

11. Cluster 运维#

11.1 常用检查#

CLUSTER INFO
CLUSTER NODES
CLUSTER SHARDS
CLUSTER SLOTS
CLUSTER KEYSLOT some:key
CLUSTER COUNTKEYSINSLOT 1234
INFO replication
INFO memory

不同命令的推荐程度和输出会随版本变化。自动化解析应锁定版本并有兼容测试,不要依赖人为拼接文本的稳定列位置。

11.2 监控表#

类别重点
集群状态cluster_state、已分配/失败槽、已知节点
节点主从角色、flags、ping/pong、连接状态
重定向MOVED/ASK 数量、拓扑刷新频率
复制lag、offset、全量/部分同步、backlog
容量每节点 used_memory、RSS、Key 数、增长率
性能每节点 CPU、P99、命令量、网络吞吐
数据分布槽内存、BigKey、HotKey、Hash Tag 倾斜
后台任务RDB、AOF rewrite、迁槽、fork/COW

11.3 运维动作隔离#

避免同时进行多个重负载动作:

迁槽 + AOF rewrite + 全量复制 + 备份

它们会竞争 CPU、内存、磁盘和网络。编排系统应有互斥或并发上限,并在指标异常时自动暂停,而不是只按固定速率推进。

12. 备份、恢复与升级#

12.1 备份#

Cluster 的数据分布在多个主分片,备份必须覆盖所有分片并记录拓扑。各分片快照时间可能不同,因此它不天然是跨分片一致性快照。若业务要求全局一致恢复,需要应用级停写、时间点协议或平台提供的能力。

12.2 恢复#

演练应验证:

  • 所有分片文件可读;
  • Redis 版本和模块兼容;
  • 槽位映射正确;
  • ACL、TLS、持久化和内核参数正确;
  • 应用能刷新拓扑并恢复流量;
  • Key 数、抽样数据和业务对账通过;
  • RTO 达标。

12.3 滚动升级#

通常先升级副本,验证后故障转移,再处理原主节点。升级前确认:

  • RDB/AOF 格式和配置兼容;
  • 客户端支持目标版本;
  • 脚本、模块和命令无兼容问题;
  • 新旧版本混跑窗口符合官方支持;
  • 有明确回滚方案,但数据文件降级兼容不能想当然。

13. 是否真的需要 Cluster#

优先使用 Sentinel 而非 Cluster 的常见条件:

  • 单主节点能满足容量、写 QPS、网络和恢复时间;
  • 业务大量使用多 Key 原子操作、事务或脚本;
  • 团队尚无 Cluster 客户端和运维能力;
  • 扩展需求主要是可容忍旧读的查询。

应考虑 Cluster 的常见条件:

  • 数据集超过单节点安全内存水位;
  • 写吞吐、主线程 CPU 或网络需要水平扩展;
  • 单节点备份/恢复和故障转移时间不可接受;
  • 数据可以按 Key 边界自然分区;
  • 团队能够承担槽位、迁移、客户端和多分片运维。

架构越复杂,理论容量越大,但故障模式也越多。能用简单架构稳定满足目标时,不应只因“以后可能增长”过早上 Cluster;一旦增长预测和演练证明单机不可持续,也不能等到满载后才迁移。

评论区

文章目录