说说 RabbitMQ 的事务机制?


面试考察点

  1. 概念掌握:知不知道 RabbitMQ 有事务机制,txSelecttxCommittxRollback 这三个命令干什么用的。

  2. 性能意识:事务机制的代价是什么,为什么生产环境基本不用它。这块答不上来,说明没真在项目中用过。

  3. 替代方案:能不能说出 Confirm 模式,知道它和事务的区别,以及为什么 Confirm 更适合生产环境。

核心答案

RabbitMQ 的事务机制和数据库事务的思路类似:开启事务 → 发消息 → 提交或回滚。通过 txSelect 开启,txCommit 提交,txRollback 回滚。提交成功消息一定到达 Broker,回滚则消息不会被投递。

但事务有个致命问题:同步阻塞。一条消息发完必须等 Broker 响应才能发下一条,吞吐量直接暴跌。所以生产环境基本不用事务,改用 Confirm 模式(也叫 Publisher Confirm),异步确认,性能高得多。

对比项事务机制Confirm 模式
原理同步阻塞,等 Broker 逐条响应异步确认,Broker 批量 ACK
吞吐量很低,约几百 TPS高,可达数万 TPS
可靠性消息不丢消息不丢
使用方式txSelect / txCommit / txRollbackconfirmSelect 开启
生产推荐❌ 不推荐✅ 推荐

深度解析

事务机制怎么用

整个流程很直观:

  • txSelect:告诉 Broker "接下来我要用事务了",Broker 返回 Tx.Select-Ok
  • basicPublish:发消息。注意,消息这个时候并没有真正投递,处于 "待提交" 状态。
  • txCommit:提交事务,Broker 确认消息已持久化并投递,返回 Commit-Ok。如果改成 txRollback,消息直接丢弃。

关键点在于同步等待。每发一条消息,Producer 都得等 Broker 回复才能继续。实测下来,事务模式的吞吐量大概只有非事务模式的几分之一,甚至更低。

代码示例

Channel channel = connection.createChannel();

try {
    // 开启事务
    channel.txSelect();

    // 发送消息
    channel.basicPublish("exchange.name", "routing.key",
            MessageProperties.PERSISTENT_TEXT_PLAIN,
            "订单消息".getBytes());

    // 提交事务
    channel.txCommit();
} catch (Exception e) {
    // 回滚事务
    channel.txRollback();
}

代码很简单,txSelect 开事务,发完 txCommit。中间出了任何异常就 txRollback 回滚,消息不会进队列。

为什么生产环境不用事务

原因就一个字:慢。

事务是同步阻塞的。Producer 发一条消息,必须等 Broker 返回 Commit-Ok 才能发下一条。这个过程中 Producer 线程一直挂着,啥也干不了。在高吞吐场景下,这完全不可接受。

我之前有个项目,早期用了事务模式,压测的时候 TPS 才几百。后来改成 Confirm 模式,同样的硬件直接飙到上万。差距就这么大。

Confirm 模式:生产环境的正确选择

Confirm 模式的思路很简单:异步确认。Producer 发完消息不用等,继续发下一条。Broker 收到消息后异步回调通知 Producer "你那条消息我收到了"。

Channel channel = connection.createChannel();

// 开启 Confirm 模式
channel.confirmSelect();

// 注册确认回调
channel.addConfirmListener(new ConfirmListener() {
    // 消息确认成功
    @Override
    public void handleAck(long deliveryTag, boolean multiple) {
        // 消息已成功到达 Broker
    }

    // 消息确认失败
    @Override
    public void handleNack(long deliveryTag, boolean multiple) {
        // 消息未到达 Broker,需要补发
    }
});

// 正常发送消息,不用等响应
channel.basicPublish("exchange.name", "routing.key",
        MessageProperties.PERSISTENT_TEXT_PLAIN,
        "订单消息".getBytes());

confirmSelect 开启之后,每条消息都会被分配一个唯一的 deliveryTag。Broker 收到后异步回调 handleAck,Producer 在回调里处理确认逻辑。发消息和确认是解耦的,互不阻塞。

Confirm 模式还支持批量确认(multiple = true 时一次 ACK 多条消息),减少网络开销。

常见误区

  • 误区一:以为事务能保证消息被消费端成功消费。不是的。事务只保证消息从 Producer 到达 Broker 这一段的可靠性,不关消费端的事。消费端的可靠性靠手动 ACK。
  • 误区二:觉得 Confirm 模式就万事大吉了。Confirm 只管 "Producer → Broker" 这一段。如果 Broker 宕机,消息还是可能丢。要真正做到不丢,得配合消息持久化(Exchange 持久化 + Queue 持久化 + Message 持久化)。
  • 误区三:以为事务和 Confirm 可以同时用。不行。RabbitMQ 规定一个 Channel 上要么用事务,要么用 Confirm,不能混着来。

面试高频追问

  1. 消息从 Producer 到 Consumer 全链路怎么保证不丢? 三段都得管:Producer 到 Broker 用 Confirm 模式;Broker 自身做持久化(Exchange、Queue、Message 都设为 durable);Consumer 端关掉自动 ACK,手动确认。

  2. Confirm 模式下消息确认失败怎么办? handleNack 回调里做补发。一般配合本地消息表或重试机制,确保最终一致。

  3. Confirm 的批量确认是怎么回事? multiple = true 表示这个 ACK 确认的是 deliveryTag 之前的所有消息。减少了网络开销,进一步提升吞吐量。

常见面试变体

  • "RabbitMQ 如何保证消息不丢?"
  • "RabbitMQ 的 Confirm 模式了解吗?"
  • "事务和 Confirm 模式有什么区别?生产环境用哪个?"

记忆口诀

事务三步:txSelect 开、txCommit 提交、txRollback 回滚。生产不用事务,用 Confirm 异步确认。消息可靠性靠三件套:Confirm + 持久化 + 手动 ACK。

总结

事务机制用 txSelect / txCommit / txRollback 三个命令保证消息到达 Broker,但同步阻塞导致吞吐量太低,生产环境基本不用。改用 Confirm 模式,异步确认,性能高出几十倍。面试把 "事务怎么用" 和 "为什么不用事务" 说清楚就够了。