Redis 集群数据一致性
一致性是 Redis 工程师绕不开的话题。本文从一次真实脑裂事故讲起,串起主从复制、min-slaves、WAIT、Redlock 之间的权衡,给出不同业务场景下的推荐配置组合。
一次真实的事故:凌晨 3 点的主从切换
2024 年的某个凌晨,某条业务数据库的主 Redis 节点宕机。系统自动做了主从切换,新主顶上来。但 12 小时后,客服反馈某活动用户的积分被错误扣了。回看日志,问题起源于切换前后「旧主还活着」的窗口:
- 旧主在一段时间里仍接受写入。
- 新主从其他从节点晋升,旧主写入的数据「丢失」。
- 切换完成后,旧主最终被降为从,丢弃自身数据;新主与从完成全量同步。
这就是经典的「脑裂」。它背后是 Redis 异步复制 + 故障转移下的数据可见性窗口。
Redis 不是强一致的存储。它是「最终一致 + 高性能」。理解这一点才能讲一致性。
Redis 复制的核心机制
Redis 主从复制有两种触发方式:全量同步(full resync)和部分同步(partial resync)。判断标准是「主从是否还能在 repl_backlog 找到偏移量」。
异步复制带来的延迟导致「刚刚写入主的数据不可见」。这个时间窗口大约 50ms 到几秒,取决于网络和写入流量。
redis-cli info replication | grep -E "role|connected_slaves|master_repl_offset"
# 输出角色 + 从节点数 + 偏移量
min-slaves-to-write 与脑裂防御
Redis 提供两个配置来约束主节点写入行为:
min-slaves-to-write:至少有 N 个从节点在线,主才能写入。min-slaves-max-lag:从节点与主的延迟不能超过 X 秒。
常见组合:min-slaves-to-write 1, min-slaves-max-lag 10。意思是「只要有一个从节点延迟在 10 秒内,主就接受写入;否则拒绝」。这是对脑裂的一道防线。
WAIT 命令:让主「等」
Redis 3.0+ 提供 WAIT numslaves timeout。它会让主在写入后等待指定数量的从确认收到复制流。例:
SET balance 100
WAIT 1 500 # 等待至少 1 个从确认,最多 500ms
WAIT 不保证强一致——它保证「至少 N 个从看到了这次写入」。代价是性能:从 1 万 QPS 降到 6 千 QPS。但对积分、库存这类业务非常值得。
Redlock 与分布式锁
讨论 Redis 一致性不得不提 Redlock:当用户「抢锁」时用一组独立的 Redis 实例加锁,多数成功才算获取。这是 Martin Kleppmann 与 Antirez 持续争论的话题。我的看法是:在多数据中心、跨区域基础设施中,Redlock 可以改善可靠性;但在单机房多副本情况下,用 Redis 主备 + 业务层幂等已经足够。
- 不要用 Redis 做资金主流程锁:用 DB unique key 或 zookeeper。
- 幂等是基础:所有「先锁再改」的业务,幂等设计比锁更重要。
- 超时必须可配置:包括客户端感知和服务端锁 TTL。
Cluster 模式下的几个误区
Redis Cluster 是把数据按 slot 切分到多节点的方案。它的一致性比单实例还更脆弱:
| 误区 | 真实代价 |
|---|---|
| 「Hash tags 解决了一致性」 | 只是把多个 key 路由到同一节点,不解决脑裂 |
| 「Cluster 自动迁移就够用」 | reshard 期间部分 key 会暂时不可用 |
| 「Slave of nobody 备份是冗余」 | 它只是副本,不能承担流量 |
Redis 7 的几个新动作
Redis 7 引入了多项与一致性相关的改进:
- Function 模块:在服务端运行脚本减少网络跳转。
- ACL v2:更细粒度的权限。
- Client side caching:让客户端跟踪 key 的变化,部分场景能避免无效读。
这些特性不会改变一致性模型本身,但能让工程化「减少共识边界」。
典型场景下的推荐配置
针对常见业务,我的推荐是:
| 场景 | 配置建议 |
|---|---|
| 用户 Session | 主从异步 + 异步持久化足够 |
| 积分 / 票务 | 主从 + WAIT 1 + 持久化开启 |
| 分布式锁 | 主从 + 锁 TTL 严格 + 业务幂等 |
| 排行榜 | 主从异步 + 业务容忍一致性 |
| 风控特征 | 主从异步 + 重建容忍 + 多副本 |
监控与事故复盘
我们有几条不可或缺的指标:
master_repl_offset - slave_repl_offset:主从延迟,期望 < 100KB。connected_slaves:在线从节点数。rdb_last_bgsave_status:持久化状态。- 复制超时:
master_last_io_seconds_ago。
任何一个抖动都要有 dashboard 覆盖。
几句不那么技术的话
Redis 是工程实践里有意思的样例:它既简单(单一数据结构模型)又复杂(多节点、多角色的语义不一致)。很多人以为它「快就行」,结果被脑裂折磨;也有人以为它「加锁就够」,结果吃了数据丢失的亏。
工程师对一致性的理解应当细到「它到底在保护什么」。把这个边界划清楚,才不会在「CAP 三角」里迷茫。
一致性是一份责任,是工程上的一种承诺。把这承诺写下来,比写十遍 SET 重要。