一笔金融交易很少只涉及一个服务。支付服务可能负责创建交易,账户服务负责记账,渠道服务负责扣款,通知服务负责回调。任何一个进程都可能重启,任何一次网络调用都可能超时,但交易不能因为其中一个环节失败就消失,也不能因为重试而多扣一次钱。
金融开发真正要解决的问题,是让一笔已经落地的业务意图,最终收敛到一个可证明、可追溯、可修复的结果。本章只讨论其中一条主线:本地事务产生事实,事实可靠外发,消费端幂等处理,状态机持续收敛,定时任务和对账负责补偿与验证。
本文面向有分布式系统基础、正在接触支付、账户、清算或账务系统的开发者。文章从本地事实的正确性开始,先讲最容易落地的定时任务,再讲如何演进到事件驱动,最后说明这条一致性主线的能力边界,以及其他资损问题应如何单独治理。
一、先建立主线:一笔交易怎样从“已提交”走向“已收敛”
本节只回答一个问题:跨服务状态收敛到底要收敛什么。
假设服务 A 创建了一笔支付单,服务 B 负责调用渠道扣款。A 的本地事务提交成功,并不代表 B 已经完成扣款;它只代表系统已经拥有一条可靠的业务意图。后续工作是让 B 最终得到这条意图,并把结果反馈到 A 的状态机中。
这个模型有五个不可绕过的约束:
- 意图不能丢:本地事务提交后,待处理事项必须留在可恢复的存储中。
- 效果不能重复:同一业务意图被触发多次,只能产生一次资金效果。
- 状态转移必须合法:迟到的回调、重复的消息和人工操作不能绕过状态机。
- 未知结果必须保留:超时只能说明调用方没有拿到结果,不能直接推断渠道没有执行。
- 结果必须可核对:本地状态、分录、余额和外部渠道账单最终能够相互验证。
这解释了金融系统的基本分工:本地数据库保存业务事实和待处理意图,触发器负责推动状态前进,幂等和状态机控制重复与乱序,对账负责验证前面是否真的做对。
二、本地事实本身可能不是正确事实
跨服务收敛的起点不是消息,而是本地数据库中的业务事实。本地事务提交成功,只能证明一次本地写入完成,不能证明写入内容符合账务规则;如果起点就是错误的,后面的可靠外发只会把错误传播到更多服务。
常见的本地事实错误包括:
- 余额更新了,但对应流水没有插入。
- 流水插入了,但订单或账务状态没有同步变化。
- 借贷分录不相等、科目映射错误,或会计日期归属错误。
- 两个并发扣款都读到余额
100,各扣80,没有行锁、乐观锁或条件更新,最终得到错误余额。
这些问题不是消息丢失,也不是跨服务调用失败。它们发生在本地事实形成的瞬间,因此必须先在本地边界内建立不变量:
- 账务模型明确账户、科目、分录和余额之间的关系,复式记账保证借贷平衡。
- 余额变更、分录、业务流水和状态更新在正确的本地事务中保持原子性。
- 金额使用统一单位和精度,手续费、汇率、舍入和会计日期都有明确规则。
- 业务唯一键、唯一索引和状态条件更新阻止重复写入和非法状态转移。
- 根据并发模型选择行锁、乐观锁、条件更新和合适的隔离级别,不能只依赖应用层的先查后写。
只有本地事实先满足这些约束,可靠外发和跨服务收敛才有意义。否则,系统得到的是一条“可靠传播的错误”。
下面的伪代码展示“幂等占位、余额检查、分录和余额同事务”的约束关系,不绑定具体数据库方言:
BEGIN;
INSERT INTO payment_effect (request_id, account_id, amount)VALUES (:request_id, :account_id, :amount)ON CONFLICT (request_id) DO NOTHING;
-- 影响行数为 0 时读取已有结果并返回,不执行后续语句UPDATE accountSET balance = balance - :amountWHERE account_id = :account_id AND balance >= :amount;
-- 影响行数不是 1 时回滚并返回余额不足或账户不存在INSERT INTO ledger_entry (request_id, account_id, amount, entry_type)VALUES (:request_id, :account_id, :amount, 'debit');
COMMIT;真实实现还需要处理影响行数、并发锁、事务回滚和异常状态。关键不在某个 SQL 关键字,而在于重复请求不能绕过唯一约束,余额检查不能与扣减分离,分录不能和余额各自提交。
三、收敛所需的基础:投递语义与幂等状态机
定时任务或消息只是触发手段。触发多少次、消息是否重复,并不能单独决定业务是否安全;真正决定结果的是投递语义、幂等约束和状态机。
3.1 投递语义:最多一次、至少一次和恰好一次
投递语义描述消息如何在发送方和接收方之间到达,不直接描述消费代码是否正确。
至多一次:可以丢,但不主动重试
At-most-once 的承诺是“最多到达一次”。发送方不等待可靠确认,失败也不重试,因此消息可能丢失,但不会因为投递重试产生重复。
这种语义适合丢失成本低、下一次采样可以覆盖的监控指标、心跳或非关键埋点。不应作为扣款、记账和退款等资金链路的底层语义。
至少一次:不轻易丢,但必须接受重复
At-least-once 依赖持久化、确认和重试。生产者等待 ACK,消费者在本地业务效果持久化后再确认;任意一步在确认前失败,消息都可能再次投递。
重复是这个语义的必然代价。消费者可能已经扣款成功,却在提交消费位点前崩溃;生产者也可能已经写入消息,只是确认在网络中丢失,于是再次发送。
金融系统通常把至少一次作为传输层基线,再用消费端幂等把重复效果挡住。常见的约束是:
- 消息或待处理意图先持久化,发送结果可查询。
- 消费者完成本地业务事务后再确认消息。
- 未确认的消息进入重试,超过上限后进入死信或人工复核。
- 消费者使用业务唯一键和状态机,而不是假设消息只来一次。
“至少消费一次”可以理解为消费者不能把未完成的业务提前标记为成功,但它不代表业务效果只发生一次。
恰好一次:区分传输次数和业务效果
Exactly-once 经常被当成一个整体承诺,实际至少要区分两个问题:消息是否只在传输链路中出现一次,业务副作用是否只发生一次。
在存在超时和进程崩溃的端到端网络中,发送方无法同时确认“对方没收到”和“对方已经处理”。某些消息系统可以在限定边界内提供事务或幂等生产能力,但这不等于跨数据库、跨服务和外部渠道的全链路恰好一次。
金融业务追求的通常是业务上的有效一次:
持久化的至少一次投递 +消费端幂等与唯一约束 +合法状态转移 +对账与差错修复 =业务效果上的有效一次看到组件文档中的“恰好一次”时,应继续确认保证边界:是否包含消费者,是否跨越本地数据库事务,外部支付渠道的超时和回调是否也在保证范围内。
3.2 幂等:重复触发不能重复产生资金效果
幂等的定义是同一个业务操作执行一次和执行多次,最终业务结果相同。它不是缓存中的一个布尔标记,而是一组由业务键、数据库约束和状态机共同形成的规则。
请求号、支付流水号、退款单号或事件中的业务主键,才适合作为幂等键。随机生成的消息 ID 只能识别某次传输,无法识别“同一笔业务被重新包装后再次发送”。
数据库唯一约束是重复写入的底线。缓存锁可以减少重复请求带来的压力,但会过期、丢失或在网络分区时失效,不能替代数据库约束。
3.3 状态机:去重不能代替合法性判断
去重表解决“这条消息是否已经处理过”,状态机解决“当前状态是否允许做这件事”。退款消费者至少要判断退款单是否完成、累计退款是否超过原交易金额、原交易是否可退款,以及当前回调是否比已处理的版本更新。
资金状态通常要显式区分 PENDING、PROCESSING、SUCCESS、FAILED 和 UNKNOWN。其中 UNKNOWN 不是失败的别名,而是“系统还没有足够事实判断结果”的状态,必须通过查询、重试、对账或人工接管继续收敛。
3.4 相关术语应该放在哪一层
| 层次 | 技术 | 解决的问题 |
|---|---|---|
| 传输 | 持久化、ACK、重试、消费位点、死信队列 | 消息如何保存、确认、重放和隔离 |
| 业务处理 | 幂等键、唯一索引、去重表、状态机 | 重复和乱序如何不产生错误效果 |
| 状态触发 | 定时任务、Outbox、事务消息、CDC | 什么时候发现并推动待处理意图 |
| 跨服务协调 | TCC、SAGA | 多个本地事务如何达成约定状态 |
| 结果校验 | 实时对账、日终对账、冲正、补差 | 如何证明并修复结果差异 |
这些术语不能互相替代。CDC 能缩短触发延迟,不能代替幂等和对账;TCC 能协调资源预留,不能替业务判断退款金额;死信队列能隔离坏消息,不能自动证明资金已经正确。
四、最小可用方案:用定时任务驱动状态收敛
如果服务 A 写入本地数据库后,还要推动服务 B 完成一项动作,入门阶段不必立刻引入消息队列或 CDC。一个持久化的待处理状态,加上一条可重入的定时任务,就能构成最小可用的最终一致方案。
4.1 定时任务方案的完整流程
定时任务的关键不是 cron 本身,而是把“还没有被下游确认的业务意图”写进本地数据库。一个可恢复的流程包含以下步骤:
- 服务 A 在本地事务中写入业务记录,状态设为
PENDING,同时记录next_retry_at、重试次数和业务幂等键。 - 定时任务按状态、时间窗口和索引批量领取到期记录,先用租约、行锁或条件更新把记录标记为
PROCESSING。 - 任务调用服务 B,携带同一个业务幂等键。服务 B 成功后,服务 A 把记录更新为成功;暂时失败则按退避时间重新调度。
- 超时或无响应不能直接当成失败。任务应查询服务 B 的结果,或者把记录保留在
UNKNOWN,等待下一次查询、重试或人工处理。 - 超过重试上限的记录进入人工复核或死信状态,不能通过“重试次数耗尽”推断资金动作已经失败。
定时任务不是分布式事务,不能让服务 A 和服务 B 原子提交。它采用的是“本地持久化意图 + 至少一次调用 + 下游幂等或可查询 + 失败补偿”,用可接受的延迟和重复调用换取可恢复性。
只要本地待处理意图没有丢失、下游操作可幂等或可查询、故障最终会恢复,任务就能不断推进未完成记录,直到进入成功、业务拒绝或人工接管等终态。这是最终收敛的前提,而不是定时任务自身提供的魔法保证。
4.2 定时任务的工程约束
定时任务方案适合入门、低并发和分钟级延迟可接受的场景,也适合作为实时链路的独立兜底。它的代价同样明确:轮询带来发现延迟,扫描会消耗数据库资源,多实例需要抢占控制,远程调用仍然会产生重复和未知结果。
任务至少需要满足以下约束:
- 用
(status, updated_at)等索引筛选窗口,禁止高频全表扫描。 - 一次只领取有限批次,使用租约、行锁或状态占位,避免多实例重复领取。
- 设置最小安全间隔,让其他实时消费者有机会先处理新记录。
- 记录重试次数、最后错误和下次执行时间,超过上限进入人工复核或死信状态。
- 补偿动作仍然使用同一套幂等和状态机,不要因为“这是补跑”就绕过校验。
补偿频率由业务允许的最大发现延迟、外部查询成本、数据库容量和状态超时窗口共同决定:
发现延迟上限 = 业务可接受延迟扫描间隔 < 发现延迟上限补偿阈值 > 正常处理时长 + 安全余量支付处理中状态可能需要分钟级发现,退款通知可以接受更长延迟,日终清算和全量对账则按账务日历执行。这些只是设计示例,不能脱离实际吞吐和数据库容量直接复制。
五、方案演进:从定时任务到 Outbox、消息和 CDC
定时任务不是“低级方案”,而是最容易验证的起点。系统需要更低延迟、更高吞吐或更明确的事件边界时,可以逐步引入 Outbox、消息队列和 CDC。演进改变的是触发方式,不改变幂等、状态机和对账这些基本约束。
5.1 Outbox:把发送意图和业务事务绑在一起
Outbox 的做法是:服务 A 在同一个本地数据库事务中写入业务记录和事件表,事务提交后,再由转发器扫描事件表并发送消息。即使发送器暂时宕机,事件仍然留在本地表里,恢复后可以继续发送。
它适合“业务事务提交后,必须发送某个明确事件”的场景。与直接在事务里发送消息相比,Outbox 不需要把远程消息系统放进数据库事务,也不会因为网络调用拖长数据库锁。
Outbox 的发送仍然是至少一次:转发器可能已经发成功,但在标记事件已发送之前崩溃,恢复后会再次发送。因此,下游消费者仍然需要业务幂等和状态判断。
5.2 CDC:实时捕获数据库变更
CDC 通过数据库日志捕获数据变更,再把事件投递给下游。它减少了轮询延迟,也让业务代码不必在每次写库后手工发送一条消息,但它的可靠性依赖一组前提:日志存在、位点正确、采集进程可恢复、下游能够接收并处理事件。
CDC 的工程实践包括:
- 位点持久化:位点保存到可恢复介质,日志保留窗口覆盖故障恢复和回放需求。
- 事件带业务字段:至少包含业务唯一键、状态、版本或序列号、发生时间和来源,不要让消费者只能依赖数据库内部行号。
- 按聚合保证顺序:同一业务主键可以要求有序,不要把跨库、跨表的全局顺序当成系统承诺。
- 捕获与处理分离:采集器负责读取和投递,复杂账务逻辑放在可独立重试的消费者中,避免坏事件阻塞整个日志位点。
- 消费必须幂等:位点恢复、主从切换、重放和网络抖动都会带来重复事件。
- 监控可操作指标:采集延迟、位点停滞、消费堆积、死信数量、日志保留窗口、主从延迟和 Schema 变化都应有告警与处理预案。
- 准备回放路径:明确从哪个位点、哪个时间窗口或哪个业务键开始重放,重放前先确认消费者幂等和影响范围。
CDC 消费者尽量不要直接承担不可逆的复杂资金动作,尤其不能把每一条变更都当成“现在必须扣款”的命令。事件应携带版本和业务状态,消费者根据当前状态判断是否允许执行。
5.3 为什么有了 CDC 还要保留定时任务
CDC 可以在满足前提时实现可靠的事件捕获,但它无法证明端到端链路永远没有缺口。常见的静默漏处理包括:
- 采集器停机时间超过日志保留窗口,恢复时旧日志已经被清理。
- 位点存储损坏或误跳,采集器从错误位置继续读取。
- 主从切换、实例重建或
DDL变更造成采集链路中断。 - 事件进入消息系统后,下游消费者丢失、错误确认或代码异常吞掉了失败。
- 外部渠道没有回调,业务单据长期停留在处理中。
定时任务直接以业务库中的中间态为对象,捕获那些 CDC 无法感知的“静默漏单”。因此,成熟系统通常采用“实时触发 + 定时巡检 + 对账校验”的组合,而不是用 CDC 删除定时任务。
5.4 三种触发方案如何选择
| 方案 | 触发依据 | 适合的起点 | 主要边界 |
|---|---|---|---|
| 业务表定时扫描 | 查询 PENDING 或超时中间态 | 系统刚起步、低并发、分钟级延迟可接受 | 数据库轮询压力大,实时性有限;必须避免全表扫描 |
Outbox + 定时发送 | 本地事务写入事件表,再扫描事件表发送 | 需要明确记录必须发送的事件,但暂时没有可靠消息组件 | 多一张事件表和转发器;下游仍须幂等 |
CDC + 消费者 | 读取数据库日志变更 | 高并发、低延迟、已有日志采集和消息基础设施 | 依赖日志、位点和采集链路;仍要保留补偿和对账 |
业务表定时扫描最容易讲清楚、最容易排查;Outbox 解决“业务事务提交后,发送意图不能丢”;CDC 解决“变更发生后,尽快产生事件”。系统从轮询演进到实时事件驱动时,定时任务仍然负责漏处理发现和状态校验。
六、事务边界:不要在本地事务里批量调用外部接口
本节解释为什么触发方案之外,还必须重新设计事务边界。
事务越长,锁持有时间越长;远程调用越不可控,事务回滚与外部成功错位的概率越高。最危险的代码形态是:开启数据库事务,批量调用支付、库存、清算或其他服务,最后再统一提交本地数据。
典型故障链路如下:远程渠道已经扣款,应用因为超时抛异常,本地事务回滚;下一次定时任务看到本地仍是“待扣款”,又发起一次扣款。此时问题不是消息重复,而是本地事实与外部事实已经分裂。
更稳妥的设计是把流程拆成可恢复的状态:
- 在本地事务中写入交易、状态和待发送事件。
- 事务提交后,由定时任务扫描待处理记录,或由
Outbox转发器、事务消息和CDC把事件发送出去。 - 调用外部接口时使用业务幂等键,并把“成功、失败、未知”分别落库。
- 对未知结果主动查询渠道状态,不直接把超时当成失败。
- 由定时任务或其他补偿链路处理长时间停留在中间态的记录。
TCC 和 SAGA 是跨服务一致性的两类模式,不是“更强的本地事务”:
TCC适合能够预留、确认和取消资源的短流程,Try、Confirm、Cancel都必须幂等,并且要处理空回滚和悬挂请求。SAGA把长流程拆成多个本地事务,失败后执行补偿动作。补偿不是数据库回滚,可能有业务损耗,也必须幂等、可重试、可审计。
如果一个批量任务需要调用几千次外部接口,不要把它们塞进一个事务。应使用批次、游标、状态和检查点,让任务可以暂停、恢复和重跑。
七、账务事实与对账:收敛之后还要证明结果正确
本节补上“最终收敛”之外的独立校验。状态进入成功,不等于金额一定正确。
余额适合快速查询,分录和交易流水才是可审计的业务事实。资金变更通常需要在同一个本地数据库事务中,原子地写入分录、更新余额视图并记录业务状态;不能先调用外部渠道,再期待本地事务回滚来抵消已经发生的外部动作。
金额计算应统一单位和精度。以最小货币单位存储整数,或使用明确舍入规则的十进制定点类型,避免浮点数直接参与记账。手续费、汇率、税费和跨账务日处理,都应把舍入模式和归属日期写成可测试的业务规则。
对账至少分成两类:
- 系统内对账:分录、余额、订单和流水之间是否相等,检查重复入账、漏记账和余额漂移。
- 跨系统对账:本方流水与支付渠道、银行或清算机构的账单是否一致,处理成功、失败、未知和金额差异。
按时效还可以分为实时、准实时和日终对账。差异不能只写一条告警日志,而应进入可追踪的差错池或挂账状态,再按规则执行主动查询、补单、冲正、退款、补差或人工复核。
对账发现差异后,系统应先冻结相关自动动作或限制影响范围,再根据差异类型选择查询、重试、冲正、补差或人工复核。冲正不是删除原记录,而是增加一笔有来源、有审批、有方向的反向分录,让账务历史保持可追溯。
对账是独立防线,不是定时任务日志的另一种写法。比如渠道实际扣款成功,但回调丢失,本地既没有成功事件,定时任务也可能因为本地状态没有变化而扫不到;只有主动拉取渠道账单或执行外部对账,才能发现这笔差异。没有对账,所谓“最终一致”只是对链路正常工作的假设。
八、这条一致性主线解决什么,解决不了什么
本章讨论的主线是:本地事务产生事实 → 可靠外发 → 消费端幂等 → 状态机收敛 → 定时或对账补偿。它主要解决的是事件链路的可靠性:事件尽量不丢,失败可以重试,重复不会重复扣款,状态最终能够到达约定终态。
这套骨架不是充分条件,更不能覆盖所有资损来源。至少有四类问题需要单独治理:
| 资损问题 | 一致性骨架为什么解决不了 | 应单独建设的能力 |
|---|---|---|
| 计算错误 | 金额、手续费、汇率、舍入或会计日期算错,可靠传播只会让错误扩散 | 账务模型、金额精度、公式测试、借贷平衡和业务不变量 |
| 并发覆盖 | 两个请求可能都带着错误的旧版本进入本地事务,消息幂等不能替代并发控制 | 行锁、乐观锁、条件更新、隔离级别和并发测试 |
| 外部不一致 | 渠道成功但回调丢失,或渠道与本地对同一笔交易给出不同结果,内部任务可能看不到外部事实 | 主动查询、渠道对账、差错池、挂账、冲正、退款和补单 |
| 人为、权限与安全问题 | 有权限的人误操作、越权操作、密钥泄露或重放请求,不属于消息投递失败 | 最小权限、双人复核、审批流、审计、防重放、风控和紧急开关 |
因此,幂等键、至少一次投递、Outbox、CDC 和状态机是分布式一致性的必要骨架,而不是“钱一定正确”的充分证明。状态机如果允许终态逆转,幂等键如果设计错误或被不同请求绕过,定时任务如果引发重试风暴,这条骨架仍然会产生资损。
这也决定了金融开发实践应该拆成多个主题:本章解决分布式一致性;计算与账务模型、并发控制、外部渠道一致性、权限与安全控制,分别需要独立的设计方法和测试体系。
九、一致性骨架在整体资损防线中的位置
本节只给出定位,不展开计算、并发、外部渠道和安全治理的完整方案。基础设施能降低技术故障风险,却不能替开发者理解业务规则。
资损代码往往不是空指针,而是语义错误:
- 把外部超时误判成失败,随后重复发起不可逆操作。
- 只做消息去重,没有检查退款上限、订单状态或版本顺序。
- 金额单位、精度、舍入或账务日期理解不一致。
- 并发锁粒度错误,扣款与退款、冻结与解冻之间出现竞态。
- 回调、
CDC事件和人工操作乱序到达,状态机允许了非法转移。 - 需求、财务规则和渠道返回码变更后,代码仍按旧语义解释。
消息队列、分布式锁和事务框架能提供可靠性原语,但它们不知道“这笔钱是否应该扣”。金融系统需要分层防御,而不是幻想一个平台替业务做决定。
9.1 事前:让错误难以进入生产
- 业务、财务、技术共同确认金额、状态、退款和冲正规则。
- 把状态转移画成显式状态机,为每个异常和未知状态写测试。
- 对事务内远程调用、浮点金额、缺少唯一键和绕过账务服务的写库行为做静态检查。
- 对超时、重复回调、乱序消息、进程崩溃和数据库切换做故障注入。
- 资金类变更采用灰度、限额和可关闭的功能开关。
9.2 事中:限制错误的影响范围
- 数据库唯一约束、行级锁和状态条件更新作为最后一道写入保护。
- 账户、商户、渠道和全局资金流量设置限额与异常阈值。
- 对高风险操作提供独立的资金开关、隔离队列和人工审核通道。
- 记录完整的业务流水号、请求参数摘要、版本、操作人和
TraceID。
9.3 事后:尽快发现并恢复
- 实时监控中间态超时、消息堆积、死信、对账差异和负余额等异常。
- 对关键金额做分钟级或账务日级对账,而不是只看服务是否返回
200。 - 为查询渠道状态、冲正、补差、冻结和人工复核建立标准流程。
- 演练回放、补偿和灾备切换,确认“发现问题”之后真的能够修复。
十、金融业务设计与上线检查清单
清单应该围绕“本地事实能否最终收敛”提问,而不是只检查是否使用了某个中间件。
设计阶段
- 本地事实有哪些不变量?余额、分录、流水和业务状态是否在正确的本地事务中保持一致?
- 借贷是否平衡,科目和会计日期是否正确,金额单位、精度和舍入规则是否明确?
- 并发扣款、退款和冻结如何控制?使用行锁、乐观锁、条件更新还是其他机制,隔离级别是否匹配?
- 业务唯一键是什么?同一请求重试、换节点重试、换消息重新发送后,如何识别为同一笔业务?
- 哪些状态是终态,哪些是中间态,未知结果如何处理?
- 本地事务提交后,谁负责推动跨服务状态收敛?定时任务、
Outbox、CDC和对账分别负责什么? - 本地事务是否只包含必要的数据库操作?是否把远程调用、批量接口或消息等待放进去了?
- 分录、余额、交易流水和外部账单之间如何对账?差异由谁处理?
编码阶段
- 是否使用业务幂等键和数据库唯一约束,而不是只依赖缓存锁?
- 消费成功后才确认消息吗?重试是否有退避、上限和死信?
- 状态更新是否带版本或条件,能否拒绝旧回调和非法转移?
- 金额是否使用固定单位和明确精度?舍入、手续费和跨日规则是否有测试?
- 定时任务是否有索引、批次、租约、最大重试次数和人工接管状态?
运行阶段
- 是否监控
CDC位点、延迟、堆积、日志保留窗口和死信? - 是否监控定时任务的扫描量、处理延迟、中间态超时和失败原因?
- 是否监控未知结果、对账差异、负余额和异常金额?
- 是否能主动获取渠道账单,发现本地没有记录的外部成功?
- 是否可以按账户、商户、渠道或功能开关限制影响范围?
- 是否有可验证的回放、冲正、补差和灾备演练记录?
结语:可靠性是一条收敛链路
本章讨论的分布式一致性主线是:本地事务产生事实,可靠外发,消费端幂等,状态机收敛,定时任务和对账补偿。它让事件尽量不丢、失败可以重试、重复不会重复扣款,并让跨服务状态最终进入可验证的终态。
但这条主线的前提是本地事实先正确,结果还要由独立对账验证。定时任务不是 CDC 的附属品,CDC 也不是定时任务的替代品;前者是最容易落地的状态巡检和补偿机制,后者是高吞吐、低延迟场景中的实时变更捕获。
计算错误、并发覆盖、外部事实不一致、权限与安全问题,不会因为事件流可靠就自动消失。后续篇章可以分别讨论账务与金额模型、并发控制、渠道一致性与对账、权限审计与安全防线;本章先把分布式一致性的骨架讲清楚。
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时








