第一章:Golang企业级分布式事务终局方案概览
在高并发、微服务架构深度落地的现代企业系统中,跨服务、跨数据库、跨消息中间件的一致性保障已成为不可回避的核心挑战。传统XA协议因性能瓶颈与厂商锁定被逐步弃用;TCC模式虽灵活却对业务侵入性强、开发成本高;Saga模式在长事务场景下补偿逻辑复杂且难以覆盖所有异常分支。当前业界正收敛于一种融合型终局方案:基于事件驱动的最终一致性 + 状态机驱动的事务生命周期管理 + 可观测性原生嵌入,其在Golang生态中已形成成熟实践路径。
核心组件构成
- 事务协调器(TxCoordinator):轻量级独立服务,不持久化状态,仅调度事务生命周期,通过gRPC暴露
Begin/Commit/Abort/QueryStatus接口; - 本地事务代理(TxAgent):以Go module形式嵌入各微服务,自动拦截DB操作与消息发送,生成带全局TxID的上下文并注入事件日志;
- 事件总线(EventBus):基于Apache Kafka或NATS JetStream构建,确保事务事件至少一次投递,并支持事务事件幂等消费;
- 状态快照存储(StateStore):使用RocksDB或TiKV存储事务元数据(如TxID、状态、超时时间、参与方列表),读写延迟
关键实现示例
以下为TxAgent中事务提交前的状态校验代码片段:
// 检查本地事务是否已提交且无未确认下游事件
func (a *TxAgent) PreCommit(ctx context.Context, txID string) error {
// 1. 查询本地DB确认业务操作已落库
if !a.db.IsCommitted(txID) {
return errors.New("local transaction not committed")
}
// 2. 查询StateStore确认无pending outbound events
pending, err := a.stateStore.CountPendingEvents(txID)
if err != nil || pending > 0 {
return fmt.Errorf("pending events found: %d", pending)
}
return nil
}
该逻辑确保“提交”动作仅在本地确定性完成且对外依赖可追索的前提下触发,从源头规避悬挂事务。
方案对比维度
| 维度 | Saga(纯补偿) | Seata AT 模式 | 本终局方案 |
|---|---|---|---|
| 业务侵入性 | 高(需编写补偿) | 中(依赖SQL解析) | 低(仅需初始化Agent) |
| 超时控制 | 弱(依赖人工配置) | 强(中心化超时) | 强(状态机+TTL自动驱逐) |
| 故障恢复能力 | 依赖人工介入 | 自动回滚 | 全自动重试+断点续传 |
该方案已在电商履约、金融支付等严苛场景验证:单日支撑2.3亿笔跨域事务,平均端到端延迟187ms,数据不一致率低于0.00002%。
第二章:Saga模式在Golang微服务中的落地实践
2.1 Saga理论本质与长事务分解原理
Saga 是一种将全局长事务拆解为一系列本地短事务的协调模式,每个子事务具备对应的补偿操作,确保最终一致性。
核心思想
- 长事务无法直接加锁或回滚 → 拆分为可独立提交的“正向步骤”
- 每步失败时,按逆序执行预定义的补偿事务(Compensating Transaction)
- 不依赖两阶段锁,规避长时间资源占用
补偿事务示例(伪代码)
def place_order():
order_id = db.insert("orders", {...}) # 步骤1:创建订单
payment_id = pay_service.charge(order_id) # 步骤2:扣款(外部服务)
inventory_service.reserve(order_id) # 步骤3:预占库存
return order_id
def compensate_place_order(order_id):
inventory_service.release(order_id) # 补偿3:释放库存
pay_service.refund(payment_id) # 补偿2:退款(需查表获取payment_id)
db.delete("orders", id=order_id) # 补偿1:删除订单
pay_service.charge()返回值需持久化存储,否则compensate_place_order中无法获取payment_id;补偿操作必须幂等,且不保证原子性,仅保障业务终态一致。
Saga 执行状态流转(Mermaid)
graph TD
A[Start] --> B[Step1: Order Created]
B --> C[Step2: Payment Charged]
C --> D[Step3: Inventory Reserved]
D --> E[Success]
C -.-> F[Fail → Compensate Step1]
D -.-> G[Fail → Compensate Step1+2]
| 特性 | 传统XA事务 | Saga模式 |
|---|---|---|
| 事务粒度 | 全局强一致 | 本地事务 + 最终一致 |
| 锁持有时间 | 跨服务全程锁 | 仅本地DB瞬时锁 |
| 故障恢复能力 | 依赖TM日志回滚 | 显式补偿逻辑驱动 |
2.2 基于Go Channel与Context的正向执行链路编排
正向执行链路需保障时序性、可取消性与错误传播一致性。核心依赖 chan 的同步传递能力与 context.Context 的生命周期协同。
数据同步机制
使用带缓冲通道解耦阶段间数据流,避免阻塞上游:
// stage1 → stage2 的安全管道,容量为10
ch := make(chan Result, 10)
Result 为结构化输出;缓冲区大小依据峰值吞吐预估,过小易阻塞,过大增内存压力。
取消与超时控制
所有阶段监听同一 ctx.Done():
select {
case <-ctx.Done():
return ctx.Err() // 统一传播 cancellation 或 timeout
case ch <- result:
}
确保任意阶段失败或超时,下游立即感知并终止。
执行链路状态对照表
| 阶段 | Channel 模式 | Context 依赖 | 错误透传方式 |
|---|---|---|---|
| 初始化 | 同步发送 | 必须 | panic → err |
| 转换处理 | 缓冲发送 | 必须 | 返回 error |
| 输出聚合 | 同步接收 | 必须 | select ctx |
graph TD
A[Start] --> B{ctx.Err?}
B -- No --> C[Stage1: Process]
C --> D[Send to ch]
D --> E[Stage2: Transform]
E --> F[Receive from ch]
F --> B
2.3 Go泛型实现可复用的Saga协调器(Coordinator)
Saga 模式要求跨服务事务具备补偿能力,而泛型使协调器能统一处理任意类型业务命令与补偿操作。
核心泛型接口设计
type Command[T any] interface {
Execute() error
Compensate() error
}
type Coordinator[T any] struct {
commands []Command[T]
}
Command[T] 抽象执行与回滚逻辑,T 可承载订单ID、库存键等上下文;Coordinator[T] 通过切片聚合同类型命令,避免运行时类型断言。
执行流程
graph TD
A[Start Saga] --> B{Execute all Commands}
B -->|Success| C[Commit]
B -->|Fail| D[Run Compensate in reverse]
D --> E[Rollback completed]
关键优势对比
| 特性 | 非泛型实现 | 泛型 Coordinator |
|---|---|---|
| 类型安全 | ❌ 运行时断言 | ✅ 编译期校验 |
| 复用粒度 | 每个业务独有协调器 | ✅ 单协调器适配多领域 |
- 支持链式构建:
NewCoordinator[OrderID]().Add(CreateOrder{}).Add(ReserveStock{}) - 补偿顺序自动逆序:无需手动维护回滚栈
2.4 分布式超时控制与跨服务断点续挂机制
在微服务链路中,单一服务超时无法反映端到端真实耗时。需构建分层超时契约:API网关设定总超时(如30s),各下游服务按SLA协商子超时(如订单服务8s、库存服务5s),并通过x-request-timeout-ms透传剩余预算。
超时传递与动态裁剪
// 基于Netty的超时头注入逻辑
ctx.channel().attr(ATTR_TIMEOUT).set(
Math.max(100L, requestTimeoutHeader - elapsedMs) // 确保不低于100ms防零值
);
该逻辑确保下游服务接收的是动态衰减后的真实剩余时间,避免因网络抖动导致误熔断;elapsedMs由统一埋点组件在入口处记录,精度达毫秒级。
断点续挂状态映射表
| 状态码 | 含义 | 可续挂 | 持久化位置 |
|---|---|---|---|
206 |
部分完成(含checkpoint) | ✅ | Redis+版本号 |
409 |
并发冲突(需重试) | ❌ | — |
503 |
临时不可用(可退避) | ✅ | Kafka重试队列 |
重试决策流程
graph TD
A[收到503或超时] --> B{是否支持续挂?}
B -->|是| C[查Redis获取last_checkpoint]
B -->|否| D[走幂等重试]
C --> E[构造增量请求体]
E --> F[携带resume_token调用]
2.5 生产环境Saga日志追踪与可视化诊断(集成OpenTelemetry)
Saga模式在分布式事务中天然具备跨服务、异步、补偿等特性,但这也导致传统日志链路断裂、状态不可追溯。OpenTelemetry 提供统一的可观测性标准,为 Saga 全生命周期埋点奠定基础。
数据同步机制
通过 Tracer 在每个 Saga 参与者(如 OrderService、InventoryService、PaymentService)中注入上下文传播:
from opentelemetry import trace
from opentelemetry.propagate import inject
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("saga-order-create") as span:
span.set_attribute("saga.id", "saga-7f3a9b")
span.set_attribute("saga.step", "reserve_inventory")
headers = {}
inject(headers) # 注入 W3C TraceContext 到 HTTP headers
requests.post("http://inventory-service/reserve", headers=headers)
此段代码在 Saga 主干流程中创建根 Span,并显式标注 Saga 实例 ID 与当前步骤,确保跨服务调用时 trace_id 与 baggage 持续传递;
inject()自动序列化 context 至headers,兼容 OpenTelemetry SDK 的 HTTP 传播器。
关键追踪字段映射
| 字段名 | 来源 | 用途 |
|---|---|---|
saga.id |
Saga Coordinator | 全局唯一 Saga 实例标识 |
saga.step |
当前参与者 | 标识执行阶段(e.g., confirm_payment) |
saga.status |
补偿/确认回调 | SUCCESS / COMPENSATED / FAILED |
状态流转可视化
graph TD
A[Start Saga] --> B[reserve_inventory]
B --> C[charge_payment]
C --> D{Success?}
D -->|Yes| E[commit_order]
D -->|No| F[compensate_payment]
F --> G[compensate_inventory]
启用 otel.exporter.otlp.endpoint 指向 Jaeger 或 Grafana Tempo 后,所有 Span 自动聚合为可下钻的 Saga 时序图。
第三章:TCC补偿模型的Go语言精细化实现
3.1 TCC三阶段语义约束与Golang并发安全建模
TCC(Try-Confirm-Cancel)在分布式事务中要求各阶段具备严格的语义隔离性与幂等性。Golang 的 sync/atomic 与 sync.Mutex 需结合业务状态机建模,确保 Try 阶段资源预占、Confirm 阶段不可逆提交、Cancel 阶段可重入回滚。
数据同步机制
使用带版本号的状态原子更新保障并发安全:
type TxState int32
const (
Try TxState = iota
Confirm
Cancel
)
type TCCResource struct {
state int32
mu sync.RWMutex
ver uint64 // CAS 版本戳
}
func (r *TCCResource) Try() bool {
return atomic.CompareAndSwapInt32(&r.state, int32(Try), int32(Try))
}
atomic.CompareAndSwapInt32 保证 Try 状态仅被设置一次;ver 字段预留用于乐观锁升级;RWMutex 在 Confirm/Cancel 阶段保护业务数据写入。
| 阶段 | 幂等性要求 | 可中断性 | 典型操作 |
|---|---|---|---|
| Try | 必须 | 是 | 预占库存、冻结额度 |
| Confirm | 必须 | 否 | 扣减、记账 |
| Cancel | 必须 | 是 | 解冻、释放 |
graph TD
A[Try: 预占] -->|成功| B[Confirm: 提交]
A -->|失败| C[Cancel: 回滚]
B --> D[状态: Committed]
C --> E[状态: Cancelled]
3.2 基于Go接口契约与反射的Try/Confirm/Cancel自动注册体系
TCC(Try-Confirm-Cancel)分布式事务需显式注册三阶段方法,传统方式易出错且耦合度高。本节通过 Go 接口契约约束 + reflect 动态扫描实现零配置自动注册。
核心契约定义
type TCCTransaction interface {
Try(ctx context.Context) error
Confirm(ctx context.Context) error
Cancel(ctx context.Context) error
}
Try预占资源并校验可行性;Confirm幂等提交;Cancel幂等回滚。所有实现必须满足该接口,为反射发现提供类型依据。
自动注册流程
graph TD
A[遍历包内所有结构体] --> B{实现TCCTransaction?}
B -->|是| C[提取方法地址]
B -->|否| D[跳过]
C --> E[注入全局事务注册表]
注册表结构
| 组件名 | 类型 | 说明 |
|---|---|---|
| ServiceKey | string | 结构体全限定名,如 user.UserService |
| TryFunc | reflect.Method | 反射获取的 Try 方法入口 |
| ConfirmFunc | reflect.Method | 同上,用于后续调度 |
自动注册机制将开发关注点从“如何注册”回归到“如何实现业务逻辑”,显著提升 TCC 模块可维护性与扩展性。
3.3 补偿事务幂等性保障与本地补偿任务持久化队列设计
幂等令牌生成与校验机制
补偿操作必须拒绝重复执行。采用 businessId + operationType + timestampMs 的 SHA-256 哈希作为幂等键,并在 Redis 中以 IDEMPOTENT:{hash} 键做 10 分钟 TTL 存储:
String idempotentKey = "IDEMPOTENT:" +
DigestUtils.sha256Hex(bizId + ":" + opType + ":" + System.currentTimeMillis());
Boolean exists = redisTemplate.opsForValue().setIfAbsent(idempotentKey, "1", Duration.ofMinutes(10));
if (!Boolean.TRUE.equals(exists)) {
throw new IdempotentException("Duplicate compensation request rejected");
}
逻辑分析:setIfAbsent 原子写入确保首次请求成功;Duration.ofMinutes(10) 避免长期占用内存,同时覆盖网络重试窗口;哈希中不含随机数,保证相同输入恒定输出,是幂等性的数学基础。
本地补偿任务队列结构
使用嵌入式 RocksDB 持久化待执行补偿任务,避免依赖外部消息中间件:
| 字段名 | 类型 | 说明 |
|---|---|---|
| taskId | String | 全局唯一 UUID |
| payload | byte[] | 序列化后的补偿指令 |
| maxRetry | int | 最大重试次数(默认3) |
| nextExecuteAt | long | 下次执行时间戳(毫秒) |
执行调度流程
graph TD
A[定时扫描RocksDB] --> B{nextExecuteAt ≤ now?}
B -->|是| C[加载任务→执行补偿]
C --> D{成功?}
D -->|是| E[标记完成并删除]
D -->|否| F[更新nextExecuteAt为指数退避时间]
F --> G[写回RocksDB]
第四章:本地消息表方案的高可靠Go工程化封装
4.1 消息表Schema设计与MySQL+Raft多副本一致性权衡
核心表结构设计
为支持高吞吐写入与强一致读取,消息表采用分片友好型Schema:
CREATE TABLE message (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '逻辑主键,全局唯一',
msg_id VARCHAR(36) NOT NULL COMMENT '业务消息ID(UUID v4)',
payload JSON NOT NULL COMMENT '压缩后Base64编码的二进制载荷',
status TINYINT NOT NULL DEFAULT 0 COMMENT '0=待投递, 1=已投递, 2=已归档',
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_msg_id (msg_id),
INDEX idx_status_created (status, created_at)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
payload使用JSON类型兼顾可读性与校验能力;TIMESTAMP(3)精确到毫秒,满足Raft日志时间戳对齐需求;ROW_FORMAT=DYNAMIC避免大字段导致页分裂。
一致性模型权衡要点
| 维度 | MySQL原生复制 | Raft嵌入式共识层 |
|---|---|---|
| 写入延迟 | 低(异步/半同步) | 中(需多数派日志提交) |
| 读取一致性 | 可能过期(从库延迟) | 线性一致(ReadIndex) |
| 运维复杂度 | 低 | 高(成员变更、快照管理) |
数据同步机制
Raft日志条目需映射到MySQL事务,确保原子性:
graph TD
A[Client Write] --> B[Leader Append Log]
B --> C{Log Committed?}
C -->|Yes| D[Apply to MySQL Tx]
D --> E[Commit & Notify Follower]
C -->|No| F[Reject Write]
关键约束:msg_id 全局唯一性由Raft顺序保证,避免MySQL主键冲突;status 更新必须携带updated_at版本戳,供读请求执行线性化校验。
4.2 Go Worker池驱动的异步消息投递与失败重试策略(指数退避+死信隔离)
核心设计思想
基于固定大小的 Worker Pool 并发消费任务队列,每个 worker 独立执行消息处理,并内置可配置的指数退避重试逻辑;永久失败的消息自动路由至死信队列(DLQ)隔离。
指数退避重试实现
func backoffDelay(attempt int) time.Duration {
base := time.Second
max := time.Minute * 5
delay := time.Duration(math.Pow(2, float64(attempt))) * base
if delay > max {
delay = max
}
return delay + time.Duration(rand.Int63n(int64(time.Second)))
}
逻辑说明:第
n次重试延迟为2^n秒(带随机抖动防雪崩),上限 5 分钟。attempt从 0 开始计数,确保首次重试不立即触发。
死信隔离判定条件
- 连续重试 ≥ 5 次
- 单次处理超时 ≥ 30s
- 非临时性错误(如
ErrInvalidPayload,ErrDLQFull)
| 状态类型 | 路由目标 | 是否可人工干预 |
|---|---|---|
| 临时网络错误 | 重试队列 | 否 |
| 业务校验失败 | 死信队列 | 是 |
| 序列化异常 | 死信队列 | 是 |
消息生命周期流程
graph TD
A[新消息入队] --> B{Worker获取}
B --> C[执行Handler]
C --> D{成功?}
D -->|是| E[ACK & 清理]
D -->|否| F[attempt++ < max?]
F -->|是| G[Sleep backoffDelay]
G --> C
F -->|否| H[投递至DLQ]
4.3 基于pglogrepl或Canal的变更数据捕获(CDC)增强型消息表同步
数据同步机制
传统轮询式同步存在延迟高、数据库负载大等问题。pglogrepl(PostgreSQL原生逻辑复制客户端)与Canal(MySQL Binlog解析中间件)分别通过WAL日志和Binlog实时捕获DML变更,实现毫秒级、低侵入的CDC。
核心实现对比
| 维度 | pglogrepl | Canal |
|---|---|---|
| 数据源 | PostgreSQL 10+ | MySQL 5.6+/8.0 |
| 协议层 | 复制协议(Logical Replication) | MySQL Binlog协议 |
| 消息格式 | JSON/protobuf(可自定义) | JSON/Avro(依赖适配器) |
# pglogrepl 示例:监听逻辑解码流
from pglogrepl import PGLogReplication
conn = PGLogReplication("host=localhost dbname=test")
conn.start_replication(slot_name="cdc_slot",
plugin="pgoutput", # 或 wal2json
start_lsn=0x0)
此代码建立逻辑复制连接,
slot_name确保WAL不被回收;plugin决定输出格式——wal2json便于下游解析为结构化变更事件(INSERT/UPDATE/DELETE + old/new tuple)。
流程演进
graph TD
A[源库写入] –> B[WAL/Binlog生成]
B –> C[pglogrepl或Canal消费]
C –> D[解析为标准化变更事件]
D –> E[写入消息表 + 幂等标记]
4.4 消息表与业务DB双写一致性校验工具链(go-dbtap)
核心设计目标
- 实时捕获业务库(MySQL)binlog 与消息表(如
msg_log)的写入时序 - 基于主键+业务时间戳双维度比对,规避单一时钟漂移误差
数据同步机制
// config.go:关键校验策略配置
type CheckPolicy struct {
TimeoutSec int `yaml:"timeout_sec"` // 超时窗口(秒),默认30s
PrimaryKey string `yaml:"primary_key"` // 业务主键字段名,如 "order_id"
TimestampCol string `yaml:"ts_col"` // 业务时间列,如 "updated_at"
MsgTable string `yaml:"msg_table"` // 消息表名
BizTable string `yaml:"biz_table"` // 业务表名
}
该结构定义了跨源比对的锚点字段与容错边界。timeout_sec 决定“延迟一致”的判定窗口;timestamp_col 需为业务侧统一更新的时间戳,避免数据库服务器时钟差异引入误判。
一致性校验流程
graph TD
A[Binlog Reader] -->|解析 biz_table 写入| B(提取 PK + updated_at)
C[MsgTable Poller] -->|查询 msg_log| D(匹配 PK + status=success)
B --> E{PK & TS 匹配?}
D --> E
E -->|不一致| F[告警 + 记录 inconsistency_log]
E -->|一致| G[标记为 verified]
支持的校验模式对比
| 模式 | 触发方式 | 适用场景 |
|---|---|---|
| 增量轮询 | 定时扫描 msg_log | 低频变更、兜底补偿 |
| Binlog驱动 | 监听 biz_table | 高实时性、核心链路 |
| 手动触发校验 | HTTP API 调用 | 故障复盘、发布后验证 |
第五章:金融级幂等设计模板与终局决策指南
核心设计原则:三要素闭环验证
金融级幂等必须同时满足「请求唯一标识(IDEMPOTENCY_KEY)」「业务状态快照(STATE_SNAPSHOT)」「执行结果可重入(RETRY_SAFE_OUTPUT)」三要素。某支付中台在跨境结算场景中,将 idempotency_key = MD5(merchant_id + order_no + timestamp + nonce) 作为主键写入分布式幂等表,并在事务提交前校验该 key 对应的 final_status IN ('SUCCESS', 'FAILED', 'TIMEOUT'),避免因网络超时导致重复扣款。
典型失败模式与修复对照表
| 场景 | 原始方案缺陷 | 金融级修复方案 | 生产验证效果 |
|---|---|---|---|
| 跨服务调用链幂等断裂 | 仅在网关层校验,下游服务未透传幂等键 | 强制 gRPC metadata 携带 x-idempotency-key,各服务接入层自动注入至本地事务上下文 |
支付+清算+记账三系统调用链幂等一致率从 92.3% 提升至 100% |
| 状态机跃迁冲突 | 使用 UPDATE ... SET status='PROCESSED' WHERE status='PENDING',忽略中间态并发覆盖 |
改为 UPDATE ... SET status='PROCESSED', version=version+1 WHERE status='PENDING' AND version=old_version,配合乐观锁版本号 |
每日 37 万笔批量入账任务零双写异常 |
关键代码片段:幂等上下文自动装配
@Aspect
public class IdempotentContextAspect {
@Around("@annotation(idempotent) && args(request,..)")
public Object enforceIdempotency(ProceedingJoinPoint pjp, Idempotent idempotent, HttpServletRequest request) throws Throwable {
String key = extractIdempotencyKey(request);
IdempotentRecord record = idempotentRepo.findByKey(key);
if (record != null && record.isTerminalState()) {
return deserializeResult(record.getPayload()); // 直接返回历史结果
}
// 执行业务逻辑并持久化幂等记录(含完整快照)
Object result = pjp.proceed();
idempotentRepo.save(new IdempotentRecord(key, serialize(result), "SUCCESS", now()));
return result;
}
}
决策树:何时放弃幂等,转向补偿
flowchart TD
A[收到重复请求] --> B{是否已生成不可逆外部动作?}
B -->|是| C[立即触发补偿事务<br>如:反向退款、冲正记账]
B -->|否| D{是否处于最终一致性窗口期?}
D -->|是| E[等待对账平台确认后自动修正]
D -->|否| F[执行幂等逻辑并返回缓存结果]
C --> G[写入补偿审计日志<br>包含原始请求ID、补偿时间、操作人]
F --> H[更新幂等表状态为SUCCESS并刷新TTL]
审计与熔断机制
所有幂等拒绝请求强制落盘至 Kafka idempotency_reject_topic,Flink 实时计算每分钟重复请求率。当 rate > 0.5% 且持续 5 分钟,自动触发 Sentinel 熔断规则,降级为同步强校验模式,并向值班工程师推送企业微信告警(含 trace_id 与上游 IP 归属地)。某基金申赎系统上线后,单日拦截恶意重放攻击 12,843 次,其中 91.7% 来自同一 IDC 出口 IP 段。
生产灰度验证路径
首次上线需按流量分层验证:先开放 0.1% 灰度流量(仅查询类接口),再扩展至 5%(含资金类读操作),最后全量(写操作)。每次扩量前必须完成三方对账平台 T+0 数据比对,确保 idempotent_key 字段在核心账务库、清结算库、监管报送库三者间完全一致。某银行理财销售系统在灰度第三阶段发现 Redis 缓存序列化精度丢失问题,及时回滚并修复 BigDecimal 序列化策略。
