Redis 主从(主副本)之间的数据同步,核心是 “主节点把写操作按顺序发给从节点执行”。默认确实是 异步复制,所以在某些时刻会出现你说的 map(比如 Hash)读到不一致的情况(更准确说:** 读到旧值/部分更新后的值**)。
下面按机制 + 会不会不一致 + 怎么避免来讲。
1) Redis 主从是怎么同步数据的?
A. 第一次建立复制:全量同步(Full resync)
- 从节点发送
PSYNC 给主节点(带上自己已知的复制进度)。
- 如果主节点无法做增量续传(例如第一次、offset 对不上、replid 变了),就触发全量:
- 主节点生成一份 RDB 快照(fork 子进程)
- 把 RDB 发给从节点
- 从节点加载 RDB 到内存
- 在生成/传输 RDB 期间,主节点仍在接收写入,会把写命令写进 replication backlog(复制积压缓冲区)
- 从节点加载完 RDB 后,再把 backlog 里这段期间的写命令补上,达到一致。
B. 后续正常运行:增量同步(Command stream)
- 主节点把每条写命令按顺序发送给从节点(类似“复制日志流”)
- 从节点按相同顺序重放这些写命令
C. 断线重连:部分同步(Partial resync)
- 断线时间不长、backlog 还保留着缺的那段写命令 → 只补缺的命令
- backlog 不够或主节点重启导致 replid/offset 对不上 → 再来一次全量同步
2) 既然是异步同步,会不会出现 “获取 map 结果不一致”?
会。
典型场景:读从节点
如果你把读请求打到从节点(比如读 Hash / 读 map),而写发生在主节点:
- 主节点写成功返回了
- 复制还没来得及把这次写同步到从节点
- 你立刻去从节点读 → 读到旧值(或读到更新前的某些字段)
这就是复制延迟(replication lag)导致的最终一致性问题:
主从不是线性一致(strong consistency),默认也不保证读写强一致。
“部分更新/不一致”会到什么程度?
- 单条 Redis 命令是原子性的(比如一次
HSET key f1 v1 f2 v2 在主节点是原子,发到从节点执行也同样是原子)
- 但如果你的“更新 map”是多条命令完成的(例如多次
HSET、或 HDEL+HSET),那么从节点可能处于“只复制到了前几条命令”的状态:
- 读到 半更新状态
- 表现为你说的“map 结果不一致”(字段缺失/新旧混杂)
3) 怎么避免或降低这种不一致?
方案 1:读主节点(最简单、最强一致)
- 所有读写都走主节点 → 避免复制延迟带来的旧读
- 缺点:主节点压力更大
方案 2:读从节点但要求“读到最新写”(读后写一致)
Redis 提供了两类常用手段:
A) WAIT:写入后等待复制到 N 个副本
- 你在主节点写完后执行
WAIT numreplicas timeout
- 这会阻塞当前客户端:直到有 N 个从节点确认收到了这条写(或超时)
- 能显著降低“写后立刻读从”的旧读概率
注意:WAIT 保证的是“从节点收到了写命令”,不是严格意义上的分布式强一致(但对很多业务够用)。
B) 让更新变成“单条原子命令”
- 能用一条命令更新多个字段就不要拆多条
- 例如
HSET key f1 v1 f2 v2 ...
- 多步逻辑用 Lua 脚本一次完成(原子执行)
- 这样从节点就算落后,也只会“整段逻辑还没到”或“整段逻辑已完成”,减少半更新状态。
方案 3:业务侧容忍最终一致
- 允许短时间旧读(例如缓存场景)
- 加版本号/时间戳字段,读到旧版本就重试或回源主节点
方案 4:监控/限制从节点“太落后就不让读”
INFO replication 看 master_link_status、master_last_io_seconds_ago、slave_repl_offset 等
- Redis 还有
min-replicas-to-write / min-replicas-max-lag:
- 当可用且延迟足够低的从节点少于阈值时,主节点拒绝写入
- 用来避免“只有主节点写成功但副本全没跟上”的风险(偏可用性换一致性/安全性)
4) 你这句“获取 map 结果不一致”我怎么具体判断你遇到的是哪种?
大概率就两类:
- 读走了从节点(读写分离)→ 复制延迟导致旧读
- 更新 map 拆成多条命令 → 从节点可能读到半更新
如果你愿意贴一下:
- 你们的 map 是 Redis Hash 吗?(
HGETALL/HGET 那种)
- 写 map 是一次
HSET 多字段,还是多次调用?
- 读请求是不是走了从库/replica?
我就能直接告诉你最匹配的解决方案(比如用 HSET 合并、Lua、还是 WAIT / 强制读主)。