发消息逻辑写在MySQL事务中,导致消费逻辑Bug

1、业务背景

在创建新用户后,需要发送一个新用户注册事件。下游服务收到这个事件后,会查询用户相关的信息。

2、错误的实现逻辑

开启事务 -> 写入新用户信息 -> 发送MQ 消息 -> 提交事务

这个逻辑会有一个问题。下游服务在消费 MQ 消息时,查询用户信息时,可能会查不到。逻辑如下所示

时刻 发消息线程 消费消息线程
T0 开启事务 \
T1 写入新用户信息 \
T2 发送MQ 消息 \
T3 \ 消费MQ 消息,查询用户消息。此时查不到,因为事务未提交
T4 提交事务 \

3、正确的实现逻辑

要确保消费者消费到 新用户注册事件消息 时,一定能读到用户信息。大概有以下几个方式

3.1、用延迟消息实现

既然是因为消费的时候,事务还没提交,导致读数据失败。那么可以考虑使用延迟消息,延迟几秒再消费。

3.2、用RocketMQ 事务消息实现

开启事务 -> 写入新用户信息 -> 发送事务消息 -> 提交事务

RocketMQ 事务消息有个弊端,那就是无法将消息发送到指定的队列。因此如果想要实现局部顺序消费,只能将 topic 的队列数设置为 1。

3.3、用本地消息表实现

在分布式事务(MySQL、与MQ 发消息本质可以理解为分布式事务)解决方案中,本地消息表是一个非常常用的解决方案。

本地消息表的表结构大致如下(当然有很多种变通哈)

sql 复制代码
create table local_message(
    id bigint(20) NOT NULL AUTO_INCREMENT,
    content text default null comment '要发送的内容',
    ...
    ...
)

本地消息表只有一个小毛病,那就是发送后,需要一段时间消息才能被消费到。当然如果想要立即消费到,可以将定时任务的逻辑换成,监听本地消息表的 binlog,实时处理 binlog 的变更。

3.4、用重试日志实现

重试日志,在业务开发中更常见,也更常用。

重试日志只有1个问题。如果应用程序在发送消息时,crash 的话,消息就会丢失。但事实上这个概率很低很低很低。

4、举一反三

核心的问题在于:在 MySQL 的事务中包着其他组件的逻辑,并且有另一个线程在读到该组件的值后,会去读未提交的数据。

类似的逻辑还有:
开启事务 -> 写本地事务 -> 写 Redis -> 提交事务

相关推荐
程序员爱钓鱼2 分钟前
为什么学习 Go?Go 能做什么?
后端·面试·go
程序员爱钓鱼38 分钟前
Rust 切片 Slice 详解:安全访问连续数据
前端·后端·rust
咖啡八杯8 小时前
GoF设计模式——解释器模式
java·后端·spring·设计模式
掘金码甲哥9 小时前
这块终端神器, 必须吹爆!
后端
糖果店的幽灵9 小时前
【DeepAgents 从入门到精通】Context Management 上下文管理
java·人工智能·后端·spring·中间件·langgraph·deepagents
Csvn9 小时前
📊 SQL 入门 Day 8:集合操作 — 用 SQL 做数学里的"并交差"
后端·sql
码事漫谈12 小时前
告别数据孤岛与AI“水土不服”:金仓多模融合时序库如何让数据真正服务于业务
后端
IT_陈寒12 小时前
Redis的持久化配置把我坑惨了:你以为数据安全了?
前端·人工智能·后端
星栈12 小时前
Node 接口该写同步还是异步?
后端·node.js
红烧大青虫13 小时前
HarmonyOS应用开发实战:小事记 - UIAbility 的冷启动/热启动/后台启动三种场景与 launchParam 解析
后端·华为·harmonyos·鸿蒙系统