文章 587
评论 5
浏览 228589
SpringBoot + 本地消息表与主库写入不一致:分布式 ID 冲突导致插入失败?雪花算法+唯一索引防重

SpringBoot + 本地消息表与主库写入不一致:分布式 ID 冲突导致插入失败?雪花算法+唯一索引防重

公司用本地消息表做分布式事务的最终一致性。订单库 INSERT 了订单,消息表 INSERT 了一条"待发送"记录。偶尔消息表 INSERT 失败报唯一键冲突——两个不同的服务实例生成了同样的消息 ID。订单已经入库了,消息没有入库,订单的状态就永远卡在"待发送"。对账的时候才发现有几十笔订单"消息未下发"。 问题出在 ID 生成方式上。他们用的是数据库自增 ID——但在分布式环境下,两个实例各写各的订单库,各自生成了相同的自增 ID(比如都是从 1 开始),然后在消息表里产生了冲突。 今天聊聊怎么用雪花算法生成全局唯一 ID,配合唯一索引做防重,让消息表的 INSERT 不再因为 ID 冲突而失败。 数据库自增 ID 为什么不行 本地消息表的标准做法是: 1. 订单库 INSERT 订单(ID=1001) 2. 消息表 INSERT 消息(msg_id=1001, 关联订单 1001) 3. 定时任务轮询消息表,发送消息 4. 发送成功 → UPDATE 消息状态 = SENT 但如果你有两个服务实例,各自的订单库都有自增 ID=1001。两条订单对应同一个 msg_id=10....

本地消息表性能瓶颈:高频写入拖垮主库?异步批量落盘 + 分表策略优化!

本地消息表性能瓶颈:高频写入拖垮主库?异步批量落盘 + 分表策略优化!

公司用本地消息表做分布式事务的最终一致性。每笔订单创建后往消息表里插一条"待发送"记录,后台定时任务轮询发送。业务量上来以后,消息表单表积压了几千万条数据,每次 INSERT 都要等几十毫秒,DB 连接池打满,主库 CPU 飙到 80%。订单创建都跟着变慢了。 本地消息表本来是为了解耦,结果自己成了瓶颈。今天聊聊怎么把消息表的高频写入从主库的业务路径上剥离,用异步批量落盘加分表来扛住海量消息。 问题到底出在哪 本地消息表最朴素的实现是业务代码里直接 INSERT: @Transactional public void createOrder(Order order) { orderMapper.insert(order); // 订单入库 messageMapper.insert(new Message( // 消息入库 —— 跟订单在同一个事务 order.getId(), "ORDER_CREATED", "PENDING" )); } 这种写法,消息的 INSERT 跟业务 SQL 在同一个事务里。单看一条没问题,但当每秒几千个订单同时创建,消息表的 INSERT 就变成了主....

SpringBoot + 本地消息表 + 定时补偿:无中间件依赖的最终一致性轻量方案

SpringBoot + 本地消息表 + 定时补偿:无中间件依赖的最终一致性轻量方案

今天和大家分享一个在分布式系统中实现最终一致性的轻量级方案——本地消息表 + 定时补偿。这套方案不需要引入额外的消息中间件,特别适合资源有限的小型团队或项目。 为什么需要最终一致性? 在微服务架构中,我们经常面临跨服务的数据一致性问题。比如用户下单时,需要同时扣减库存和冻结资金,这两个操作分别在不同的服务中。如果其中一个操作失败,就会出现数据不一致的问题。 传统的解决方案通常是使用分布式事务(如2PC),但这会带来性能损耗和系统复杂性。而消息队列(如RocketMQ、Kafka)虽然能解决这个问题,但需要额外部署和维护中间件。 那么有没有一种更轻量级的方案呢?答案就是今天的主角——本地消息表。 什么是本地消息表? 本地消息表本质上是在业务数据库中创建一张专门用于存储消息的表。当执行业务操作时,将需要异步处理的消息也一并存入这张表中,利用本地事务的ACID特性保证业务操作和消息记录的原子性。 这种方式的核心思想是:既然不能保证所有操作同时成功,那就保证失败的操作能在后续得到补偿。 核心实现原理 本地消息表的实现主要分为三个部分: 消息记录:在业务事务中记录待发送的消息 消息发送:通过定....

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程