Back to Article

03. 金融级微服务工程实践

03. 金融级微服务工程实践

本章把“能调用起来”的微服务推进到“能承受金融业务”的工程实践。金融系统的核心不是技术名词,而是资金、身份、风险、合规、审计和恢复能力。

1. 金融微服务的质量目标

普通业务接口常见目标是“功能正确、性能可接受”。金融微服务还要补齐:

  • 资金正确:金额、币种、方向、状态不可错。
  • 可追溯:每次状态变化可查来源、操作人、请求 ID、上下游调用。
  • 可恢复:失败后能重试、补偿、对账、人工处理。
  • 可审计:日志、流水、审批记录、配置变更可留痕。
  • 可隔离:单个依赖故障不能无限扩散。
  • 可降级:非关键能力失败时核心链路仍有明确策略。
  • 可演练:故障处理不能只写在文档里。

2. 领域建模:先画业务事实

以支付为例,最低限度要区分:

  • 支付订单:商户发起的一次支付请求。
  • 支付流水:每一次通道尝试。
  • 账户流水:账户余额变化记录。
  • 账务分录:借贷记账事实。
  • 风控决策:对交易的风险判断。
  • 通知记录:对用户、商户、内部系统发出的通知。
  • 对账记录:内部账和外部通道账的核对结果。

错误示例:一个 payment 表里塞所有字段,状态随便改,失败时不知道哪一步成功。

改进示例:

  • 支付订单只表达商户视角的支付状态。
  • 支付流水表达通道调用状态。
  • 账务服务独立记录借贷分录。
  • 通知服务独立处理可重试通知。
  • 对账服务后置发现差错。

3. 状态机:金融业务不要靠 if-else 漫游

支付订单示例状态:

  • CREATED
  • RISK_CHECKING
  • RISK_REJECTED
  • PAYING
  • PAY_SUCCESS
  • PAY_FAILED
  • CLOSED
  • REFUNDING
  • REFUNDED

状态转换要回答:

  • 哪个角色/系统可以触发。
  • 前置状态是什么。
  • 是否幂等。
  • 是否产生流水。
  • 是否发消息。
  • 失败如何处理。

示例:

CREATED -> RISK_CHECKING -> PAYING -> PAY_SUCCESS
CREATED -> RISK_CHECKING -> RISK_REJECTED
PAYING -> PAY_FAILED
PAY_SUCCESS -> REFUNDING -> REFUNDED

数据库层可以保存:

  • 当前状态。
  • 状态版本号。
  • 最近变更时间。
  • 状态变更流水。

更新时使用乐观锁:

update payment_order
set status = 'PAY_SUCCESS',
    version = version + 1,
    updated_at = now()
where order_no = ?
  and status = 'PAYING'
  and version = ?;

如果更新行数为 0,要判断是重复通知、非法状态还是并发冲突。

4. 幂等:重试世界里的生命线

金融系统中失败经常是不确定的:

  • 客户端超时,但服务端已经处理成功。
  • 服务端调用通道超时,但通道后来成功。
  • MQ 消息重复投递。
  • 用户重复点击。
  • 网关重试。

幂等设计层次:

4.1 请求幂等

同一商户订单只能创建一次支付:

create unique index uk_payment_merchant_order
on payment_order(merchant_id, merchant_order_no);

服务逻辑:

  1. 收到请求。
  2. 按唯一键插入。
  3. 如果唯一键冲突,查询已有订单。
  4. 如果参数一致,返回已有结果。
  5. 如果参数不一致,返回幂等冲突。

4.2 消息幂等

消费 MQ 消息时记录消费表:

create table consumed_message (
    message_id varchar(64) primary key,
    topic varchar(128) not null,
    consumed_at timestamp not null
);

处理逻辑:

  1. 本地事务中先插入 message_id
  2. 插入成功则执行业务。
  3. 主键冲突说明已处理,直接 ack。

4.3 回调幂等

通道回调可能重复。要用通道流水号、支付流水号、状态机前置状态处理,不能每次回调都重复记账。

5. 一致性:不要一上来就追求全局强事务

5.1 本地事务

单服务单数据库内,优先使用本地事务。它简单、可靠、容易排查。

5.2 Outbox/事务消息

本地数据变更和事件写入同一个数据库事务:

begin;
insert into payment_order(...);
insert into outbox_event(event_id, aggregate_id, type, payload, status);
commit;

后台任务或 CDC 将 outbox 事件发送到 MQ。好处:

  • 本地状态和事件不会一成一败。
  • MQ 短暂不可用不影响本地事务提交。
  • 可重放、可审计。

5.3 Saga/补偿

适用于长事务:

  1. 创建支付。
  2. 冻结账户资金。
  3. 调用通道扣款。
  4. 记账。
  5. 通知。

如果第 3 步失败,需要解冻资金;如果第 4 步失败,可能需要补记账或进入人工处理。

补偿不是简单反向 SQL。补偿也要符合业务规则和审计要求。

5.4 TCC

Try/Confirm/Cancel 适合明确资源预留的场景,例如冻结资金:

  • Try:检查并冻结。
  • Confirm:扣减冻结金额。
  • Cancel:释放冻结。

TCC 难点:

  • 空回滚。
  • 悬挂。
  • 幂等。
  • 超时恢复。

初学阶段先理解模式,不建议在不了解业务约束时强行套 TCC。

6. 对账:承认分布式系统会出错

对账不是“出了事故才做”,而是金融系统常规能力。

对账对象:

  • 内部支付订单 vs 支付流水。
  • 支付流水 vs 外部通道账单。
  • 支付成功记录 vs 账务分录。
  • 退款记录 vs 原支付记录。

对账流程:

  1. 拉取或接收外部账单。
  2. 标准化格式。
  3. 按业务主键匹配。
  4. 比较金额、币种、状态、时间。
  5. 生成差错单。
  6. 自动补偿或人工处理。
  7. 留痕。

差错类型:

  • 我方成功,对方失败。
  • 我方失败,对方成功。
  • 金额不一致。
  • 重复扣款。
  • 状态延迟。
  • 缺单。

7. 可观测性:技术指标和业务指标都要有

7.1 技术指标

  • QPS。
  • P50/P95/P99 延迟。
  • 错误率。
  • 超时率。
  • 线程池队列长度。
  • 数据库连接池使用率。
  • MQ 堆积。
  • GC 暂停。

7.2 业务指标

  • 支付创建数。
  • 支付成功率。
  • 风控拒绝率。
  • 通道成功率。
  • 对账差错率。
  • 退款成功率。
  • 人工审核积压。

7.3 Trace 标签

调用链应包含:

  • traceId
  • requestId
  • merchantId
  • orderNo
  • paymentId
  • tenantId
  • channel

注意日志脱敏,不要把身份证、银行卡、手机号、完整 token 写入日志。

8. 稳定性设计

8.1 超时预算

每条链路都要有总预算。不能每层都配 3 秒,最终叠成 30 秒。

示例:

用户请求总预算:2000ms
- 网关认证:100ms
- payment 本地校验:100ms
- risk RPC:400ms
- account RPC:300ms
- 本地落库:200ms
- 预留网络和序列化:300ms
- 响应返回:100ms
- 缓冲:500ms

8.2 舱壁隔离

不要让一个下游拖垮所有请求。可以按依赖分线程池、连接池、限流器。

8.3 降级策略

降级必须业务确认:

  • 营销券不可用:可不发券,但支付继续。
  • 短信不可用:站内信补充。
  • 风控不可用:高风险交易拒绝或人工审核。
  • 账务不可用:支付核心链路是否必须阻断要严肃评审。

8.4 灰度和回滚

灰度前检查:

  • 新老接口兼容。
  • 数据库变更向前兼容。
  • 消息消费者兼容。
  • 配置中心可回滚。
  • 指标和告警已接入。

回滚不是重新部署旧包这么简单。如果新版本写了旧版本不认识的数据,就可能无法回滚。

9. 发布流水线

建议流水线:

  1. 编译。
  2. 单元测试。
  3. 静态扫描。
  4. 依赖漏洞扫描。
  5. 构建镜像。
  6. 镜像扫描。
  7. 集成测试。
  8. 契约测试。
  9. 部署到测试环境。
  10. 自动冒烟。
  11. 预发环境压测或回归。
  12. 灰度发布。
  13. 指标观察。
  14. 全量发布。
  15. 发布后对账和巡检。

DORA 的研究长期强调软件交付能力与组织、平台和流程有关。2025 DORA 报告关于 AI 的核心提醒是:AI 会放大已有能力和问题。因此微服务团队不能只追求“生成更多代码”,而要把测试、平台、价值流、可观测性和反馈循环补齐。

10. 本章练习

练习 1:设计支付幂等表

设计表:

  • payment_order
  • payment_request_log
  • payment_status_history

要求:

  • 支持同一商户订单幂等。
  • 支持参数不一致时识别冲突。
  • 支持查询每次请求。

练习 2:设计 Outbox

当支付成功时,写出:

  • 支付订单状态更新。
  • Outbox 事件写入。
  • 事件投递任务。
  • 投递失败重试。
  • 消费端幂等。

练习 3:设计对账差错处理

给出三种差错:我方成功对方失败、我方失败对方成功、金额不一致。分别写处理策略和人工介入条件。

11. 本章检查清单

  • 能说清支付订单、流水、账务分录、对账记录的区别。
  • 能为写接口设计幂等键和唯一约束。
  • 能解释 Outbox、Saga、TCC 的适用边界。
  • 能列出技术指标和业务指标。
  • 能设计灰度发布前的兼容性检查。