3、RabbitMQ工作模式(一):简单队列模式(Hello World)、Work Queues(工作队列)模式、消息确认机制

好,咱们正式开始聊 RabbitMQ 的具体用法。

说实话,很多初学者一上来就被各种交换机、路由键搞懵了。我个人习惯是,先别管那些花里胡哨的,从最简单的模式入手。今天我们就先啃下三块硬骨头:简单队列模式工作队列模式,以及那个救命的消息确认机制

3.1 简单队列模式(Hello World)

这个模式,说白了就是「一个生产者,一个消费者」。消息从生产者发出,经过一个队列,被消费者拿走。就像你给一个人发微信,他收到并看了。

我在项目中第一次用 RabbitMQ 就是这种模式。当时要做一个日志收集的 demo,简单到不行,但跑通的那一刻还是挺爽的。

3.1.1 核心流程

  1. 生产者连接到 RabbitMQ 服务器
  2. 生产者声明一个队列
  3. 生产者发送消息到该队列
  4. 消费者连接到同一个队列
  5. 消费者接收并处理消息

3.1.2 代码示例

先看生产者(send.py):

import pika

# 建立连接
connection = pika.BlockingConnection(
    pika.ConnectionParameters('localhost')
)
channel = connection.channel()

# 声明队列。如果队列不存在,就创建它
channel.queue_declare(queue='hello')

# 发送消息
channel.basic_publish(
    exchange='',
    routing_key='hello',
    body='Hello World!'
)

print(" [x] 发送了 'Hello World!'")

# 关闭连接
connection.close()

再看消费者(receive.py):

import pika

connection = pika.BlockingConnection(
    pika.ConnectionParameters('localhost')
)
channel = connection.channel()

# 同样声明队列。防止消费者先启动时队列不存在
channel.queue_declare(queue='hello')

# 定义回调函数,处理收到的消息
def callback(ch, method, properties, body):
    print(f" [x] 收到消息: {body}")

# 告诉 RabbitMQ,这个回调函数用来处理队列中的消息
channel.basic_consume(
    queue='hello',
    on_message_callback=callback,
    auto_ack=True
)

print(' [*] 等待消息。按 Ctrl+C 退出')
channel.start_consuming()
💡 小提示: 为什么消费者也要 queue_declare?因为如果消费者先启动,而队列还没创建,就会报错。两边都声明一下,保证队列一定存在。这是一种防御性编程习惯。

3.2 Work Queues(工作队列)模式

简单队列只能一对一。但实际项目中,消息量一大,一个消费者根本忙不过来。这时候就需要工作队列模式了。

工作队列,也叫任务队列。它的核心思想是:一个生产者,多个消费者,共同消费同一个队列中的消息。每条消息只会被一个消费者消费。

你想想看,这就像你有一个任务列表,几个同事各自认领任务。谁有空谁干,干完一个再领下一个。

3.2.1 轮询分发(Round-robin dispatching)

默认情况下,RabbitMQ 会按顺序把消息轮流发给每个消费者。第一个消息给消费者A,第二个给消费者B,第三个又给A,以此类推。这叫轮询分发

嗯,这里要注意:轮询分发不考虑消费者的处理能力。如果A处理慢,B处理快,A那边会积压,B却闲着。这显然不合理。

3.2.2 公平分发(Fair dispatch)

为了解决上面的问题,RabbitMQ 提供了公平分发机制。它通过 basic_qos(prefetch_count=1) 来实现。

这个设置的意思是:每次只给消费者发一条消息,等消费者处理完并确认后,再发下一条。这样,处理快的消费者就能多干活,处理慢的少干活。

🔑 关键点: 公平分发必须配合手动确认使用。如果用了自动确认,prefetch_count 就失效了。

3.2.3 代码示例:工作队列的生产者

import pika
import sys

connection = pika.BlockingConnection(
    pika.ConnectionParameters('localhost')
)
channel = connection.channel()

channel.queue_declare(queue='task_queue', durable=True)

message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(
    exchange='',
    routing_key='task_queue',
    body=message,
    properties=pika.BasicProperties(
        delivery_mode=2,  # 消息持久化
    )
)
print(f" [x] 发送了 {message}")
connection.close()

3.2.4 代码示例:工作队列的消费者(带公平分发)

import pika
import time

connection = pika.BlockingConnection(
    pika.ConnectionParameters('localhost')
)
channel = connection.channel()

channel.queue_declare(queue='task_queue', durable=True)

# 关键:每次只取一条消息
channel.basic_qos(prefetch_count=1)

def callback(ch, method, properties, body):
    print(f" [x] 收到 {body.decode()}")
    # 模拟耗时任务
    time.sleep(body.count(b'.'))
    print(" [x] 处理完成")
    # 手动发送确认
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_consume(
    queue='task_queue',
    on_message_callback=callback
)

print(' [*] 等待消息。按 Ctrl+C 退出')
channel.start_consuming()

3.3 消息确认机制

这是 RabbitMQ 里最容易被忽视,但也是最关键的部分。没有它,你的系统随时可能丢消息。

我曾经在一个日志处理系统里踩过这个坑。消费者处理到一半崩溃了,消息没了,日志丢了。排查了半天才发现是自动确认惹的祸。

3.3.1 自动确认 vs 手动确认

确认方式 行为 风险
自动确认(auto_ack=True) RabbitMQ 一发送消息,就认为消费成功 消费者崩溃,消息丢失
手动确认(auto_ack=False) 消费者处理完,主动调用 basic_ack 通知 RabbitMQ 不会丢消息,但需要正确处理

3.3.2 手动确认的正确姿势

  1. 消费者设置 auto_ack=False
  2. 处理完业务逻辑后,调用 ch.basic_ack(delivery_tag=method.delivery_tag)
  3. 如果处理失败,可以调用 basic_nackbasic_reject 让消息重新入队
⚠️ 避坑指南: 我曾经犯过一个错误——在回调函数里忘记调用 basic_ack。结果消息一直处于「未确认」状态,RabbitMQ 认为消费者还活着,就不重新投递。最终导致消息积压,消费者内存爆掉。所以,一定要确保 ack 被调用,最好放在 try/finally 里。

3.3.3 消息拒绝与重新入队

如果消费者发现消息有问题,比如格式错误,可以拒绝它:

# 拒绝单条消息,并重新入队
ch.basic_reject(delivery_tag=method.delivery_tag, requeue=True)

# 或者批量拒绝
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True, multiple=False)

注意:requeue=True 会让消息重新回到队列头部,可能被同一个消费者再次消费,导致死循环。我建议在拒绝时记录日志,并设置最大重试次数。

3.3.4 消息持久化

光有确认还不够。如果 RabbitMQ 服务器宕机了,内存里的消息全没了。怎么办?持久化。

持久化需要做两件事:

  • 队列持久化:声明队列时设置 durable=True
  • 消息持久化:发送消息时设置 delivery_mode=2

但要注意,持久化会影响性能。如果对消息可靠性要求极高,可以持久化;如果允许少量丢失,用内存队列更快。

💡 我的经验: 在金融、订单这类场景,我坚持用持久化+手动确认。在日志、监控这类场景,用自动确认+非持久化,追求吞吐量。没有银弹,看业务需求。

3.4 小结

今天咱们聊了三个核心概念:

  • 简单队列模式:一对一,入门必备
  • 工作队列模式:一对多,轮询或公平分发
  • 消息确认机制:手动确认+持久化,保证消息不丢

说实话,这些基础模式虽然简单,但它们是后面所有高级模式(发布订阅、路由、主题)的基石。把今天的内容吃透了,后面的路就好走了。

下一章,咱们聊聊 RabbitMQ 的交换机,那才是真正灵活的地方。