BASE 理论和最终一致性怎么理解?
简化版
BASE 是面向分布式系统的一组工程取舍:基本可用、软状态、最终一致。它不是放弃一致性,而是允许系统在明确的时间窗口内处于中间状态,用异步复制、重试、幂等、补偿和对账保证最终收敛;能否采用取决于业务是否允许这段不一致窗口。
详细版
BASE 包含三个部分:Basically Available 表示故障时保住核心能力,允许非核心功能降级;Soft State 表示系统可以暂时存在“处理中、待同步、补偿中”等中间状态;Eventually Consistent 表示停止新写入并且故障恢复后,各副本应在约定时间内收敛到一致结果。
最终一致必须回答四个问题:权威事实是什么、不一致最长允许多久、靠什么机制收敛、超过时限如何发现和处理。例如订单创建与积分赠送可以异步:订单本地事务成功后写 outbox,消息重复投递由积分流水唯一键去重,失败进入重试和死信,对账任务发现漏发并补偿。余额扣减、库存超卖等强不变量则不能只靠事后“最终一致”。
BASE 与 ACID 不是替代关系。单个服务内部仍可用 ACID 保证本地状态和 outbox 原子提交,服务之间再采用 BASE 异步收敛。面试时应讲清一致性边界、最大收敛时间和失败闭环,不能只说“用消息队列实现最终一致”。
完整版教学
一、BASE 解决的是什么矛盾
跨服务原子提交会引入协调、锁持有和可用性代价。若订单、积分、通知三个服务必须同步全部成功才返回,任何一个服务抖动都会拖慢主链路;但若完全不管后续结果,又会留下永久脏数据。BASE 在两者之间选择“核心事实先可靠落地,派生状态允许延迟收敛”。
这个取舍只适用于能容忍短暂差异的业务。用户下单后积分晚 10 秒到账通常可接受,两个账户转账后凭空多出资金则不可接受。因此讨论 BASE 的第一步不是选中间件,而是定义业务不变量和容忍窗口。
记忆钩子:最终一致的重点不是“最终”两个字,而是谁负责、多久收敛、失败如何被看见。
二、三个术语分别意味着什么
基本可用不是“接口还能返回 200”就算可用,而是故障时保留已定义的核心服务。例如大促时关闭个性化推荐、延迟生成报表,但下单与支付仍受保护。降级必须明确数据新鲜度、功能范围和用户提示。
软状态表示中间状态可被业务识别,例如 PENDING、SYNCING、COMPENSATING。它不是任意脏数据;每个状态都应有进入条件、超时阈值和退出路径。最终一致则承诺在无新变更且通信恢复后,系统通过确定机制收敛,而不是靠人工偶然修好。
| 概念 | 正确含义 | 必须配套的工程机制 |
|---|---|---|
| 基本可用 | 故障时保核心、降非核心 | 分级、限流、降级、容量预案 |
| 软状态 | 允许可解释的过渡状态 | 状态机、超时扫描、审计记录 |
| 最终一致 | 在时限内收敛到同一事实 | 可靠事件、幂等、重试、补偿、对账 |
三、用订单与积分看完整链路
订单服务不能在数据库事务中直接假设“发消息一定成功”。常用 outbox 做法是把订单和待发布事件写在同一本地事务里;后台发布器读取 outbox 发消息,成功后标记已发送。即使发布器崩溃,记录仍在,可以继续重试。
本地事务:INSERT order + INSERT outbox_event
提交后: publisher -> MQ -> points-service
消费端: INSERT points_ledger(event_id UNIQUE)
失败: 指数退避 -> 死信/人工处理 -> 定时对账
消费端仍可能收到重复消息,因此用 event_id 唯一约束保证只赠送一次。这里本地 ACID 保证订单和事件不丢,跨服务 BASE 允许积分稍晚到账,两者是组合关系。
四、带数字估算收敛窗口
假设正常每秒产生 800 条积分事件,消费者每秒可处理 1000 条。故障 5 分钟会积压 800 × 300 = 240000 条;恢复后净消化速度只有 1000 - 800 = 200 条/秒,清空需 240000 / 200 = 1200 秒,也就是 20 分钟。
因此“MQ 恢复了”不等于“一致性恢复了”。若业务承诺积分 10 分钟到账,这套容量就不达标,需要提高消费能力、临时扩容或降低生产速率。最终一致要用最大滞后时间、最老消息年龄和待补偿数量衡量,而非只看队列是否在线。
恢复时间 = 故障期间积压量 / (恢复后消费速率 - 持续生产速率)
前提:消费速率必须大于生产速率,否则永远无法收敛
五、冲突时以谁为准
多副本或离线写入场景可能同时产生不同版本,最终一致并不会自动决定正确答案。最后写入获胜简单,但依赖时钟且可能覆盖更有价值的数据;版本向量能识别并发更新,却需要业务合并;单主写入减少冲突,但牺牲分区时的写可用性。
订单状态通常不能按时间戳随意覆盖,因为“已退款”不能被较晚到达的“已发货”逆转。应使用合法状态迁移和版本条件更新。购物车商品数量则可能采用按项合并。冲突规则必须服从业务语义,而不是为了技术统一只选一个算法。
六、如何证明系统真的会收敛
每条异步链路都要有可重放的输入、幂等的处理、有限重试后的去向,以及独立于主流程的对账。只做重试不够:永久格式错误会一直失败;只做死信也不够:无人处理的死信只是另一个垃圾桶。
观测至少包含事件成功率、重试次数、最老未处理事件年龄、死信量、对账差异量和补偿成功率。可定期按业务主键或日期分桶计算校验和,再对差异做明细核查。严重差异要能冻结相关写入,避免补偿覆盖新状态。
event_lag_p99_seconds: 8
oldest_pending_seconds: 47
dead_letter_count: 3
reconciliation_diff_count: 2
compensation_success_rate: 99.9%
七、BASE 的适用边界
适合异步化的通常是派生信息和可补偿副作用,如搜索索引、积分、通知、报表。涉及资金守恒、唯一名额、库存不能为负等硬不变量时,应在能原子约束的边界内完成关键判断,再异步传播结果。不能因为系统是分布式的,就把所有一致性要求都降成最终一致。
用户体验也要诚实表达软状态。支付回调尚未确认时显示“处理中”,比提前显示“成功”更正确;同时给出查询和恢复入口。业务状态机是在产品层暴露真实系统状态,不是技术泄漏。
八、常见误区与追问
- 误区:BASE 与 ACID 二选一。 常见架构是服务内使用 ACID,服务间使用 BASE,两者作用边界不同。
- 误区:最终一致等于允许永久不一致。 必须有明确的收敛机制、时间目标和超时告警,永久差异属于故障。
- 误区:消息队列保证了最终一致。 MQ 只传递消息,生产端丢事件、消费端重复执行和业务冲突仍要分别解决。
- 误区:基本可用就是任何结果都能返回。 降级后的结果必须满足预先定义的业务安全边界,不能用错误数据冒充可用。
- 追问:如何避免 outbox 重复发消息? 不必强求只发一次,发布和消费都按事件 ID 幂等,使至少一次投递仍得到一次业务效果。
- 追问:补偿和回滚有什么区别? 回滚撤销同一原子事务,补偿是一个新的业务动作,可能失败并需要再次重试。
- 追问:怎样确定一致性窗口? 从用户承诺和业务风险反推,再结合积压量、净消费速度和故障恢复能力验证容量。
九、加强记忆
用“核心先落地、状态可解释、链路会收敛”串起 BASE。基本可用规定故障时保什么,软状态说明当前走到哪一步,最终一致依靠可靠事件、幂等、重试、补偿和对账结束中间状态。回答时再补上一个收敛时间算例与强不变量边界,才能说明它是可验证的工程方案,而不是降低正确性要求的借口。