攻克微服务数据一致性难题:Egg.js分布式事务全攻略
你是否还在为微服务架构下的数据一致性问题头疼?订单创建后库存未扣减、支付成功但物流状态未更新——这些跨服务数据不一致问题不仅影响用户体验,更可能造成业务损失。本文将带你基于Egg.js生态,从零构建一套分布式事务解决方案,通过可靠消息队列、TCC模式等实战方案,彻底解决跨服务数据一致性难题。读完本文,你将掌握:
- 微服务数据不一致的三大根源及规避方法
- Egg.js环境下基于Redis实现可靠消息队列
- TCC模式在Egg.js中的代码落地与事务补偿
- 分布式事务监控与故障恢复最佳实践
分布式事务挑战与Egg.js应对思路
在微服务架构中,一个业务操作往往需要跨多个服务完成。例如电商平台的下单流程,涉及订单服务创建订单、库存服务扣减库存、支付服务处理支付三个独立服务。当其中某个服务操作失败时(如库存扣减超时),如何保证已成功服务的数据回滚,成为确保业务正确性的关键。
分布式事务挑战
Egg.js作为企业级Node.js框架,虽然未提供官方分布式事务模块,但通过其灵活的插件机制和生命周期管理,可以优雅集成分布式事务能力。核心实现路径包括:
- 可靠消息队列:基于Redis的发布订阅机制,确保消息可靠投递
- TCC补偿事务:通过Try-Confirm-Cancel模式实现业务层事务控制
- Saga模式:长事务的状态管理与补偿链执行
官方文档中关于服务间通信的章节docs/core/remote-service.md,为分布式事务实现提供了基础通信保障。
基于Redis的可靠消息队列实现
可靠消息队列是解决分布式事务的常用方案,其核心思想是将分布式事务拆分为本地事务和消息发送两个步骤。在Egg.js中,我们可以借助plugins/redis/插件实现这一方案。
环境准备与配置
首先通过npm安装Egg.js Redis插件:
npm install egg-redis --save
在项目配置文件config/plugin.js中启用插件:
exports.redis = {
enable: true,
package: 'egg-redis',
};
在config/config.default.js中配置Redis连接:
config.redis = {
client: {
host: '127.0.0.1',
port: 6379,
password: '',
db: 0,
},
};
实现本地消息表与消息发送
创建消息表模型app/model/message.js,用于存储待发送消息:
module.exports = app => {
const { STRING, INTEGER, DATE } = app.Sequelize;
const Message = app.model.define('message', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
businessId: { type: STRING, comment: '业务ID' },
topic: { type: STRING, comment: '消息主题' },
content: { type: STRING, comment: '消息内容' },
status: { type: INTEGER, defaultValue: 0, comment: '0-待发送 1-已发送 2-已消费' },
createdAt: { type: DATE, defaultValue: app.Sequelize.NOW }
});
return Message;
};
在业务服务中实现本地事务与消息发送:
// app/service/order.js
const Service = require('egg').Service;
class OrderService extends Service {
async createOrder(orderInfo) {
const { app } = this;
// 开启本地事务
const result = await app.model.transaction(async t => {
// 1. 创建订单(本地事务)
const order = await app.model.Order.create(orderInfo, { transaction: t });
// 2. 插入消息表(本地事务)
await app.model.Message.create({
businessId: order.id,
topic: 'order_created',
content: JSON.stringify(order),
status: 0
}, { transaction: t });
return order;
});
// 3. 发送消息到Redis(事务提交后)
await this.ctx.service.message.sendPendingMessages();
return result;
}
}
module.exports = OrderService;
消息发送与消费实现
创建消息服务app/service/message.js处理消息发送:
// app/service/message.js
const Service = require('egg').Service;
class MessageService extends Service {
async sendPendingMessages() {
const { app } = this;
// 查询待发送消息
const messages = await app.model.Message.findAll({
where: { status: 0 },
limit: 100
});
for (const msg of messages) {
try {
// 发送消息到Redis
await app.redis.publish(msg.topic, msg.content);
// 更新消息状态
await msg.update({ status: 1 });
} catch (error) {
this.ctx.logger.error('消息发送失败', error);
// 消息发送失败将由定时任务重试
}
}
}
// 消费消息
async subscribe() {
const { app } = this;
const subscriber = app.redis.createClient();
subscriber.on('message', async (channel, message) => {
try {
// 处理消息(调用相应业务逻辑)
const msgContent = JSON.parse(message);
switch (channel) {
case 'order_created':
await this.ctx.service.inventory.deductStock(msgContent);
break;
// 其他消息类型处理
}
// 消息处理成功后更新状态(如需)
} catch (error) {
this.ctx.logger.error('消息处理失败', error);
// 消息处理失败可发送到死信队列
}
});
subscriber.subscribe('order_created', 'payment_success');
}
}
module.exports = MessageService;
TCC模式在Egg.js中的实践
TCC(Try-Confirm-Cancel)模式是另一种常用的分布式事务解决方案,通过业务逻辑的拆分实现事务控制。在Egg.js中,可以通过Service层方法设计实现TCC模式。
TCC模式核心概念
TCC模式将一个分布式事务拆分为三个步骤:
- Try:资源检查与预留
- Confirm:确认执行业务操作
- Cancel:取消执行业务操作,释放资源
以电商下单为例,TCC模式实现如下:
订单服务 库存服务 支付服务
| | |
Try Try Try
| | |
|<---------------|----------------|
| | |
Confirm Confirm Confirm
| | |
|<---------------|----------------|
| | |
完成 完成 完成
代码实现与事务协调
创建库存服务的TCC接口app/service/inventory.js:
// app/service/inventory.ts
import { Service } from 'egg';
export default class InventoryService extends Service {
// Try: 检查并预留库存
async tryDeductStock(productId: string, quantity: number, businessId: string) {
const { app } = this;
// 检查库存
const inventory = await app.model.Inventory.findOne({
where: { productId }
});
if (!inventory || inventory.stock < quantity) {
throw new Error('库存不足');
}
// 预留库存(使用version防止并发问题)
const result = await app.model.Inventory.update(
{
stock: inventory.stock - quantity,
reservedStock: inventory.reservedStock + quantity,
version: inventory.version + 1
},
{
where: {
productId,
version: inventory.version
}
}
);
if (result[0] !== 1) {
throw new Error('库存并发更新失败');
}
// 记录预留日志
await app.model.InventoryReserve.create({
businessId,
productId,
quantity,
status: 'RESERVED'
});
return true;
}
// Confirm: 确认扣减库存
async confirmDeductStock(businessId: string) {
const { app } = this;
// 查询预留记录
const reserve = await app.model.InventoryReserve.findOne({
where: { businessId, status: 'RESERVED' }
});
if (!reserve) {
throw new Error('预留记录不存在');
}
// 更新预留状态
await reserve.update({ status: 'CONFIRMED' });
return true;
}
// Cancel: 取消扣减,释放库存
async cancelDeductStock(businessId: string) {
const { app } = this;
// 查询预留记录
const reserve = await app.model.InventoryReserve.findOne({
where: { businessId, status: 'RESERVED' }
});
if (!reserve) {
return true; // 已经取消或不存在,直接返回成功
}
// 释放库存
await app.model.Inventory.increment(
{
stock: reserve.quantity,
reservedStock: -reserve.quantity
},
{
where: { productId: reserve.productId }
}
);
// 更新预留状态
await reserve.update({ status: 'CANCELED' });
return true;
}
}
创建分布式事务协调器app/service/tcc/coordinator.js:
// app/service/tcc/coordinator.js
const Service = require('egg').Service;
class TccCoordinatorService extends Service {
constructor(ctx) {
super(ctx);
this.transactions = new Map(); // 存储事务上下文
}
// 创建事务
createTransaction() {
const txId = this.ctx.helper.generateUUID();
this.transactions.set(txId, {
status: 'TRYING',
participants: []
});
return txId;
}
// 注册参与者
registerParticipant(txId, participant) {
const tx = this.transactions.get(txId);
if (!tx) {
throw new Error('事务不存在');
}
tx.participants.push(participant);
}
// 提交事务
async commit(txId) {
const tx = this.transactions.get(txId);
if (!tx) {
throw new Error('事务不存在');
}
// 执行所有参与者的confirm操作
for (const participant of tx.participants) {
try {
await participant.confirm();
} catch (error) {
this.ctx.logger.error(`参与者confirm失败: ${error.message}`);
// 记录失败信息,后续人工介入或自动重试
}
}
tx.status = 'CONFIRMED';
return true;
}
// 回滚事务
async rollback(txId) {
const tx = this.transactions.get(txId);
if (!tx) {
throw new Error('事务不存在');
}
// 执行所有参与者的cancel操作
for (const participant of tx.participants) {
try {
await participant.cancel();
} catch (error) {
this.ctx.logger.error(`参与者cancel失败: ${error.message}`);
// 记录失败信息,后续人工介入
}
}
tx.status = 'CANCELED';
return true;
}
}
module.exports = TccCoordinatorService;
在业务层使用TCC协调器app/service/order.js:
// app/service/order.js
const Service = require('egg').Service;
class OrderService extends Service {
async createOrderWithTcc(orderInfo) {
const { ctx } = this;
const txId = ctx.service.tcc.coordinator.createTransaction();
try {
// 创建订单记录(待确认状态)
const order = await ctx.model.Order.create({
...orderInfo,
status: 'PENDING',
txId
});
// 注册订单服务自身的TCC参与者
ctx.service.tcc.coordinator.registerParticipant(txId, {
confirm: async () => {
await order.update({ status: 'CONFIRMED' });
},
cancel: async () => {
await order.update({ status: 'CANCELED' });
}
});
// 调用库存服务的Try方法
await ctx.service.inventory.tryDeductStock(
orderInfo.productId,
orderInfo.quantity,
txId
);
// 注册库存服务的TCC参与者
ctx.service.tcc.coordinator.registerParticipant(txId, {
confirm: async () => {
await ctx.service.inventory.confirmDeductStock(txId);
},
cancel: async () => {
await ctx.service.inventory.cancelDeductStock(txId);
}
});
// 调用支付服务的Try方法(省略)
// 所有Try成功,提交事务
await ctx.service.tcc.coordinator.commit(txId);
return order;
} catch (error) {
// 任何Try失败,回滚事务
await ctx.service.tcc.coordinator.rollback(txId);
throw error;
}
}
}
module.exports = OrderService;
分布式事务监控与故障恢复
分布式事务的复杂性使得监控和故障恢复至关重要。在Egg.js应用中,可以通过以下机制保障事务可靠性:
定时任务与消息重试
利用Egg.js的定时任务插件plugins/schedule/,实现消息重发和事务状态检查:
// app/schedule/resend_message.js
module.exports = {
schedule: {
interval: '1m', // 每分钟执行一次
type: 'all', // 所有worker都执行
},
async task(ctx) {
// 重试发送失败的消息
await ctx.service.message.sendPendingMessages();
// 检查长时间未完成的TCC事务
const pendingTxs = await ctx.model.TccTransaction.findAll({
where: {
status: 'TRYING',
createdAt: {
[Op.lt]: new Date(Date.now() - 15 * 60 * 1000), // 15分钟未完成
}
}
});
for (const tx of pendingTxs) {
ctx.logger.warn(`TCC事务超时: ${tx.id}`);
// 自动回滚超时事务
await ctx.service.tcc.coordinator.rollback(tx.id);
}
},
};
事务状态监控
创建事务状态监控接口app/controller/transaction.js,用于查询和手动处理异常事务:
// app/controller/transaction.js
const Controller = require('egg').Controller;
class TransactionController extends Controller {
async index() {
const { ctx } = this;
const { status, startTime, endTime } = ctx.query;
// 构建查询条件
const where = {};
if (status) where.status = status;
if (startTime && endTime) {
where.createdAt = {
[Op.between]: [new Date(startTime), new Date(endTime)]
};
}
// 查询事务记录
const transactions = await ctx.model.TccTransaction.findAll({
where,
order: [['createdAt', 'DESC']],
limit: 100
});
ctx.body = {
success: true,
data: transactions
};
}
// 手动回滚事务
async rollback() {
const { ctx } = this;
const { txId } = ctx.params;
await ctx.service.tcc.coordinator.rollback(txId);
ctx.body = {
success: true,
message: '事务已回滚'
};
}
}
module.exports = TransactionController;
方案对比与最佳实践
不同分布式事务方案各有优缺点,在Egg.js应用中应根据业务场景选择:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 可靠消息队列 | 低侵入性、高可靠性 | 一致性弱、有延迟 | 非实时场景、异步通信 |
| TCC模式 | 强一致性、性能好 | 侵入业务代码、开发复杂 | 核心业务、实时性要求高 |
| Saga模式 | 长事务支持、灵活性高 | 补偿逻辑复杂 | 长事务场景、跨多个服务 |
最佳实践建议:
- 优先使用可靠消息队列:实现简单,对业务代码侵入小
- 核心业务采用TCC模式:保证强一致性,适合关键业务流程
- 完善监控与报警机制:及时发现并处理事务异常
- 设计幂等性接口:确保消息重复消费和事务重试的正确性
Egg.js的插件生态和灵活架构,为分布式事务实现提供了良好基础。通过本文介绍的方案,你可以根据业务需求,选择合适的分布式事务解决方案,确保微服务架构下的数据一致性。
更多分布式事务实践案例,可以参考Egg.js官方示例examples/和社区教程README.md。在实际项目中,建议结合具体业务场景,选择最适合的方案,并通过充分测试确保事务可靠性。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



