18.4 复制延迟与读一致性
一句话
异步复制下,“写完立刻读” 有 60% 概率读到旧值(本机模拟);读一致性不是 “把从库加个开关”,而是要在 会话/版本 层面设计——你要的是 “读己之写”,还是 “最终一致”,必须先说清。
四种读一致性
| 级别 | 保证 | 实现手段 | 代价 |
|---|---|---|---|
| 强一致 | 读到的必是最新已提交 | 读主库 / 多数派读 | 主库压力、延迟 |
| 读己之写 | 自己写的,自己一定读到 | 会话粘性、写后读主、版本等待 | 需会话/版本信息 |
| 单调读 | 不会读到 “更旧” 的版本 | 绑定副本/版本单调 | 副本切换要小心 |
| 最终一致 | 一段时间后一致 | 异步复制 | 窗口内可能读到旧值 |
实测:无粘性 vs 会话粘性
2 万次 “写后 0–200ms 读”、复制延迟 120ms:
| 策略 | 陈旧读 | 比例 |
|---|---|---|
| 无会话粘性(随机读副本) | 12,013 | 60.1% |
| 会话粘性(写后读主库) | 0 | 0% |
60% 这个数字说明:只要 “读” 落在复制延迟窗口内,就大概率读到旧值——这不是 “偶发”,是默认行为。
// 写后 N 秒内读主库(最简单可靠)
func (s *Service) GetAfterWrite(ctx context.Context, id string) (User, error) {
if s.session.WroteRecently(id, 2*time.Second) {
return s.master.Get(ctx, id) // 读主
}
return s.replica.Get(ctx, id)
}
三种 “读己之写” 实现
| 实现 | 做法 | 适用 |
|---|---|---|
| 写后读主(时间窗) | 会话记录 “最近写过”,窗口内读主 | 简单、通用 |
| 版本等待 | 客户端带上 “我写到哪个版本”,副本等到 ≥ 该版本 | 精确、需版本号 |
| 会话粘性 | 同一会话固定路由到同一副本 | 负载均衡要支持 |
反模式
| 反模式 | 后果 |
|---|---|
| 写完立刻读从库,且不处理 | 用户看到 “刚改的没生效”,投诉 |
| 用 “复制延迟很小” 当理由 | 高峰期延迟变大,问题在高峰暴露 |
| 只做主从,不做会话一致性 | 60% 级别的陈旧读(见实测) |
| 用 “读主库” 解决所有读 | 主库压力翻倍,扩展性丢失 |
| 副本切换后不校验版本 | 单调读被破坏,用户看到 “回退” |
| 不监控复制延迟 | 不知道窗口有多长,无法设阈值 |
三条铁律
🔑 先声明一致性级别,再选实现;“最终一致” 也要写进契约。
🔑 读己之写靠会话/版本,不靠 “延迟很小”。
🔑 复制延迟要监控;它是 “陈旧窗口” 的唯一度量。
自检
Q1 为什么 “写完立刻读” 会读到旧值,而且概率这么高?
▸ 答案:因为异步复制的延迟窗口(本例 120ms)覆盖了 “写后读” 的时间范围(0–200ms):只要读发生在 “主库已提交、备库还没重放” 的窗口里,就会读到旧值。实测 60% 正是这个窗口与读时间的重叠比例。
Q2 “读己之写” 与 “强一致” 的区别?
▸ 答案:强一致要求所有读者都看到最新值;读己之写只要求写的人自己看到。前者成本高(多数派读/读主),后者可以用 “会话粘性/版本等待” 低成本实现。多数业务真正需要的是后者。
Q3 版本等待怎么实现?
▸ 答案:写成功时返回一个版本号/位点(如 LSN、GTID 或单调递增的版本),客户端后续读带上它;副本在 “已重放到 ≥ 该版本” 前不返回(等待或报错重试)。这样既不总是读主,也保证 “至少看到自己写的”。
Q4 为什么 “单调读” 也需要设计?
▸ 答案:因为多副本延迟不同:第一次读副本 A(较新)、第二次读副本 B(较旧),用户会看到 “数据回退”。单调读要求 “同一会话的读版本不倒退”,实现方式是 “绑定副本” 或 “记录已读版本,低于它的结果不接受”。
Q5 复制延迟多少算 “可接受”?
▸ 答案:取决于业务:读己之写要求窗口内能兜住(如 < 1s);报表/分析可以分钟级;资金/库存通常不能容忍,必须读主或用共识。没有统一阈值,必须按 “读到旧值的后果” 定。
Q6 主库压力大,又不能接受陈旧读,怎么办?
▸ 答案:三条:读写分离只用于可容忍陈旧的查询(列表、详情可缓存);关键读走主库(按接口分级);用共识/同步复制(代价是写延迟)。先按业务分级,再决定哪类读必须强一致。
Q7 怎么衡量 “读一致性有没有做好”?
▸ 答案:两个指标:陈旧读比例(采样:写后读,比对版本)与 P99 复制延迟。前者告诉你 “用户是否真的看到旧值”,后者告诉你 “窗口有多长”。只看延迟不看陈旧读,容易自我感觉良好。
关联
- 前置:18.2 同步、半同步、异步、18.3 复制日志 (语句 / 行 / 逻辑)
- 并列:18.5 故障检测与切换、18.7 复制拓扑 (链式 / 树形 / 星形)
- 下游:16.4 因果一致性、44.13 主从复制与读写分离