如何用 Redpanda + 本地事务,实现“发消息 + 写 DB” 的强一致性!

关注我,从零开始构建基础IT设施
作者:旷野说
定位:Spring Boot 开发者速查手册 + 高并发系统设计认知指南
关键澄清:在分布式系统中,"发消息 + 写 DB" 的强一致性无法靠"同步调用"实现,必须借助事务消息、本地事务表或 Redpanda 的幂等生产者 + 消费幂等的组合策略------没有银弹,只有权衡。


如何用 Redpanda + 本地事务,实现"发消息 + 写 DB" 的强一致性?

我是羅蘭。

在"最右"的打赏系统中,有一个看似简单却致命的需求:

"用户打赏成功后,必须 100% 保证:DB 有记录,且消息已发出。"

早期我们天真地这样写:

java 复制代码
@Transactional
public void sendGift(GiftRequest req) {
    giftRecordMapper.insert(req); // 1. 写 DB
    redpandaTemplate.send("gift-sent", event); // 2. 发消息
}

结果呢?

  • DB 写成功,消息发送失败 → 消息丢失,下游无感知
  • 消息发成功,DB 提交失败(事务回滚)→ 消息重复,下游重复处理

"发消息 + 写 DB" 的原子性,成了资损的黑洞。

直到我们深入 Redpanda 的能力边界,结合本地事务表 + 定时扫表任务 ,才真正守住一致性底线------即使 Redpanda 宕机两小时,恢复后也能自动续发,零消息丢失


一、为什么"同步发消息"不可靠?

Spring 的 @Transactional 只能管理 JDBC 事务无法控制 Redpanda 的消息发送

  • 消息发送在 DB 提交前:若网络超时,你无法知道消息是否真失败,重试可能造成重复。
  • 消息发送在 DB 提交后 :DB 成功,但消息发送失败 → 消息永久丢失

💡 根本矛盾DB 与 Redpanda 是两个独立系统,无法跨系统原子提交。

我们必须换思路:不追求"一次成功",而要"失败可重试 + 重试不重复"


二、我们的方案:本地事务表 + Redpanda 幂等 + 消费幂等

步骤 1:建"消息事务表"(与业务同库)
sql 复制代码
CREATE TABLE `outbox_event` (
  `id` BIGINT AUTO_INCREMENT,
  `event_id` VARCHAR(64) NOT NULL COMMENT '唯一事件ID',
  `topic` VARCHAR(128) NOT NULL,
  `payload` JSON NOT NULL,
  `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=待发送, 1=已发送',
  `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_event_id` (`event_id`)
);
步骤 2:主流程:写 DB + 写消息表(同一个事务)
java 复制代码
@Service
public class GiftService {

    @Transactional
    public void sendGift(GiftRequest req) {
        // 1. 扣款(Redis Lua 保证原子性)
        if (!diamondService.tryDeduct(...)) throw ...;
        
        // 2. 写打赏记录
        giftRecordMapper.insert(req);
        
        // 3. 写消息到本地事务表(同事务!)
        outboxEventMapper.insert(
            new OutboxEvent(UUID.randomUUID().toString(), 
                           "gift-sent", 
                           toJson(new GiftEvent(req)))
        );
    }
}

关键消息与业务数据在同一个 DB 事务中,要么全成功,要么全失败。


三、消息积压在 DB,如何"恢复后自动续发"?

答案:一个独立的定时扫表任务(Polling-based Outbox Pattern)。

核心逻辑:
java 复制代码
@Component
public class OutboxEventPublisher {

    @Scheduled(fixedDelay = 1000) // 每秒扫描
    public void pollAndPublish() {
        List<OutboxEvent> events = outboxEventMapper.findPending(100);
        for (OutboxEvent event : events) {
            try {
                // 发送到 Redpanda(幂等)
                redpandaTemplate.send(event.getTopic(), event.getPayload()).get(2, SECONDS);
                // 标记为已发送
                outboxEventMapper.markAsSent(event.getId());
            } catch (Exception e) {
                log.warn("发送失败,下次重试", e); // 失败则下次继续
            }
        }
    }
}
场景:Redpanda 宕机 2 小时
  • 期间 :所有打赏仍正常处理,消息安全落在 outbox_event
  • 恢复后:扫表任务自动查出积压消息,逐条重试发送
  • 全程无需人工干预,系统自愈

这就是"消息积压在 DB,恢复后自动续发"的实现本质


四、三重保险:确保"不丢不重"

风险 防御措施
消息丢失 本地事务表 + 扫表重试,保证"至少发一次"
消息重复 Redpanda 幂等生产者 + 消费端幂等,保证"最多处理一次"
消费异常 DLQ + 人工补偿 + 监控告警
1. Redpanda 幂等生产者(防重复发送)
yaml 复制代码
spring:
  kafka:
    producer:
      enable-idempotence: true   # 幂等
      acks: all                  # 强一致
      retries: 2147483647        # 无限重试(安全)
2. 消费端幂等(最后一道防线)
java 复制代码
@KafkaListener(topics = "gift-sent")
public void handleGiftEvent(GiftEvent event) {
    if (processedEventService.exists(event.getEventId())) return; // 已处理则跳过
    // 执行业务
    notificationService.push(...);
    processedEventService.record(event.getEventId()); // 标记
}

五、性能与可靠性优化

问题 优化方案
扫表慢 status + created_at 联合索引
空轮询多 无积压时动态延长扫描间隔(1s → 5s)
主库压力 批量发送 + 独立线程池
积压告警 监控 status=0 的 count,超阈值告警

📌 我们的选择不引入 CDC(如 Debezium),用扫表保持架构简单,单机支撑 5K TPS。


强一致性实现口诀(羅蘭实战总结)

同库事务写消息,
扫表异步保可达;
Redpanda 幂等防重发,
消费幂等守底线;
不求一次就成功,
但求重试不翻车。


结语:可靠,往往藏在"笨办法"里

在追求"流式""实时""事件驱动"的今天,一个简单的定时扫表任务,反而成了我们最信赖的"消息守门人"

它不酷,但稳;

它不新,但可靠。

高并发系统的韧性,常常不在前沿技术里,而在这些"老派但有效"的设计中。

关注我,从零开始构建基础IT设施

相关推荐
IT邦德1 分钟前
基于OEL8环境的图形化部署Oracle26ai
数据库·oracle
Wang15309 分钟前
Java编程基础与面向对象核心概念
java
毕设源码-郭学长9 分钟前
【开题答辩全过程】以 康复管理系统为例,包含答辩的问题和答案
java
毅炼13 分钟前
hot100打卡——day17
java·数据结构·算法·leetcode·深度优先
一心赚狗粮的宇叔14 分钟前
mongosDb 安装及Mongosshell常见命令
数据库·mongodb·oracle·nosql·web·全栈
winfreedoms16 分钟前
java-网络编程——黑马程序员学习笔记
java·网络·学习
naruto_lnq26 分钟前
Python生成器(Generator)与Yield关键字:惰性求值之美
jvm·数据库·python
开开心心就好27 分钟前
键盘改键工具免安装,自定义键位屏蔽误触
java·网络·windows·随机森林·计算机外设·电脑·excel
IManiy27 分钟前
总结之Temporal全局速率控制(二)第三方速率控制服务设计
java
OpenMiniServer35 分钟前
电气化能源革命下的社会
java·人工智能·能源