tcc-transaction-nodejs分布式事务实现示例

tcc-transaction-nodejs是基于TCC(Try-Confirm-Cancel)模式实现的分布式事务框架,专为Node.js设计。其核心原理是将一个完整的业务操作拆分为三个阶段,通过协调这三个阶段的执行来保证分布式系统的数据一致性。以下是该框架的工作原理、核心组件及执行流程的详细介绍:

一、TCC模式基础概念

在理解框架之前,需要先明确TCC模式的三个核心阶段:

  1. Try(尝试):预留资源(如冻结库存、预扣余额),但不真正提交业务操作。
  2. Confirm(确认):在所有服务的Try阶段都成功后,执行真正的业务提交(如确认扣减库存、确认扣款)。
  3. Cancel(取消):若任一服务的Try阶段失败,撤销之前所有服务的预留资源(如释放冻结的库存、解冻预扣的余额)。

二、tcc-transaction-nodejs框架核心组件

tcc-transaction-nodejs框架主要由以下组件构成:

1. 事务管理器(TransactionManager)

负责整个分布式事务的生命周期管理,包括:

  • 创建和维护事务上下文(TransactionContext);
  • 协调各参与者的Try、Confirm、Cancel阶段执行;
  • 处理事务的提交(Commit)和回滚(Rollback)操作。

2. 参与者(Participant)

每个微服务作为一个参与者,需实现三个接口:

  • try():预留资源;
  • confirm():确认提交;
  • cancel():取消操作。

3. 事务存储(TransactionStore)

持久化事务状态,确保在系统崩溃或网络故障后能恢复状态。默认支持Redis存储,也可扩展为其他存储(如MySQL)。

4. 事务上下文(TransactionContext)

贯穿整个分布式事务的上下文对象,包含:

  • 全局事务ID(唯一标识一个分布式事务);
  • 分支事务ID(标识每个参与者的子事务);
  • 事务状态(TRYING、CONFIRMING、CANCELLING);
  • 参与者列表及状态。

三、框架工作流程

1. Try阶段执行流程

1
2
3
4
5
6
7
8
客户端请求 -> 主业务服务(TM)
├─ 创建全局事务(生成全局事务ID)
├─ 调用服务A的Try方法(传递事务上下文)
│ └─ 服务A执行本地Try逻辑(预留资源)
├─ 调用服务B的Try方法(传递事务上下文)
│ └─ 服务B执行本地Try逻辑(预留资源)
└─ 调用服务C的Try方法(传递事务上下文)
└─ 服务C执行本地Try逻辑(预留资源)

关键点:

  • 所有Try操作必须是幂等的(可重复调用不影响最终结果);
  • Try阶段需确保资源预留成功,但不真正提交;
  • 若任一服务的Try失败,立即进入Cancel阶段。

2. Confirm阶段执行流程(所有Try成功)

1
2
3
4
5
6
7
8
TM确认所有Try成功 -> 
├─ 标记事务状态为CONFIRMING
├─ 调用服务A的Confirm方法(传递事务上下文)
│ └─ 服务A执行本地Confirm逻辑(确认资源使用)
├─ 调用服务B的Confirm方法(传递事务上下文)
│ └─ 服务B执行本地Confirm逻辑(确认资源使用)
└─ 调用服务C的Confirm方法(传递事务上下文)
└─ 服务C执行本地Confirm逻辑(确认资源使用)

关键点:

  • Confirm操作需保证最终成功(若失败需重试);
  • Confirm操作必须是幂等的(可重复调用不影响最终结果);
  • Confirm阶段可能在系统故障恢复后重新执行。

3. Cancel阶段执行流程(任一Try失败)

1
2
3
4
5
6
7
8
TM检测到Try失败 -> 
├─ 标记事务状态为CANCELLING
├─ 调用服务C的Cancel方法(传递事务上下文)
│ └─ 服务C执行本地Cancel逻辑(释放预留资源)
├─ 调用服务B的Cancel方法(传递事务上下文)
│ └─ 服务B执行本地Cancel逻辑(释放预留资源)
└─ 调用服务A的Cancel方法(传递事务上下文)
└─ 服务A执行本地Cancel逻辑(释放预留资源)

关键点:

  • Cancel操作需保证最终成功(若失败需重试);
  • Cancel操作必须是幂等的(可重复调用不影响最终结果);
  • Cancel阶段按Try的逆序执行(后执行的先回滚)。

四、框架如何保证事务一致性

1. 幂等性设计

框架要求所有Confirm和Cancel操作必须是幂等的,通过以下方式实现:

  • 基于唯一事务ID和分支ID判断操作是否已执行;
  • 执行前先查询操作状态,已完成则直接返回成功。

2. 失败重试机制

  • 当Confirm/Cancel失败时,框架会自动重试(默认配置为3次,可自定义);
  • 事务状态持久化到Redis,确保故障恢复后能继续未完成的Confirm/Cancel。

3. 最终一致性

  • 框架不保证强一致性,但通过补偿机制(Cancel)和重试机制,保证最终一致性;
  • 在高并发场景下,可能存在短暂的数据不一致(如库存已冻结但未确认扣减),但最终状态是一致的。

五、框架使用示例(简化版)

以下是使用tcc-transaction-nodejs的基本代码结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
const { TCC } = require('tcc-transaction-nodejs');
const Redis = require('ioredis');

// 1. 初始化TCC框架(配置Redis存储)
const redis = new Redis({ host: 'localhost', port: 6379 });
const tcc = new TCC({ redis, serviceName: 'order-service' });

// 2. 定义参与者服务(如订单服务)
const orderService = {
// Try阶段:创建待确认订单
try: async (ctx, orderId, productId, quantity) => {
// 创建订单但标记为"待确认"
await db.orders.create({
id: orderId,
productId,
quantity,
status: 'PENDING'
});
},

// Confirm阶段:确认订单
confirm: async (ctx, orderId) => {
// 更新订单状态为"已确认"
await db.orders.update({ status: 'CONFIRMED' }, { where: { id: orderId } });
},

// Cancel阶段:取消订单
cancel: async (ctx, orderId) => {
// 更新订单状态为"已取消"
await db.orders.update({ status: 'CANCELLED' }, { where: { id: orderId } });
}
};

// 3. 定义库存服务参与者
const inventoryService = {
// Try阶段:冻结库存
try: async (ctx, productId, quantity) => {
const product = await db.products.findByPk(productId);
if (product.stock < quantity) {
throw new Error('库存不足'); // 会触发Cancel阶段
}
// 冻结库存(stock减少,frozen增加)
await db.products.update(
{ stock: product.stock - quantity, frozen: product.frozen + quantity },
{ where: { id: productId } }
);
},

// Confirm阶段:确认扣减库存
confirm: async (ctx, productId, quantity) => {
// 减少冻结量(实际扣减在Try已完成)
await db.products.update(
{ frozen: db.sequelize.literal(`frozen - ${quantity}`) },
{ where: { id: productId } }
);
},

// Cancel阶段:解冻库存
cancel: async (ctx, productId, quantity) => {
// 解冻库存(恢复可用库存)
await db.products.update(
{ stock: db.sequelize.literal(`stock + ${quantity}`), frozen: db.sequelize.literal(`frozen - ${quantity}`) },
{ where: { id: productId } }
);
}
};

// 4. 执行分布式事务
async function createOrderWithTCC() {
const ctx = await tcc.begin(); // 创建事务上下文
try {
// 调用订单服务的Try
await tcc.call(ctx, orderService.try, 'ORDER_123', 'PROD_456', 2);
// 调用库存服务的Try
await tcc.call(ctx, inventoryService.try, 'PROD_456', 2);

// 所有Try成功,提交事务(触发Confirm)
await tcc.commit(ctx);
} catch (error) {
// 任一Try失败,回滚事务(触发Cancel)
await tcc.rollback(ctx);
throw error;
}
}

六、框架优缺点

优点

  • 性能较高:相比2PC,TCC无长时间资源锁定,各阶段可异步执行;
  • 扩展性好:支持多种存储后端(Redis、MySQL等);
  • 适合复杂业务:可自定义资源预留和补偿逻辑。

缺点

  • 开发成本高:需为每个服务编写Try、Confirm、Cancel三个接口;
  • 补偿逻辑复杂:需考虑各种异常情况(如部分Confirm成功后系统崩溃);
  • 仅保证最终一致性:不适合对实时一致性要求极高的场景。

七、适用场景

  • 金融支付系统(如转账、充值);
  • 库存管理(如预占库存、扣减库存);
  • 长流程业务(如订单处理、物流配送);
  • 高并发场景(TCC比2PC性能更优)。

总结

tcc-transaction-nodejs通过TCC模式将分布式事务拆分为三个阶段,利用事务上下文和状态持久化机制,保证了跨服务操作的最终一致性。框架的核心在于幂等性设计和失败重试策略,适用于对性能要求较高、业务逻辑复杂的分布式系统。