架构师方案-数据库事务和消息的一致性方案

前言:

在传统的数据库应用中,数据库事务与消息的发送是独立的两个操作,如果数据库事务因为某种原因失败了,消息可能已经被发送出去了,这就导致了数据的不一致性。

方案1:下游服务过滤异常消息

在第3步中,如果发现分发的消息的业务唯一id在数据库不存在,就不执行业务操作

注意:这个并没有解决数据库事务和消息的一致性,只是从业务影响上,忽视程序异常(鸵鸟政策)

方案2:数据库事务提交之后,通过发布/订阅模式将消息发送给消息队列

arduino 复制代码
    private final ApplicationEventPublisher applicationEventPublisher;
    /**
     * 保存单据、并发送消息
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveOrderAndPushMQ() {
        //业务省略
        expectedPayBillRepository.save(expectedPayBill);
        //这里发送事件里的方法并不是立刻执行的
        applicationEventPublisher.publishEvent(new BusinessTaskEvent("发送BusinessTask消息", expectedPayBill));
    }
scala 复制代码
public class BusinessTaskEvent extends ApplicationEvent {

    private static final long serialVersionUID = -6206752641309458207L;
    public BusinessTask getBusinessTask() {
        return businessTask;
    }

    private ExpectedPayBill expectedPayBill;

    public BusinessTaskEvent(Object source, ExpectedPayBill expectedPayBill) {
        super(source);
        this.expectedPayBill = expectedPayBill;
    }
}

真正发送事件

less 复制代码
@Component
@RequiredArgsConstructor
@Slf4j
public class TaskListener {

    /**
     * 阿里云kafka
     */
    private final AliYunKafkaProducer aliYunKafkaProducer;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void createPayableStatementTask(BusinessTaskEvent businessTaskEvent) {
        //业务省略
        ExpectedPayBill expectedPayBill = businessTaskEvent.getExpectedPayBill();
        // 发送消息触发任务执行
        aliYunKafkaProducer.send(getMessageTopic(), JSON.toJSONString(expectedPayBill));
    }

}

核心注解 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)

方案3:监听数据库binlog变更,再发送事件

方案 实现复杂度 优劣
下游服务过滤异常消息 简单 这个并没有解决数据库事务和消息的一致性,容易给下游造成误导
数据库事务提交之后,通过发布/订阅模式将消息发送给消息队列 简单 需要依赖事务框架
监听数据库binlog变更,再发送事件 复杂 引入中间件,增加系统复杂度,但是业务和事件彻底解耦

参考

事务性发件箱模式:解决数据库事务和消息的一致性
canal使用
TransactionalEventListener解决事物提交与消息发送顺序问题
相关推荐
70后写java的老码农1 小时前
Spring Boot 3.x + ShardingSphere 5.x + MyBatis-Plus 3.x:订单分库分表实战(含两个踩坑记录)
java
霸道流氓气质1 小时前
AI 翻译插件:翻译记忆级联复用、术语混合检索与结构化质量门禁的 Vue3 + Spring AI 生产级实战
java·人工智能·spring
斯维赤1 小时前
OCR到底是接入第三方API 还是自己封装?
java·ocr
利威尔xu1 小时前
Go IM 源码实战(11):token 只是个 HASH,多端互踢的顺序错了就踢错人
后端·微服务·面试·架构·golang
充值内卷1 小时前
把 Visio 和 Axure 塞进一个 1.5 MB 的 exe:Visioxure 的架构与取舍
架构·axure
码智社1 小时前
Redis 哨兵模式详解:从原理到生产实践
java·redis
QianhangQianping2 小时前
一块大屏的数据在浏览器里怎么变成画面:背景、子屏、前景三层结构
前端·低代码·架构·编辑器·数据可视化·大屏可视化
救救孩子把2 小时前
结构化输出为何“格式正确、答案错误”?JSON Schema 的能力边界
android·java·json·agent·agent服务
猿究院--Cu-Sn合金2 小时前
Spring Boot + DeepSeek 求职 Agent 实践:工具注册、多轮调用与简历 JD 对比
数据库·人工智能·spring boot·后端·deepseek
程序员cxuan2 小时前
别学 subagent 了,很可能要凉了。
后端·agent·ai编程