RabbitMQ消费消息后报未知delivery tag错误,通道持续创建求助
错误现象
每条消息消费后触发通道关闭错误,通道数量持续增长,消息消费处理正常但资源消耗异常:
Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag 1, class-id=60, method-id=80)
o.s.a.r.l.SimpleMessageListenerContainer - : Restarting Consumer@3d5a462b: tags=[{amq.ctag-zGA1v36aEmXxnG7bqBUcdg=asstMgmt.queue}], channel=Cached Rabbit Channel: AMQChannel(amqp://mqadmin@xx:xx:xx:xx:5672/assetmgmt,1165), conn: Proxy@5c87dd9e Shared Rabbit Connection: SimpleConnection@2ec6c89 [delegate=amqp://mqadmin@@xx:xx:xx:xx:5672/assetmgmt, localPort= port], acknowledgeMode=AUTO local queue size=0
核心问题原因
手动ACK与容器自动ACK冲突
从日志可见容器使用的是acknowledgeMode=AUTO模式,容器会自动处理消息确认,但你的代码中手动调用了basicAck/basicReject。这会导致RabbitMQ收到重复的确认请求:容器已经将delivery tag标记为已处理,手动再次操作时tag失效,触发unknown delivery tag错误。通道因错误被关闭后,容器只能创建新通道维持消费,最终导致通道数量暴增。消费代码存在重复确认逻辑
在msgType为空的分支中,执行channel.basicReject后没有终止方法,后续代码依然会执行channel.basicAck,进一步加剧重复确认问题。容器工厂冗余配置
在SimpleRabbitListenerContainerFactory中创建的RabbitTemplate未被容器使用,属于无效配置,浪费资源。
解决方案
方案一:切换为手动确认模式(推荐)
修改容器工厂配置,启用手动确认模式,让代码完全控制消息确认逻辑:
@Bean public SimpleRabbitListenerContainerFactory assetContainerFactory( @Qualifier("connectionFactory") ConnectionFactory connectionFactory ) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setConcurrentConsumers(3); factory.setMaxConcurrentConsumers(6); factory.setDefaultRequeueRejected(false); // 启用手动确认模式 factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 移除无用的RabbitTemplate配置 return factory; }
同时修复消费代码的重复确认问题,在执行拒绝操作后终止方法:
@RabbitListener(admin = "amqpAssetMgmtAdmin" ,queues = "${asstMgmt.queue.name}", containerFactory = "assetContainerFactory") public void loadAssetMgmtData(Message msg,Channel channel) throws IOException { long tag =0; try { UDDAO dao = new UDDAO(); dao.setAssetMgmtRabbitTemplate(assetMgmtRabbitTemplate); Map<String, Object> headers = msg.getMessageProperties().getHeaders(); String msgType = (String) headers.get("type"); String payload = new String(msg.getBody()); tag = msg.getMessageProperties().getDeliveryTag(); if(msgType == null || msgType.isEmpty()){ log.error("Got message with empty message type, rejecting the message"); channel.basicReject(tag, false); // 添加return避免后续执行basicAck return; } //Process the message channel.basicAck(tag, false); } catch (JDBCConnectionException e) { log.error("DB exception so requeueing the message"); log.error(e.getMessage()); if(tag>0) channel.basicReject(tag, true); }catch (Exception e) { if(msg.getMessageProperties().getRedelivered()){ log.error("Internal server error occurred. rejecting without requeueing ", e); if(tag>0) channel.basicReject(tag, false); }else{ if(tag>0) channel.basicReject(tag, true); log.error("Internal server error occurred. rejecting and requeuing ", e); } } }
方案二:保留自动确认,移除手动确认代码
如果不需要精细控制确认逻辑,直接删除代码中所有channel.basicAck和channel.basicReject调用,让容器自动处理确认。可以通过factory.setDefaultRequeueRejected(false)配置全局是否重入队,或在@RabbitListener中通过requeueRejected属性单独配置。
额外优化
删除两个SimpleRabbitListenerContainerFactory中冗余的RabbitTemplate创建代码,减少不必要的资源消耗。
内容的提问来源于stack exchange,提问作者mrtCherry

