文章目录
消息持久化存储
消息队列服务器停止会导致消息丢失, 为避免此类情况, 可使用消息持久化存储.
由生产者和消费者共同在代码中声明:
channel.queue_declare(queue='hello', durable=True)
消息在发布时还需标记为持久化:
channel.basic_publish(exchange='',
routing_key="task_queue",
body=message,
properties=pika.BasicProperties(
delivery_mode = 2, # 2 for "persistent", 1 for "transient"
))
消息的properties可参见:
添加链接描述
AMQP 0-9-1协议预先定义了一组与消息相关的14个属性。除下列情况外,大部分很少使用:
- delivery_mode:将消息标记为持久化(值为2)或瞬态(任何其他值)。您可能还记得第二个教程中的这个属性。
- content_type:用于描述编码的mime类型。例如,对于经常使用的JSON编码,最好将此属性设置为:application/ JSON。
- reply_to:通常用于命名回调队列。
- correlation_id:用于将RPC响应与请求关联起来
消息确认的重要性
消费者如不发送ACK确认, 消息会一直在队列之中. 当消费者退出时, 消息会被重新传递. 同时RabbitMQ会因为发布消息的增加而逐渐耗尽资源.
ACK确认由消费者注册时指定, 有2种方式:
- 自动确认(不推荐)
channel.basic_consume("hello", callback, auto_ack=True)
此种方式下, 消费者中途退出会导致消息丢失, 不会重传. 因而不建议采用此种方式
- 非自动确认(推荐)
def callback(ch, method, properties, body):
print(f"[x] Received {body}")
for _ in range(body.count(b'.')):
time.sleep(1)
print(" [x] Done")
ch.basic_ack(delivery_tag=method.delivery_tag) # 发送ACK确认
channel.basic_consume("hello", callback, auto_ack=False) # 非自动确认
消费者注册时auto_ack为False, 同时回调函数中调用ch.basic_ack()发送确认.
此种方式消费者中途退出时, 所有未确认消息都会发送给其他消费者处理.
消息分配
默认情况下服务器会一次将所有消息平均发送个各个消费者, 而不确认消息负荷是否平均.
消费者可要求服务器一次只发送一条消息, 这样可达到负荷均衡:
def callback(ch, method, properties, body):
print(f"[x] Received {body}")
for _ in range(body.count(b'.')):
time.sleep(1)
print(" [x] Done")
ch.basic_ack(delivery_tag=method.delivery_tag) # 发送ACK确认
channel.basic_qos(prefetch_count=1) # 一次只向消费者发送一条消息
channel.basic_consume("hello", callback, auto_ack=False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
交换器Exchange–fanout方式
实际上生产者是向交换器Exchange发送消息, 由exchange将消息推送到绑定的队列中. Exchange的类型有:direct, topic, headers 和 fanout, 其中fanout是广播方式.
广播方式的示例代码:
生产者:
#!/usr/bin/env python
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
# 声明exchange(exchange与队列的绑定由消费者定义)
channel.exchange_declare(exchange='logs',
exchange_type='fanout')
message = ' '.join(sys.argv[1:]) or "info: Hello World!"
channel.basic_publish(exchange='logs',
routing_key='',
body=message)
print(" [x] Sent %r" % message)
connection.close()
消费者:
#!/usr/bin/env python
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
# 声明exchange
channel.exchange_declare(exchange='logs',
exchange_type='fanout')
# 声明一个随机队列
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue # 获得队列名
# 绑定exchange与队列
channel.queue_bind(exchange='logs',
queue=queue_name)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r" % body)
channel.basic_consume(callback,
queue=queue_name,
no_ack=True)
channel.start_consuming()
广播型exchange:
- 声明exchange(生产者与消费者)
- 声明队列(消费者)
- 绑定exchange与队列(消费者)
交换器–direct方式和路由键routing_key
直接交换背后的路由算法很简单——消息转到队列,其绑定键与消息的路由键完全匹配(一个交换器可绑定多个路由键). 它们的绑定由消费者完成:
channel.queue_bind(exchange=exchange_name,
queue=queue_name,
routing_key='black')
交换器–topic方式
可基于多个标准进行路由.
主题为由点分隔的单词列表, 由参数routing_key指定, 有以下规则:
- *(星号)只能代替一个单词。
- #(哈希)可以替代0个或多个单词。
使用RPC服务器
使用RPC服务器进行耗时计算, 由生产者在properties属性中指定回调队列(reply_to参数)和请求id(correlation_id参数)
当客户端启动时,它会创建一个匿名排他回调队列。
对于RPC请求,客户端发送具有两个属性的消息:reply_to(设置为回调队列)和correlation_id(设置为每个请求的唯一值)。
请求被发送到rpc_queue队列。
RPC工作人员(又名:服务器)正在等待该队列上的请求。当出现请求时,它会执行任务并使用reply_to字段中的队列将结果发送回客户机。
客户机等待回调队列上的数据。当消息出现时,它检查correlation_id属性。如果与请求中的值匹配,则返回对应用程序的响应。
详见:添加链接描述