一粟 · yisu.app
· 13 min read
Redis

Redis 集群数据一致性

一致性是 Redis 工程师绕不开的话题。本文从一次真实脑裂事故讲起,串起主从复制、min-slaves、WAIT、Redlock 之间的权衡,给出不同业务场景下的推荐配置组合。

一次真实的事故:凌晨 3 点的主从切换

2024 年的某个凌晨,某条业务数据库的主 Redis 节点宕机。系统自动做了主从切换,新主顶上来。但 12 小时后,客服反馈某活动用户的积分被错误扣了。回看日志,问题起源于切换前后「旧主还活着」的窗口:

  1. 旧主在一段时间里仍接受写入。
  2. 新主从其他从节点晋升,旧主写入的数据「丢失」。
  3. 切换完成后,旧主最终被降为从,丢弃自身数据;新主与从完成全量同步。

这就是经典的「脑裂」。它背后是 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 主备 + 业务层幂等已经足够。

  1. 不要用 Redis 做资金主流程锁:用 DB unique key 或 zookeeper。
  2. 幂等是基础:所有「先锁再改」的业务,幂等设计比锁更重要。
  3. 超时必须可配置:包括客户端感知和服务端锁 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 重要。