使用RabbitMQ的手动接收模式:消息第二次入队Failed to declare queue

文章描述了一位开发者在使用RabbitMQ时遇到的问题,具体是手动接收模式下出现`Failedtodeclarequeue`错误。通过检查代码和对比配置,发现`listener.direct.acknowledge-mode`应改为`listener.simple.acknowledge-mode`,修改后问题得到解决。作者还探讨了`Simple`和`Direct`两种模式的区别,并决定暂时使用`Simple`模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:在rabbitMQ测试使用手动接收模式时发生 Failed to declare queue错误

: 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)
: Received a frame on an unknown channel, ignoring it
: Received a frame on an unknown channel, ignoring it
: Received a frame on an unknown channel, ignoring it
: Restarting Consumer@2cd2c8fe: tags=[[amq.ctag-RQecbmbYQjunIZtW-ENloQ]], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@IP:5672/,1), conn: Proxy@1b9ea3e3 Shared Rabbit Connection: SimpleConnection@42257bdd [delegate=amqp://guest@IP:5672/], acknowledgeMode=AUTO local queue size=0
: Failed to declare queue: csnz_queue
: Queue declaration failed; retries left=3

org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[csnz_queue]
at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.java:710) [spring-rabbit-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.passiveDeclarations(BlockingQueueConsumer.java:594) [spring-rabbit-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:581) [spring-rabbit-2.1

现象:

监听器第一次重入队成功,第二次试图重入队发生错误

原因

设置的接收模式有问题

解决步骤:

1、排查问题:是什么导致了队列消息丢失?
2、检查代码
3、更改接收模式配置代码
4、重新测试
5、总结,吸取教训

一、排查问题:是什么导致了队列消息丢失?

最开始报错异常为:Channel shutdown,Failed to declare queue
但是检查了一遍逻辑代码,确认没问题,开始怀疑是不是配置的手动接收模式代码错了

二、检查代码

# 配置RabbitMQ 的基本信息 IP 端口 username pass
spring:
  rabbitmq:
    host: 
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      direct:
        acknowledge-mode: manual

明明没错啊,acknowledge-mode 就是为 manual,还是idea自动提示补全的呢!

看看别人怎么写这块的

listener.simple.acknowledge-mode = manual

不是吧,居然是 simpl.acknowledge-mode

三、更改接收模式配置代码

# 配置RabbitMQ 的基本信息 IP 端口 username pass
spring:
  rabbitmq:
    host: 
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual

四、重新测试

还真tm好了
在这里插入图片描述

五、总结,吸取教训

那么 listener.simple.acknowledge-mode 和 listener.direct.acknowledge-mode 究竟有什么区别呢?

搜罗到的信息:
spring.rabbitmq.listener.direct.acknowledge-mode= # 确认容器的模式
spring.rabbitmq.listener.simple.acknowledge-mode= # 确认容器的模式

目前看来 两个应该都是可以用的,只是它和交换机的类型应该有关联。

而且在debug的过程中,我也发现了导入的《spring-boot-starter-amqp》默认使用的是Simple类型

在这里插入图片描述

要使用 direct的话应该要换掉交换机的配置,目前就先用着simple吧…

RabbitMQ 中,为了避免多个消费者重复消费消息(即确保消息的幂等性),可以通过以下几种方式来实现,而不依赖 Redis 或数据库校验 `messageId`。 ### 方法1:使用 RabbitMQ消息去重插件 RabbitMQ 社区提供了一个名为 `rabbitmq-deduplication` 的插件,可以用来自动去重消息。这个插件会根据消息的 `x-deduplication-header` 或 `message-id` 字段来判断消息是否已经被消费过。 #### 实现步骤: 1. **安装插件**: 下载并启用 `rabbitmq-deduplication` 插件。 ```bash rabbitmq-plugins enable rabbitmq_deduplication ``` 2. **配置队列**: 在声明队列时,启用去重功能。 ```python import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 声明一个启用了去重的队列 channel.queue_declare(queue='dedup_queue', arguments={'x-deduplication': True}) connection.close() ``` 3. **发送带有唯一标识的消息**: 在发送消息时,设置 `message-id` 或其他唯一标识符。 ```python import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() properties = pika.BasicProperties(message_id="unique_message_id_123") channel.basic_publish(exchange='', routing_key='dedup_queue', body='Hello World!', properties=properties) connection.close() ``` 4. **消费消息**: 消费者正常消费消息即可,插件会自动处理去重。 ```python def callback(ch, method, properties, body): print(f"Received {body}") channel.basic_consume(queue='dedup_queue', on_message_callback=callback, auto_ack=True) channel.start_consuming() ``` #### 解释: - `rabbitmq-deduplication` 插件会在消息入队列之前检查其唯一性。 - 如果消息已经存在,则直接丢弃该消息,避免重复消费。 --- ### 方法2:消费者端实现幂等性逻辑 如果不使用插件,可以在消费者端通过业务逻辑实现幂等性。例如,将消息的 `message-id` 存储在内存中(如 Python 的集合 `set`)或本地文件中,并在消费前检查是否已经处理过该消息。 #### 实现代码: ```python import pika # 用于存储已消费的消息ID consumed_message_ids = set() def callback(ch, method, properties, body): message_id = properties.message_id if message_id in consumed_message_ids: print(f"Message {message_id} already processed, skipping...") ch.basic_ack(delivery_tag=method.delivery_tag) return # 处理消息 print(f"Processing message: {body}") consumed_message_ids.add(message_id) # 确认消息已被成功处理 ch.basic_ack(delivery_tag=method.delivery_tag) connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() channel.queue_declare(queue='my_queue') channel.basic_consume(queue='my_queue', on_message_callback=callback) print('Waiting for messages...') channel.start_consuming() ``` #### 解释: - 使用一个集合 `consumed_message_ids` 来记录已经消费过的消息 ID。 - 在每次消费消息之前,先检查该消息 ID 是否已经存在于集合中。 - 如果已经存在,则跳过处理;否则,处理消息并将消息 ID 添加到集合中。 --- ### 方法3:使用 RabbitMQ 的事务机制 RabbitMQ 提供了事务机制,可以通过确认机制(Acknowledgment)来确保消息只被消费一次。 #### 实现代码: ```python import pika def callback(ch, method, properties, body): try: # 模拟消息处理 print(f"Processing message: {body}") # 确认消息已被成功处理 ch.basic_ack(delivery_tag=method.delivery_tag) except Exception as e: print(f"Failed to process message: {e}") ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False) # 不重新入队 connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() channel.queue_declare(queue='my_queue') channel.basic_consume(queue='my_queue', on_message_callback=callback, auto_ack=False) print('Waiting for messages...') channel.start_consuming() ``` #### 解释: - `auto_ack=False` 表示消费者不会自动确认消息。 - 在消息处理完成后,显式调用 `basic_ack` 确认消息已被成功处理。 - 如果处理失败,则调用 `basic_nack` 并设置 `requeue=False`,确保消息不会重新入队。 --- ### 方法4:使用 RabbitMQ 的死信队列(DLX) 如果某些消息可能因为幂等问题需要特殊处理,可以使用死信队列(Dead Letter Exchange)来隔离这些消息。 #### 实现代码: ```python import pika def callback(ch, method, properties, body): try: # 模拟消息处理 print(f"Processing message: {body}") # 确认消息已被成功处理 ch.basic_ack(delivery_tag=method.delivery_tag) except Exception as e: print(f"Failed to process message: {e}") ch.basic_reject(delivery_tag=method.delivery_tag, requeue=False) # 将消息发送到死信队列 connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 声明普通队列和死信队列 channel.queue_declare(queue='normal_queue', arguments={ 'x-dead-letter-exchange': 'dlx_exchange', 'x-dead-letter-routing-key': 'dlx_routing_key' }) channel.queue_declare(queue='dlx_queue') channel.exchange_declare(exchange='dlx_exchange', exchange_type='direct') channel.queue_bind(queue='dlx_queue', exchange='dlx_exchange', routing_key='dlx_routing_key') channel.basic_consume(queue='normal_queue', on_message_callback=callback, auto_ack=False) print('Waiting for messages...') channel.start_consuming() ``` #### 解释: - 如果消息处理失败,可以通过 `basic_reject` 将消息发送到死信队列。 - 死信队列中的消息可以单独处理或分析。 --- ### 总结 以上方法都可以避免多个消费者重复消费消息。具体选择哪种方法取决于你的业务需求和技术栈。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值