个人编制软件展示

PSI - Purchase Sale Inventory 进销存软件

PSI 进销存系统分布式事务与数据一致性保障

引言

在分布式架构下,进销存系统需要处理采购、销售、库存、财务等多个服务的数据一致性。本文将介绍 PSI 系统中的分布式事务实现方案,确保跨服务操作的原子性和一致性。

分布式事务方案对比

常见的分布式事务解决方案:

方案 一致性 性能 复杂度
两阶段提交 (2PC) 强一致性
TCC 最终一致性
可靠消息 最终一致性
Saga 最终一致性

核心代码实现

TCC 事务管理器实现:

// TCC 事务管理器
class TCCTransactionManager {
  constructor(config) {
    this.transactionLog = config.transactionLog;
    this.participantRegistry = new Map();
    this.timeout = config.timeout || 30000;
  }

  // 注册参与者
  registerParticipant(transactionId, participant) {
    if (!this.participantRegistry.has(transactionId)) {
      this.participantRegistry.set(transactionId, []);
    }
    this.participantRegistry.get(transactionId).push(participant);
  }

  // 开启事务
  async beginTransaction(transactionId) {
    const transaction = {
      id: transactionId,
      status: 'pending',
      participants: [],
      startTime: Date.now(),
      createdAt: new Date()
    };

    await this.transactionLog.save(transaction);
    return transaction;
  }

  // 预 try
  async tryPhase(transactionId) {
    const transaction = await this.transactionLog.findById(transactionId);
    const participants = this.participantRegistry.get(transactionId) || [];

    const results = await Promise.allSettled(
      participants.map(p => p.try())
    );

    // 检查是否有失败
    const failures = results.filter(r => r.status === 'rejected');
    if (failures.length > 0) {
      // Try 阶段失败,进入取消阶段
      await this.cancelPhase(transactionId);
      throw new Error('Try phase failed, transaction rolled back');
    }

    // 更新事务状态
    transaction.status = 'try-success';
    await this.transactionLog.update(transaction);

    return transaction;
  }

  // 确认 commit
  async confirmPhase(transactionId) {
    const transaction = await this.transactionLog.findById(transactionId);
    const participants = this.participantRegistry.get(transactionId) || [];

    const results = await Promise.allSettled(
      participants.map(p => p.confirm())
    );

    const failures = results.filter(r => r.status === 'rejected');
    if (failures.length > 0) {
      // 记录确认失败,需要人工干预
      transaction.status = 'confirm-failed';
      transaction.error = failures.map(f => f.reason);
      await this.transactionLog.update(transaction);
      throw new Error('Confirm phase failed, requires manual intervention');
    }

    // 更新事务状态
    transaction.status = 'committed';
    transaction.commitTime = new Date();
    await this.transactionLog.update(transaction);

    // 清理注册信息
    this.participantRegistry.delete(transactionId);

    return transaction;
  }

  // 取消rollback
  async cancelPhase(transactionId) {
    const transaction = await this.transactionLog.findById(transactionId);
    const participants = this.participantRegistry.get(transactionId) || [];

    // 逆序取消
    for (let i = participants.length - 1; i >= 0; i--) {
      try {
        await participants[i].cancel();
      } catch (error) {
        // 记录取消失败
        console.error(`Participant ${i} cancel failed:`, error);
      }
    }

    // 更新事务状态
    transaction.status = 'cancelled';
    transaction.cancelTime = new Date();
    await this.transactionLog.update(transaction);

    return transaction;
  }

  // 执行事务
  async execute(transactionId, handler) {
    try {
      // 开启事务
      await this.beginTransaction(transactionId);

      // 执行业务逻辑
      await handler();

      // Try 阶段成功,执行确认
      await this.tryPhase(transactionId);

      // 发送确认指令
      await this.confirmPhase(transactionId);

      return { success: true, transactionId };

    } catch (error) {
      // 发生错误,取消事务
      await this.cancelPhase(transactionId);
      return { success: false, error: error.message };
    }
  }

  // 扫描超时事务
  async scanTimeoutTransactions() {
    const timeout = Date.now() - this.timeout;
    const transactions = await this.transactionLog.findTimeout(timeout);

    for (const tx of transactions) {
      if (tx.status === 'try-success') {
        // Try 阶段成功但未确认,重试确认
        await this.confirmPhase(tx.id).catch(console.error);
      } else if (tx.status === 'pending') {
        // Try 阶段超时,取消事务
        await this.cancelPhase(tx.id).catch(console.error);
      }
    }
  }
}

// TCC 参与者接口
class TCCParticipant {
  constructor(resourceId, service) {
    this.resourceId = resourceId;
    this.service = service;
    this.tryResult = null;
  }

  // Try 阶段:预留资源
  async try() {
    this.tryResult = await this.service.reserve(this.resourceId);
    return this.tryResult;
  }

  // Confirm 阶段:确认使用资源
  async confirm() {
    if (this.tryResult) {
      await this.service.confirm(this.resourceId, this.tryResult);
    }
  }

  // Cancel 阶段:释放预留的资源
  async cancel() {
    if (this.tryResult) {
      await this.service.cancel(this.resourceId, this.tryResult);
    }
  }
}

// 库存服务 TCC 实现
class InventoryTCCService {
  constructor(inventoryService) {
    this.inventoryService = inventoryService;
  }

  // Try: 扣减库存(预留)
  async reserve(params) {
    const { productId, warehouseId, quantity, orderId } = params;

    // 检查库存是否充足
    const available = await this.inventoryService.checkAvailable(
      productId, warehouseId, quantity
    );

    if (!available) {
      throw new Error('Insufficient inventory');
    }

    // 创建预留记录
    const reserveResult = await this.inventoryService.createReservation({
      productId,
      warehouseId,
      quantity,
      orderId,
      status: 'reserved',
      expireAt: new Date(Date.now() + 30 * 60 * 1000) // 30分钟过期
    });

    return reserveResult;
  }

  // Confirm: 确认使用预留
  async confirm(reserveId, reserveResult) {
    // 将预留转为正式库存扣减
    await this.inventoryService.confirmReservation(reserveId);

    // 扣减实际库存
    await this.inventoryService.decreaseStock({
      productId: reserveResult.productId,
      warehouseId: reserveResult.warehouseId,
      quantity: reserveResult.quantity,
      orderId: reserveResult.orderId,
      type: 'sale'
    });
  }

  // Cancel: 取消预留
  async cancel(reserveId, reserveResult) {
    // 释放预留
    await this.inventoryService.cancelReservation(reserveId);
  }
}

可靠消息方案

基于消息队列的最终一致性:

// 可靠消息服务
class ReliableMessageService {
  constructor(mqClient, db) {
    this.mqClient = mqClient;
    this.db = db;
    this.retryPolicy = {
      maxRetries: 3,
      retryInterval: 5000,
      exponentialBackoff: true
    };
  }

  // 发送消息
  async sendMessage(topic, message, options = {}) {
    const msg = {
      id: this.generateId(),
      topic,
      payload: JSON.stringify(message),
      status: 'pending',
      retryCount: 0,
      createdAt: new Date(),
      nextRetryAt: new Date(),
      ...options
    };

    // 存储消息
    await this.db.messageStore.save(msg);

    // 发送到消息队列
    try {
      await this.mqClient.send(topic, msg);

      // 更新发送状态
      msg.status = 'sent';
      msg.sentAt = new Date();
      await this.db.messageStore.update(msg);

      return msg.id;

    } catch (error) {
      msg.status = 'send-failed';
      msg.error = error.message;
      await this.db.messageStore.update(msg);
      throw error;
    }
  }

  // 发送事务消息
  async sendTransactionMessage(topic, message, transaction) {
    const msgId = this.generateId();

    // 注册事务回调
    transaction.on('commit', async () => {
      await this.sendMessage(topic, { ...message, msgId });
    });

    transaction.on('rollback', async () => {
      console.log(`Transaction rolled back, message ${msgId} not sent`);
    });

    return msgId;
  }

  // 消费消息
  async consumeMessage(topic, handler) {
    await this.mqClient.subscribe(topic, async (rawMessage) => {
      const message = JSON.parse(rawMessage);

      try {
        // 处理消息
        await handler(message);

        // 确认消息
        await this.ackMessage(message.id);

      } catch (error) {
        // 处理失败
        await this.handleMessageFailure(message, error);
      }
    });
  }

  // 确认消息
  async ackMessage(messageId) {
    const msg = await this.db.messageStore.findById(messageId);
    if (msg) {
      msg.status = 'acknowledged';
      msg.acknowledgedAt = new Date();
      await this.db.messageStore.update(msg);
    }
  }

  // 处理失败消息
  async handleMessageFailure(message, error) {
    const msg = await this.db.messageStore.findById(message.id);
    if (!msg) return;

    msg.retryCount++;
    msg.lastError = error.message;

    if (msg.retryCount >= this.retryPolicy.maxRetries) {
      // 超过最大重试次数,进入死信队列
      msg.status = 'dead-letter';
      msg.deadLetterAt = new Date();
      msg.deadLetterReason = error.message;
    } else {
      // 计算下次重试时间
      const interval = this.retryPolicy.exponentialBackoff
        ? this.retryPolicy.retryInterval * Math.pow(2, msg.retryCount - 1)
        : this.retryPolicy.retryInterval;

      msg.status = 'pending';
      msg.nextRetryAt = new Date(Date.now() + interval);
    }

    await this.db.messageStore.update(msg);
  }

  // 扫描并重发pending消息
  async resendPendingMessages() {
    const pending = await this.db.messageStore.findPending(
      new Date()
    );

    for (const msg of pending) {
      try {
        await this.mqClient.send(msg.topic, msg);
        msg.status = 'sent';
        msg.sentAt = new Date();
        await this.db.messageStore.update(msg);
      } catch (error) {
        msg.status = 'send-failed';
        msg.lastError = error.message;
        await this.db.messageStore.update(msg);
      }
    }
  }

  generateId() {
    return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

// Saga 模式实现
class SagaOrchestrator {
  constructor(services, compensations) {
    this.services = services;
    this.compensations = compensations;
    this.executedSteps = [];
  }

  // 执行 Saga
  async execute(steps) {
    this.executedSteps = [];

    for (const step of steps) {
      try {
        // 执行步骤
        const result = await step.service(step.input);

        // 记录已执行的步骤
        this.executedSteps.push({
          step: step.name,
          result,
          compensation: step.compensation
        });

      } catch (error) {
        // 发生错误,执行补偿
        await this.compensate();
        throw new Error(`Saga failed at step ${step.name}: ${error.message}`);
      }
    }

    return this.executedSteps;
  }

  // 补偿
  async compensate() {
    // 逆序执行补偿操作
    for (let i = this.executedSteps.length - 1; i >= 0; i--) {
      const executed = this.executedSteps[i];

      try {
        await this.compensations[executed.step](executed.result);
        console.log(`Compensation for ${executed.step} succeeded`);
      } catch (error) {
        console.error(`Compensation for ${executed.step} failed:`, error);
        // 记录补偿失败,需要人工干预
      }
    }
  }
}

最佳实践建议

总结

PSI 进销存系统通过分布式事务技术保障数据一致性:

通过合理选择和实现分布式事务方案,可以构建可靠的分布式进销存系统。

← 下一篇:PSI 进销存系统工作流引擎设计与流程自动化实现