3156 字
约 10 分钟
18
S17-A04 · 分布式一致性选择:从强一致到最终一致

一致性不是"越好越贵"的单选题,而是一组按业务错误代价分配的决策。同一系统里,下单扣库存必须是强一致,商品浏览量统计可以差几分钟。判断的起点不是技术偏好,而是三个业务问题:错了要赔多少、能不能撤回、用户能不能等。下面按"层级 → 决策依据 → 实现代价 → 收敛与对账"的顺序给出结构。


1. 一致性谱系的四个层级

层级 保证 实际系统里的对应物
线性一致 所有操作像发生在单点上,且与真实时间顺序一致 单机数据库事务、共识算法日志(Raft/Paxos 提交后读主)
顺序一致 所有节点看到同一顺序,但不保证与真实时间一致 单主复制、有序日志消费
因果一致 有因果关系的操作顺序不乱 会话粘滞 + 版本号、部分 CRDT 实现
最终一致 停止写入后,副本最终收敛 异步复制、消息投递、缓存回填
常见误读:"用了某中间件就得到了某层级"。
中间件提供的是机制(复制、日志、版本号),
实际层级取决于部署方式与读写路径选择。
例如同一套消息队列,读主 + 单分区 = 顺序一致;
多分区并行消费 = 只能靠业务做因果排序。

2. CAP 的常见误读与分区时的具体决策

误读 实际情况
"三选二" 分区不是可选项,P 迟早发生,真正的选择只在 C 与 A 之间
"选了 CA 就没有 P" 单机也有网络分区(与自身存储的连通),只是概率低
"最终一致就是放弃 C" 最终一致是延迟容忍,不是放弃正确性
分区发生时的决策模板(按数据类型分别填):
类型 1:账户余额、库存扣减   → 拒绝写入,保一致
类型 2:订单状态流转         → 主分区可写,少数侧只读
类型 3:浏览计数、推荐埋点   → 本地可写,异步合并

这张表要在设计阶段写完,而不是等分区发生时才讨论。多数"分区时数据错乱"的根因是从来没有按数据类型分过类。


3. BASE 与最终一致的工程含义

"最终一致"必须被量化,否则等于没有承诺:

不一致窗口 = 从写入成功到所有读路径可见的最长时间
必须明确三件事:
1. 窗口上限(例:P99 不超过 X 秒)
2. 窗口内的读行为(返回旧值?报错?排队?)
3. 窗口超限时的动作(告警、降级、进入对账)
概念 工程含义 落地物
Basically Available 允许返回旧值或部分数据 读路径降级规则
Soft state 中间态可被后续事件修正 状态机 + 补偿任务
Eventually consistent 存在可度量的收敛时间 对账与延迟监控

没有第三条的落地物,"最终一致"只是把问题推给运维。


4. 业务判断依据

判断维度 偏强一致 偏最终一致
错误代价 资金、库存、配额 展示、统计、推荐
可逆性 不可逆(已扣款) 可重算(报表可重建)
用户容忍度 秒级不一致即投诉 分钟级无感
冲突概率 高并发同资源竞争 不同用户互不影响
修复成本 人工介入、赔付 后台重跑
优先级:可逆性 > 用户容忍度 > 错误代价 > 技术偏好。
不可逆且高错误代价的数据,无论实现多麻烦都走强一致;
可重算的数据,追求强一致是纯浪费。

5. 强一致的实现代价

手段 代价 代价出现在哪
共识算法(Raft/Paxos) 至少一次多数派往返的延迟 写延迟随节点数与地理分布上升
跨服务事务 协调者故障、锁持有时间、死锁 吞吐与可用性
单点写入 写入能力上限、故障切换期不可写 扩容与运维窗口
加锁/序列化 并发度下降 高峰期的排队
强一致的隐性成本清单:
- 变更为两阶段(准备 + 提交),回滚路径必须显式实现
- 参与方的超时与重试语义必须一致
- 监控需要新增"悬挂事务/未决事务"指标

6. 最终一致的四种收敛手段

手段 机制 适用 风险
重试 失败后指数退避重发 瞬时故障、网络抖动 重试风暴、重复执行
对账 周期性比对两侧数据 无法靠事件保证的路径 发现晚、修复动作需人工定义
补偿 执行反向业务操作 已产生的错误副作用 补偿本身失败、需幂等
事件重放 重放日志重建状态 计算逻辑变更、数据修复 需要事件不可变且可回放
-- 事务发件箱:业务写入与事件写入同一本地事务,保证不丢
BEGIN;
INSERT INTO orders(id, status, version) VALUES ('O1', 'CREATED', 1);
INSERT INTO outbox(id, aggregate_id, type, payload)
VALUES ('E1', 'O1', 'OrderCreated', '{"orderId":"O1"}');
COMMIT;

发件箱把"跨服务一致"降级为"本地事务 + 至少一次投递",这是最常用也最稳的一条:代价挪到了消费侧的幂等要求上。


7. 幂等与去重是一切的基础

CREATE TABLE idempotency_key (
  key_hash   CHAR(64) PRIMARY KEY,
  biz_id     VARCHAR(64) NOT NULL,
  created_at DATETIME NOT NULL
);
-- 消费侧:先插入唯一键,冲突即视为重复,直接返回上次结果
INSERT INTO idempotency_key VALUES (?, ?, NOW());
机制 适用场景 局限
唯一键 创建类操作 需要稳定的业务唯一标识
版本号 更新类操作(乐观并发) 需要读改写;冲突要重试
状态机 流程类操作 需要状态流转定义完整
结果缓存 查询类重试 结果体积与有效期
状态机口径:每个状态只允许特定入边,非法流转直接拒绝并记录。
"已支付 → 待支付"这类回退,只能通过显式退款动作,不能靠重试产生。

8. 顺序问题的来源与最小化

来源 现象 处理
多生产者 同一实体的事件交错 按实体 ID 分区/取模
分区并行 全局顺序丢失 只保留实体级顺序
重试 旧事件晚到覆盖新事件 版本号比较后丢弃
消费并发 同分区内并发处理 单分区单消费者串行
顺序性需求最小化:能只保实体级顺序,就不要求全局顺序。
全局顺序的代价是单点吞吐上限,几乎总是过度设计。

9. 读己之写的会话保证

跨服务下"用户刚提交就能看到"需要显式传递信号:

三种做法(按可靠性递增):
1. 写后短窗口内的读请求强制走主库/同分区
2. 携带写入版本号,读路径要求版本 >= 写入返回版本
3. 服务端会话粘滞(同一用户固定路由到同一副本)
做法 成本 失效场景
强制读主 主库压力上升 无
版本号约束 需要在读路径传递上下文 客户端不传版本时退化
会话粘滞 负载均衡复杂度上升 路由变更、故障切换后失效

10. 冲突解决策略

策略 机制 风险
最后写入胜出(LWW) 比时间戳取最新 依赖时钟,会静默丢数据
向量时钟 记录因果关系,检测并发写 元数据膨胀、需要业务合并
业务合并 按业务规则合并(如集合求并) 规则要能覆盖全部字段
单写者 指定唯一写入方 单点与切换成本

LWW 最危险的地方不是精度,而是它看起来总是"成功":没有任何报错,只是某次写入无声消失。凡是丢失不可接受的数据,都不要用时间戳决胜。


11. 对账系统的设计

要素 设计要点
周期 关键数据小时级,统计类天级
差异分类 缺失、多出、字段不一致、金额不一致
修复动作 补单、撤销、标记待人工;每类动作必须幂等
审计 每次差异与修复留痕,可追溯到源事件
-- 差异分类:以对账基准表为左表,全外连接找两侧不一致
SELECT a.id, b.id,
       CASE WHEN b.id IS NULL THEN 'MISSING'
            WHEN a.amount <> b.amount THEN 'AMOUNT_DIFF'
            ELSE 'OK' END AS diff_type
FROM ledger a FULL OUTER JOIN cps_account b ON a.id = b.id
WHERE a.amount <> b.amount OR b.id IS NULL;
对账不是失败兜底,而是最终一致系统的必要组件:
没有对账的最终一致,等于把"最终"两个字交给运气。

12. 排错:六种一致性异常

现象 原因 处理
用户反复刷新才看到更新 读路径未保会话一致 加版本号约束或短窗读主
同一订单出现两次 消费侧无幂等键 引入唯一键或状态机
状态回退 旧事件晚到未被丢弃 版本号比较后丢弃
金额对不上 部分路径绕过对账 扩大对账覆盖,分类差异
高峰吞吐骤降 锁持有时间过长 缩短事务,改为发件箱异步
补偿越补越乱 补偿不幂等、可重复执行 补偿加唯一键,限重试次数

13. 边界:这些情况不要引入分布式一致

场景 判断 理由
单库事务能解决 用本地事务 分布式事务只增加故障面
能异步化 异步 + 最终一致 同步一致的延迟成本换不到业务价值
数据可重算 重算 + 对账 不需要事务保证
强一致需求只在单实体 单实体单写 + 版本号 不需要共识算法
一致性需求说不清 先量化不一致窗口 没量化就等于拍脑袋

判断顺序固定为:先问能不能不跨服务,再问能不能异步,最后才考虑分布式事务。多数被"分布式一致性"困住的系统,问题出在第一问没问。


14. 自测

  1. 线性一致与顺序一致的差别,用一句话怎么区分?
  2. 为什么说 CAP 里的 P 不是可选项,真正的选择只在 C 与 A 之间?
  3. 定义"不一致窗口"必须明确哪三件事?
  4. 四种收敛手段中,哪一种的修复动作最需要事先定义?
  5. 发件箱方案把一致性代价挪到了哪里?
  6. 为什么 LWW 比向量时钟更危险,尽管它实现更简单?
S17-A04 · 分布式一致性选择:从强一致到最终一致
https://www.clxhxhhr.top/posts/6075/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。