第27章 RabbitMQ / RocketMQ 异常
27.1 RabbitMQ 常见异常
| 异常 | 触发场景 |
|---|---|
AlreadyClosedException | 在 Channel/Connection 已经关闭后继续尝试使用(常见于连接被服务端主动断开后,业务代码没有检测到就继续调用) |
IOException(发布确认超时等) | 网络问题或 Broker 端资源限制(如内存告警水位触发流控) |
ShutdownSignalException | Connection/Channel 因为协议错误或服务端主动关闭而收到的关闭信号 |
连接意外断开的常见诱因:
- 心跳超时:客户端和 Broker 之间的心跳(heartbeat)机制检测到对方失联,主动断开连接。网络不稳定或者客户端所在 JVM 发生长时间 GC 停顿(导致心跳线程被暂停调度)都可能触发误判式断连。
- Broker 内存/磁盘告警水位:RabbitMQ 在内存或磁盘使用超过阈值时会阻塞发布者(发布者流控),此时客户端的
basicPublish可能会长时间阻塞或抛出相关异常,需要结合 RabbitMQ 管理后台查看告警状态。
ACK 超时/消费者确认异常:
channel.basicConsume(queue, false, (consumerTag, delivery) -> {
try {
process(delivery);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); // 手动确认
} catch (Exception e) {
channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, true); // 处理失败,重新入队
}
}, consumerTag -> {});
未正确处理确认(ACK/NACK)的后果:如果消费者收到消息后由于代码异常直接崩溃、且没有在合理的地方做好 ACK/NACK 逻辑(比如把 basicAck 放在了可能抛异常的业务代码之后但没有 try-catch 包裹),消息会在网络断开重连后被重新投递(因为 Broker 没有收到确认,认为消息还没被成功处理),如果业务处理不是幂等的,会导致重复处理的问题。
27.2 RocketMQ 常见异常
| 异常 | 触发场景 |
|---|---|
RemotingConnectException | 客户端无法连接到 NameServer 或 Broker,通常是网络问题或对应服务未启动 |
RemotingTimeoutException | 请求 NameServer/Broker 超时 |
MQClientException | 客户端配置错误、Topic 不存在、消费者组配置冲突等多种场景的通用包装异常 |
MQBrokerException | Broker 端处理请求时返回的错误(比如消息大小超限、Broker 磁盘空间不足拒绝写入) |
消息发送失败的重试与幂等设计:
try {
SendResult result = producer.send(msg);
} catch (MQClientException | RemotingException | MQBrokerException e) {
// RocketMQ 客户端内部已经有默认重试机制(retryTimesWhenSendFailed),
// 这里捕获到的是重试耗尽后的最终失败,需要业务侧做进一步的补偿处理(比如记录到本地待重发表)
log.error("消息发送最终失败", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
消费端幂等性是排查"重复消费"问题的核心,而不是某个具体异常:无论是 RabbitMQ 的重新投递还是 RocketMQ 的消费重试机制,都建立在"消息可能会被重复投递"这一前提上(这是绝大多数消息中间件在"至少一次"投递语义下的必然结果),业务消费逻辑必须自行保证幂等(比如基于消息的唯一 ID 做已处理记录去重判断),而不能寄希望于中间件保证"消息只会被消费一次"。