MetaERP 从“Oracle EBS 单库单事务”切到“多微服务 + 独立 Schema + 事件总线”后,跨服务不可能再靠一个COMMIT保证强一致。它的做法是:
单服务内用本地 ACID;跨服务用事件驱动 + 本地消息表/事务消息 + Saga 补偿 + 幂等消费 + 关账对账,最终达到“业财可追、账账可对、月结能关”。
下面按“原则 → 机制 → 典型链路 → 异常恢复 → 和传统 ERP 的区别”讲。
一、总体原则:放弃“跨库强一致”,追求“业务最终一致”
MetaERP 的跨服务一致性模型可以概括成:
范围 | 一致性模型 |
|---|---|
单微服务内(AR / 采购 / 库存 / 会计引擎) | 本地数据库事务,强一致 |
同步调用(如校验信用、查主数据) | 短时一致,失败快速回退 |
异步跨域(发票→会计→GL→收款→核销) | 事件驱动 + 最终一致 |
财务关账口径 | 强校验:子账=总账、未清项可解释 |
所以它不是“到处用 2PC”,而是:
- 核心交易短链路:同步 + 资源预占(类似 TCC 思路)
- 长流程业财链路:Saga / 事件驱动
- 财务结果:用对账和关账把“最终一致”钉成“可审计一致”
二、核心机制 1:本地事务 + Outbox(事件不丢)
每个服务只写自己的库。业务数据和“待发事件”在同一个本地事务里落库:
AR 服务本地事务: insert ar_invoice insert ar_open_item insert outbox_event(event_id, aggregate_id, type, payload, status) commit;提交后,再由可靠投递组件把outbox_event发到消息总线。
好处:
- 发票写成功 → 事件一定存在
- 发票回滚 → 事件不会发
- 不会出现“业务成了、事件丢了”
这是跨服务一致性的第一道地基。
三、核心机制 2:消息总线按“聚合键”保序
跨服务不是全局保序,而是按业务对象保序:
invoice_id:发票生命周期事件customer_account_id:回款 / 信用 / 催收事件contract_id:收入确认事件po_id:采购—收货—发票匹配事件
消息系统按这些 key 路由到固定 partition,partition 内 FIFO。
于是:
INVOICE_CREATED INVOICE_VALIDATED INVOICE_POSTED RECEIPT_APPLIED INVOICE_CLOSED不会被下游处理成“先收款再开发票”。
四、核心机制 3:消费端幂等(重复不可怕)
消息语义通常是At-Least-Once,所以下游必须幂等。
会计引擎 / GL / 核销服务一般会建:
processed_event ( event_id primary key, source_system, journal_batch_id, status )处理前:
insert ignore into processed_event(event_id, ...)插入失败 = 已处理过 = 直接返回成功。
结果:
- MQ 重发 3 次 → 只生成 1 笔凭证
- 同一收款事件重放 → 不会重复核销
- 多账簿(CAS / IFRS / 管理账)都源自同一个
event_id
五、核心机制 4:Saga 补偿,而不是跨库回滚
以 P2P 为例:
采购服务:创建 PO(本地事务) → 事件:PO_CREATED 库存服务:收货(本地事务) → 事件:RECEIPT_DONE 发票服务:AP 发票匹配(本地事务) → 事件:INVOICE_MATCHED 会计引擎:生成应付凭证(本地事务) → 事件:AP_JE_CREATED GL:应收/应付统驭更新(本地事务)如果“会计引擎生成凭证”失败:
- 不会回滚 PO / 库存 / 发票库
- 而是:
- 标记事件未处理
- 重试
- 或发补偿事件
- 或进异常工作台
补偿例子:
- 发票不匹配 → 发
INVOICE_MATCH_ROLLBACK→ 释放 PO 占用 - 收款撤销 → 发
RECEIPT_REVERSAL→ 恢复未清项 - 凭证错误 → 红冲事件,而不是 DELETE 原凭证
这就是 Saga:正向有步骤,反向有补偿。
六、核心机制 5:状态机防“乱序/非法跳转”
每个聚合对象有状态约束:
AR 发票: DRAFT → VALIDATED → POSTED → PARTIALLY_RECEIVED → CLOSED下游收到事件时会校验:
- POSTED 事件:当前必须是 VALIDATED
- RECEIPT_APPLIED:发票必须已 POSTED
- CLOSED:未清金额 = 0
不合法:
- 进
event_exception - 不更新余额
- 告警 / 自动重排 / 人工处理
所以即使消息迟到、重发、跨区复制延迟,也不会把账弄脏。
七、核心机制 6:子账—会计引擎—总账的“派生一致”
MetaERP 继承并云原生化了 EBS 的 SLA + GL 模型:
业务事件 → 子账状态变化(AR/AP/INV/PO) → 会计引擎生成日记账 → GL 统驭科目汇总关键约束:
- GL 不直接录应收/应付手工凭证
- GL 余额 = 子账汇总派生
- 多账簿凭证都挂
source_event_id
月结前跑:
AR 未清项总额 = 发票 - 收款 - 贷项 - 坏账 SLA 日记账笔数 = 已处理业务事件数 GL 应收统驭 = AR 子账汇总 多账簿来源事件一致只要跨服务漏处理 / 重复处理 / 状态跳变,关账报表就会红,而不是“看起来平”。
八、核心机制 7:主数据 / 维度一致性靠“共享域 + 版本化”
客户、供应商、科目、利润中心、项目、组织这些跨服务对象:
- 不在每个服务里各存一份“真值”
- 由主数据 / 元数据服务发布事件
- 各服务存只读快照 / 缓存视图
规则:
- 主数据变更带
version - 业务服务处理旧单据时用旧维度
- 新交易用新维度
- 维度映射错 → 会计引擎拒生成凭证
这样避免“AR 里客户名变了,GL 报表还用老编码”这种跨服务漂移。
九、一次完整链路:OTC 跨服务一致
OM:订单确认 → 事件 ORDER_CONFIRMED 信用服务:占用额度(本地事务) → 事件 CREDIT_RESERVED INV:发货 → 事件 GOODS_ISSUED 会计引擎:发出商品/递延成本 → 事件 COGS_DEFERRED 税务/开票:开票 → 事件 INVOICE_POSTED AR:未清项 +1000 会计引擎:应收/收入/销项税 GL:应收统驭 +1000 现金/银行适配:回款 → 事件 RECEIPT_CREATED AR:未分配收款 +980 汇兑:财务费用 20 核销服务:RECEIPT_APPLIED AR:未清项 0 GL:银行 +980,应收 -1000,汇兑损益 20任一步失败:
- 上游不回滚
- 事件重投 / 补偿 / 异常队列
- 月结前必须清零或留痕
十、一句话总结
MetaERP 跨服务数据一致性不是“一个事务锁全公司”,而是:
本地事务保单域,Outbox 保事件不丢,分区保业务内有序,幂等保重复无害,状态机保状态合法,Saga 保失败可补偿,子账—SLA—GL 保财务可派生,月结对账保最终可审计。
对比一下:
维度 | Oracle EBS | MetaERP |
|---|---|---|
跨模块 | 单库 PL/SQL 事务 | 微服务 + 事件 |
一致性 | 强一致(单 DB) | 最终一致 + 关账强校验 |
回滚 | ROLLBACK | 补偿事件 / 红冲 / 异常队列 |
消息 | 接口表批处理 | Outbox + MQ + 幂等 |
财务核对 | 子账导入 GL | 子账派生 GL,多账簿同源 |