如何用 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设施

相关推荐
unclecss1 小时前
从 0 到 1 落地 SSE:Spring Boot 3 实战 Server-Sent Events 推送全链路
java·spring boot·后端·http·sse
e***95641 小时前
springboot-自定义注解
java·spring boot·spring
管理大亨1 小时前
Canal:企业数据实时同步的利器
数据库·mysql
stormsha1 小时前
Java 设计模式探秘饿汉式与懒汉式单例模式的深度解析
java·单例模式·设计模式·java-ee
稚辉君.MCA_P8_Java1 小时前
DeepSeek Java 多线程打印的19种实现方法
java·linux·jvm·后端·架构
白露与泡影1 小时前
spring Security 认证流程闭环与调用链路详解
java·后端·spring
i***58671 小时前
Java开发的AI应用框架简述——LangChain4j、Spring AI、Agent-Flex
java·人工智能·spring
6***09261 小时前
MS SQL Server partition by 函数实战三 成绩排名
java
i***27951 小时前
SpringBoot实现异步调用的方法
java·spring boot·spring