使用同一MySQL、RabbitMQ的跨进程数据竞争问题解决方案咨询
适用的架构方案
方案1:本地消息表(事务发件箱)模式,最推荐
这个是解决这类「数据库操作+消息发送原子性」问题的最成熟低成本方案,实现逻辑如下:
- 在你的MySQL业务库中新增一张
pending_messages表,核心字段包含:id(主键)、msg_content(消息体内容)、queue_name(目标RabbitMQ队列名)、status(枚举值:待发送/已发送/发送失败)、retry_count(重试次数)、create_time(创建时间) - 改造第一个进程的事务逻辑:把原来事务内发送RabbitMQ的逻辑去掉,改为在同一个本地事务中,完成业务记录状态更新 + 插入一条status为「待发送」的消息到
pending_messages表,事务提交后该阶段结束,无需实时发消息 - 新增一个独立的轻量中继进程,定时轮询
pending_messages表中所有status=待发送且重试次数未达上限的记录,按顺序发送到RabbitMQ,发送成功后将该条消息的status更新为「已发送」,或者直接物理删除 - 可选优化:如果不想自己维护中继进程,可以接入CDC工具,监听
pending_messages表的binlog变更,检测到新插入的待发送消息时自动推送到RabbitMQ,可靠性更高
这个方案的优势:
- 业务更新和消息落库是同一个本地事务,完全避免了「事务提交成功但消息没记录」的问题
- 中继进程/CDC的逻辑非常简单,即便中途崩溃,重启后还能重新扫描未发送的消息,保证消息至少投递一次
方案2:优化现有重试机制,适合不想动核心流程的场景
你当前用的状态校验+重试逻辑本身是可落地的,只要做两处优化就能降低负面影响:
- 消费者侧不要校验失败就立刻回推队列,改为采用指数退避重试,比如第一次等1s、第二次等3s、第三次等10s,避免短时间内大量无效重试占满队列资源
- 配置最大重试上限,超过上限的消息直接转入死信队列,触发告警通知人工介入,避免异常消息无限循环占用资源
必要的配套优化
不管用哪种方案,都需要做两个基础适配:
- 消费者侧必须实现幂等处理:可以给每条消息绑定唯一的业务ID作为幂等键,处理前先判断该业务ID是否已经处理过,避免重复投递导致的业务异常
- 本地消息表要定期归档已发送的历史消息,避免表数据量过大影响轮询性能
内容的提问来源于stack exchange,提问作者smiler
相关产品推荐
相关产品推荐

