基于本地消息表的可靠消息最终一致性分布式事务解决方案

399 阅读5分钟

一、方案概述

本地消息表方案 是一种通过 数据库本地事务 保证业务操作与消息发送原子性的分布式事务解决方案。其核心思想是:将消息存储在与业务数据相同的数据库中,利用本地事务的原子性,确保业务操作与消息记录同时成功或失败,再通过 异步重试机制 实现消息的可靠投递,最终达成数据一致性。


二、核心原理

1. 架构角色

事务发起方(Producer) :执行本地业务操作并写入消息表。 • 本地消息表:存储待发送消息,与业务数据同库同事务。 • 消息消费者(Consumer) :订阅并处理消息,保证幂等性。 • 消息状态补偿服务:定时扫描消息表,重试失败消息。

2. 工作流程
  1. 业务操作与消息记录 • 事务发起方在本地事务中执行业务操作(如更新订单状态),并同步插入一条消息到本地消息表。 • 原子性保障:业务操作与消息插入在同一事务中,确保二者同时成功或回滚。
  2. 消息异步投递 • 事务提交后,通过独立线程或定时任务读取消息表中的 待发送 消息,调用消息队列(MQ)发送。 • 发送成功后更新消息状态为 已发送;失败则记录重试次数,等待下次重试。
  3. 消息消费与确认 • 消费者从MQ拉取消息,执行业务逻辑(如扣减库存)。 • 消费成功后返回ACK,MQ删除消息;失败则重试投递(需消费者幂等)。
  4. 消息状态补偿 • 定时任务扫描消息表,处理 发送失败未确认 的消息,重新投递或标记为死亡消息(需人工介入)。

image-20250401224533948

三、关键技术实现

1. 本地消息表设计
CREATE TABLE local_message (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,  -- 消息ID
  biz_id VARCHAR(64) NOT NULL,          -- 业务唯一ID(如订单号)
  topic VARCHAR(128) NOT NULL,          -- MQ主题
  content TEXT NOT NULL,                -- 消息内容(JSON格式)
  status TINYINT NOT NULL DEFAULT 0,    -- 状态(0-待发送,1-已发送,2-已确认,3-死亡)
  retry_count INT NOT NULL DEFAULT 0,   -- 重试次数
  create_time DATETIME NOT NULL,        -- 创建时间
  update_time DATETIME NOT NULL         -- 更新时间
);

索引优化:对 statuscreate_time 建立联合索引,加速补偿任务查询。

2. 消息发送与重试

发送线程: 独立线程池轮询消息表中 status=0 的消息,调用MQ发送。

@Scheduled(fixedDelay = 5000) // 每5秒执行一次
public void scanAndSendMessages() {
    List<Message> messages = messageDao.selectPendingMessages();
    for (Message msg : messages) {
        try {
            mqProducer.send(msg.getTopic(), msg.getContent());
            messageDao.updateStatus(msg.getId(), Status.SENT);
        } catch (Exception e) {
            messageDao.incrementRetryCount(msg.getId());
            if (msg.getRetryCount() >= MAX_RETRY) {
                messageDao.markAsDead(msg.getId());
            }
        }
    }
}
3. 消费者幂等性设计

唯一业务标识:消息中携带 biz_id,消费前检查是否已处理。

public void handleMessage(Message message) {
    String bizId = message.getBizId();
    if (duplicateCheckService.isProcessed(bizId)) {
        return; // 已处理,直接跳过
    }
    // 执行业务逻辑
    inventoryService.deductStock(message.getProductId(), message.getQuantity());
    // 记录处理状态
    duplicateCheckService.markAsProcessed(bizId);
}

数据库唯一约束:通过唯一索引或乐观锁防重。

4. 死亡消息处理

人工干预:提供管理界面查看并手动重试死亡消息。 • 告警机制:当死亡消息数量超过阈值时触发告警。


四、最佳实践示例:订单支付与库存扣减

场景描述

• 用户支付成功后,订单服务需异步通知库存服务扣减库存。 • 要求:支付成功必须扣减库存,允许短暂延迟。

实现步骤
  1. 订单服务(Producer) • 支付成功后,在本地事务中更新订单状态并插入消息:

    BEGIN;
    UPDATE orders SET status = 'PAID' WHERE id = 'ORDER_001';
    INSERT INTO local_message (biz_id, topic, content, status) 
    VALUES ('ORDER_001', 'stock_deduction', '{"productId":"P1001","quantity":1}', 0);
    COMMIT;
    
  2. 消息补偿服务 • 定时任务扫描 local_message 表中 status=0 的记录,发送到MQ(如RocketMQ):

    // 发送逻辑参考前文代码
    
  3. 库存服务(Consumer) • 监听MQ主题 stock_deduction,消费消息并扣减库存:

    @RocketMQMessageListener(topic = "stock_deduction", consumerGroup = "stock_group")
    public class StockListener implements RocketMQListener<String> {
        @Override
        public void onMessage(String message) {
            OrderEvent event = parseMessage(message);
            if (redisCache.get(event.getOrderId()) != null) {
                return; // 幂等检查
            }
            inventoryService.deduct(event.getProductId(), event.getQuantity());
            redisCache.set(event.getOrderId(), "PROCESSED", 24, TimeUnit.HOURS);
        }
    }
    
容错处理

消息发送失败:补偿任务最多重试3次,超过后标记为死亡消息。 • 消费端宕机:MQ自动重试投递,消费者依赖幂等性避免重复扣减。 • 数据不一致:定时核对订单与库存状态,触发对账补偿。


五、方案优缺点

优点

强可靠性:利用本地事务保证消息持久化,无消息丢失风险。 • 业务低侵入:无需改造现有服务接口(对比TCC)。 • 适用于异构系统:消费者只需实现幂等逻辑,无需感知生产者细节。

缺点

数据库压力:高频消息场景下,消息表可能成为性能瓶颈。 • 时效性有限:依赖定时任务轮询,消息延迟通常在秒级。 • 需额外组件:需实现消息补偿服务,增加运维复杂度。


六、适用场景

  1. 异步通知场景:如订单支付后通知库存、物流、营销系统。
  2. 数据同步场景:如数据库变更同步到缓存或搜索引擎。
  3. 最终一致性要求:允许短暂延迟,如用户积分发放、日志记录。

七、优化策略

  1. 消息表分库分表:按业务ID哈希分片,避免单表过大。
  2. 批量消息发送:补偿任务批量读取消息,减少数据库IO。
  3. MQ事务消息集成:结合RocketMQ事务消息,替代本地消息表(需MQ支持)。
  4. CDC(变更数据捕获) :通过数据库日志(如MySQL Binlog)捕获变更事件,替代手动写入消息表。

八、总结

本地消息表方案通过 本地事务原子性异步重试机制 平衡了可靠性与性能,是分布式系统中实现最终一致性的经典模式。关键成功因素: • 消息表设计:合理分片、索引优化。 • 幂等消费:避免重复处理导致数据错误。 • 监控与告警:实时跟踪消息积压与处理延迟。

通过合理架构设计,该方案可广泛应用于电商、金融、物流等领域,成为高可用分布式系统的基石。