生产者消费者代码示例
上一章节中对消息通信概念做了详细的说明,本章节我们对 RabbitMQ 生产者和消费者代码分别做一示例说明。
1. 生产者代码
#!/usr/bin/env python
# coding=utf-8
# producer
import pika
# 指定远程 rabbitmq 的用户名密码并创建凭证
credentials = pika.PlainCredentials(username="guest", password="guest")
# 1. 创建 connect 连接
connect = pika.BlockingConnection(pika.ConnectionParameters(
host='127.0.0.1', port=5672, virtual_host='/', credentials=credentials))
# 2. 在 connect 上创建一个 channel
channel = connect.channel()
# 3. 在 channel 上声明交换器 exchange
channel.exchange_declare(exchange='hello', exchange_type='direct', passive=False, durable=True,
auto_delete=False)
# 4. 声明一个队列,生产者和消费者都要声明一个相同的队列,用来防止万一某一方挂了,另一方能正常运行
channel.queue_declare(queue='hello')
# 5. 通过键 'world' 将队列和交换器绑定
channel.queue_bind(queue='hello', exchange='hello', routing_key='world')
# 6. 创建纯文本消息
msg_props = pika.BasicProperties()
msg_props.content_type = 'text/plain'
# 7. 将消息发送到 RabbitMQ
message = 'quit'
channel.basic_publish(exchange='hello', routing_key='world', properties=msg_props, body=message)
# 8. 关闭通道
channel.close()
# 9. 当生产者发送完消息后,可选择关闭连接
connect.close()
exchange_declare 方法参数详解:
- exchange : 交换器的名称;
- exchange_type : 交换器的类型,常见的如 fanout、direct 、topic;
- durable:设置是否持久化。
durable 设置为 true 表示持久化, 反之是非持久化。持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息;
- autoDelete : 设置是否自动删除。
autoDelete 设置为true 则表示自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定, 之后所有与这个交换器绑定的队列或者交换器都与此解绑。注意不能错误地把这个参数理解为: “当与此交换器连接的客户端都断开时, RabbitMQ 会自动删除本交换器”
- internal:设置是否是内置的。
如果设置为true,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。
queue_declare 方法参数详解:
-
queue : 队列的名称;
-
durable: 设置是否持久化。为 true 则设置队列为持久化。持久化的队列会存盘,在服务器重启的时候可以保证不丢失相关信息;
-
exclusive : 设置是否排他。为true 则设置队列为排他的。如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点:
- 排他队列是基于连接( Connection) 可见的,同一个连接的不同信道 (Channel) 是可以同时访问同一连接创建的排他队列;
- "首次"是指如果一个连接己经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同:
- 即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除,这种队列适用于一个客户端同时发送和读取消息的应用场景;
-
autoDelete: 设置是否自动删除。
为true 则设置队列为自动删除。自动删除的前提是:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。不能把这个参数错误地理解为:“当连接到此队列的所有客户端断开时,这个队列自动删除”,因为生产者客户端创建这个队列,或者没有消费者客户端与这个队列连接时,都不会自动删除这个队列。
2. 消费者代码
#!/usr/bin/env python
# coding=utf-8
# consumer
import pika
# 指定远程 rabbitmq 的用户名密码并创建凭证
credentials = pika.PlainCredentials(username="guest", password="guest")
# 1. 创建 connect 连接
connect = pika.BlockingConnection(pika.ConnectionParameters(
host='127.0.0.1', port=5672, virtual_host='/', credentials=credentials))
# 2. 在 connect 上创建一个 channel
channel = connect.channel()
# 3. 在 channel 上声明交换器 exchange
channel.exchange_declare(exchange='hello', exchange_type='direct', passive=False, durable=True,
auto_delete=False)
# 4. 声明一个队列,生产者和消费者都要声明一个相同的队列,用来防止万一某一方挂了,另一方能正常运行
channel.queue_declare(queue='hello')
# 5. 通过键 'world' 将队列和交换器绑定
channel.queue_bind(queue='hello', exchange='hello', routing_key='world')
# 6. 定义一个回调函数,用来接收生产者发送的消息
'''
在 Python3 中,bytes 和 str 的互相转换方式是
str.encode('utf-8')
bytes.decode('utf-8')
'''
def callback(channel, method, properties, body):
# 消息确认
channel.basic_ack(delivery_tag=method.delivery_tag)
if body.decode('utf-8') == "quit":
# 停止消费,并退出
channel.basic_cancel(consumer_tag='hello-consumer')
channel.close()
connect.close()
else:
print("msg is {}".format(body))
print("msg type {}".format(type(body)))
print("msg eval after type {}".format(type(eval(body))))
# 7. 订阅消费者
channel.basic_consume(callback, queue='hello', no_ack=False)
# 8. 开始循环取消息
channel.start_consuming()
queue_bind 方法参数详解:
- queue: 队列名称;
- exchange: 交换器的名称;
- routingKey: 用来绑定队列和交换器的路由键
3. 消费消息
RabbitMQ 的消费模式分两种: 推 (Push) 模式和拉 (Pull) 模式。推模式采用 basic_consume 进行消费,而拉模式则是调用 basic_get 进行消费。
basic_get 可以单条地获取消息,获取消息完成后就关闭连接,通常不使用该方法。
basic_consume 参数说明:
- callback : 设置消费者的回调函数。用来处理 RabbitMQ 推送过来的消息;
- queue : 队列的名称;
- autoAck : 设置是否自动确认。建议设成false ,即不自动确认;
- consumerTag: 消费者标签,用来区分多个消费者;
- noLocal : 设置为true 则表示不能将同一个Connectio口中生产者发送的消息传送给这个Connection 中的消费者;
- exclusive : 设置是否排他;
4. 消费端的确认和拒绝
为了保证消息从队列可靠地达到消费者, RabbitMQ 提供了消息确认机制( message acknowledgement) 。
消费者在订阅队列时,可以指定autoAck 参数,当autoAck 等于 false时, RabbitMQ 会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上是先打上删除标记,之后再删除) 。
当 autoAck 等于 true 时, RabbitMQ 会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费到了这些消息。
采用消息确认机制后,只要设置 autoAck 参数为false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题, 因为 RabbitMQ 会一直等待持有消息直到消费者显式调用 Basic.Ack 命令为止。
当 autoAck 参数置为 false ,对于 RabbitMQ 服务端而言,队列中的消息分成了两个部分:
-
等待投递给消费者的消息:
-
己经投递给消费者,但是还没有收到消费者确认信号的消息。
如果 RabbitMQ 一直没有收到消费者的确认信号,并且消费此消息的消费者己经断开连接,则 RabbitMQ 会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来的那个消费者。
RabbitMQ 不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否己经断开,这么设计的原因是 RabbitMQ 允许消费者消费一条消息的时间可以很久很久。
在消费者接收到消息后,如果想明确拒绝当前的消息而不是确认,那么应该怎么做呢?
RabbitMQ 在2.0.0 版本开始引入了Basic.Reject 这个命令,Basic.Reject 命令一次只能拒绝一条消息,如果想要批量拒绝消息,则可以使用 Basic.Nack 这个命令。消费者客户端可以调用channel.basicNack 方法来实现,
5. 关闭连接
在应用程序使用完之后,需要关闭连接,释放资源:
channel.close()
conn.close()
显式地关闭 channel 是个好习惯,但这不是必须的,在 connection 关闭的时候,channel 也会自动关闭。