携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情
一、简单队列
1)介绍
2)消息生产者
import pika
# 连接mq的信息
host = 'xxx' # mq服务所在ip
port = xxx # amqp端口
virtualhost = '/xxx' # 需要连接的环境
"""
创建凭证,如果不创建,则用户名和密码默认是guest
erase_on_connect:连接后是否删除凭证,默认false
"""
credentials = pika.PlainCredentials('xxx', 'xxx', erase_on_connect=False)
# 创建连接
connection = pika.BlockingConnection(pika.ConnectionParameters(host, port, virtualhost, credentials))
# 创建信道
channel = connection.channel()
"""
创建消息队列
queue:队列名称;如果为空,则代理将创建唯一的队列名称
passive=False:只检查队列是否存在,如果不存在则引发 ChannelClosed
durable=False:持久化配置;mq重启后队列还在
exclusive=False:只能有一个消费者监听队列,一般是false,发布订阅是独占
auto_delete=False:当没有消费者或断开连接时,自动删除队列,一般是false
arguments=None:队列的自定义键值对参数
"""
channel.queue_declare(queue='hello')
"""
创建交换机
exchange:交换机名称
routing_key:要绑定的路由key
body:消息体,没有则空字符串
properties=None:消息属性(pika.spec.BasicProperties)
mandatory=False:强制性标志
"""
channel.basic_publish(exchange='', routing_key='hello', body='hello word')
# 关闭连接
connection.close()
3)消息消费者(会一直监听,所监听的队列有消息进入时,则会进行消费)
import pika
def main():
host = 'xxx'
port = xxx
virtualhost = '/xxxx'
credentials = pika.PlainCredentials('xxx', 'xxx', erase_on_connect=False)
# 创建连接
connection = pika.BlockingConnection(pika.ConnectionParameters(host, port, virtualhost, credentials))
# 创建信道
channel = connection.channel()
# 声明消息队列
channel.queue_declare(queue='hello')
# 定义一个回调函数来处理消息队列中的消息,这里用打印到控制台来演示
def callback(ch, method, properties, body):
"""
:param ch:BlockingChannel
:param method:spec.Basic.Deliver
:param properties:spec.BasicProperties
:param body:bytes
:return:
"""
# ch.basic_ack(delivery_tag=method.delivery_tag)
print(body.decode())
"""
接收消息
queue:要使用的队列
on_message_callback:调用函数,用于将消息分派给用户所必需的函数,定义方式:
on_message_callback(channel, method, properties, body)
auto_ack=False :是否自动确认,默认不自动确认消息;设置成 False,在调用callback函数时,未收到确认标识,消息会重回队列。True,无论调用callback成功与否,消息都被消费掉
exclusive=False:排他队列, 是否允许在队列中允许还有其他的consumer消费者
consumer_tag=None:consumer的标志符
arguments=None:消费者的自定义键/值对参数
"""
channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
print('监听/dev环境的hello队列')
# 开始接收信息,并进入阻塞状态,队列里有信息才会调用callback进行处理
channel.start_consuming()
4)执行
- 先启动生产者
- 再启动消费者对队列中的消息进行消费【正常情况应该是先启动消费者再启动生产者的】
二、轮询策略【默认】
1)介绍
- 什么是轮询策略
即不管消费者处理信息的效率,队列给所有消费者轮流发送一条信息,直至消息发送完毕
- 假如:生产者发送了10条信息,那么:
- 消费者A收到的信息:1,4,7,10
- 消费者B收到的信息:2,5,8
- 消费者C收到的信息:3,6,9
- 场景模拟
- 消息生产者的能力大于消费者能力
- 有2个消费节点,其中一个模拟消费能力差的节点。
- 两个节点处于竞争关系,生产者生产的消息会平均分摊给这两个消费节点
- 缺点
- 由于两个消费节点被分配的消息数量是一样的,那样消费能力差的节点所需时间会更长,整体速度则会变慢
- 图示
2)消息生产者
- send.py
import pika
# 连接mq的信息
host = 'xxx' # mq服务所在ip
port = xxx # amqp端口
virtualhost = '/xxx' # 需要连接的环境
credentials = pika.PlainCredentials('xxx', 'xxx')
connection = pika.BlockingConnection(pika.ConnectionParameters(host, port, virtualhost, credentials))
channel = connection.channel()
# durable=True:持久化配置;true时,mq重启后队列还在
channel.queue_declare(queue='work_fair', durable=True)
for i in range(0, 10):
message = f'hello word + {i}'
channel.basic_publish(exchange='', routing_key='work_fair', body=message,
# 2:声明消息在队列中持久化;1:消息非持久化
properties=pika.BasicProperties(delivery_mode=2))
print(f'生产:hello word + {i}')
channel.close()
3)消息消费者
- recv1.py(消费速度慢的消费节点)
import time
import pika
# 连接mq的信息
host = 'xxx' # mq服务所在ip
port = xxx # amqp端口
virtualhost = '/xxx' # 需要连接的环境
credentials = pika.PlainCredentials('xxx', 'xxx')
connection = pika.BlockingConnection(pika.ConnectionParameters(host, port, virtualhost, credentials))
channel = connection.channel()
channel.queue_declare(queue='work_fair', durable=True)
def callback(ch, method, properties, body):
time.sleep(5) # 模拟消费缓慢
print(body.decode())
ch.basic_ack(delivery_tag=method.delivery_tag)
# 当前消费者只处理一个消息,处理完之后生产者再发送新的消息过来
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='work_fair', on_message_callback=callback)
channel.start_consuming()
- reve2.py(消费速度快的消费节点)
import time
import pika
# 连接mq的信息
host = 'xxxx' # mq服务所在ip
port = xxx # amqp端口
virtualhost = '/xxx' # 需要连接的环境
credentials = pika.PlainCredentials('xxx', 'xxx')
connection = pika.BlockingConnection(pika.ConnectionParameters(host, port, virtualhost, credentials))
channel = connection.channel()
# queue_declare=True:消息队列持久化储存,rabbitmq重启后这个队列还在
channel.queue_declare(queue='work_fair', durable=True)
def callback(ch, method, properties, body):
time.sleep(1) # 模拟消费时间
print(body.decode())
ch.basic_ack(delivery_tag=method.delivery_tag)
# 当前消费者只处理一个消息,处理完之后生产者再发送新的消息过来
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='work_fair', on_message_callback=callback)
channel.start_consuming()
4)执行
- 轮训策略验证步骤:
- 先启动所有消费者,再启动生产者
- 轮训策略缺点:
- 存在部分节点消费过快,部分节点消费过慢,从而导致出现不能合理处理消息,消费消息的时间延长的情况
三、公平策略
1)介绍
- 公平策略
- 解决消费者能力不足导致不能合理消费消息,消费时间过长的问题
- 给消费者设定最多消费的上限,比如说设置为1,那么当前消费者只处理1个消息,在处理完这个消息之前,生产者就不会给这个消费者分配消息,去分配给其它有能力的消费者,从而减少总体的消费时间
- 文档
2)消息生产者
- send.py
import pika
# 连接mq的信息
host = 'xxxx' # mq服务所在ip
port = xxx # amqp端口
virtualhost = '/xxx' # 需要连接的环境
credentials = pika.PlainCredentials('xxx', 'xxx')
connection = pika.BlockingConnection(pika.ConnectionParameters(host, port, virtualhost, credentials))
channel = connection.channel()
# durable=True:持久化配置;true时,mq重启后队列还在
channel.queue_declare(queue='work_fair', durable=True)
for i in range(0, 10):
message = f'hello word + {i}'
channel.basic_publish(exchange='', routing_key='work_fair', body=message,
# 2:声明消息在队列中持久化;1:消息非持久化
properties=pika.BasicProperties(delivery_mode=2))
print(f'生产:hello word + {i}')
channel.close()
3)消息消费者
- 缓慢消费者 recv1.py
import time
import pika
# 连接mq的信息
host = 'xxx' # mq服务所在ip
port = xxx # amqp端口
virtualhost = '/xxx' # 需要连接的环境
credentials = pika.PlainCredentials('xxxx', 'xxxx')
connection = pika.BlockingConnection(pika.ConnectionParameters(host, port, virtualhost, credentials))
channel = connection.channel()
channel.queue_declare(queue='work_fair', durable=True)
def callback(ch, method, properties, body):
time.sleep(5) # 模拟消费缓慢
print(body.decode())
ch.basic_ack(delivery_tag=method.delivery_tag)
# 当前消费者只处理一个消息,处理完之后生产者再发送新的消息过来
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='work_fair', on_message_callback=callback)
channel.start_consuming()
- 正常消费者 recv2.py
import time
import pika
# 连接mq的信息
host = 'xxx' # mq服务所在ip
port = xxx # amqp端口
virtualhost = '/xxx' # 需要连接的环境
credentials = pika.PlainCredentials('xxx', 'xxx')
connection = pika.BlockingConnection(pika.ConnectionParameters(host, port, virtualhost, credentials))
channel = connection.channel()
# queue_declare=True:消息队列持久化储存,rabbitmq重启后这个队列还在
channel.queue_declare(queue='work_fair', durable=True)
def callback(ch, method, properties, body):
time.sleep(1) # 模拟消费时间
print(body.decode())
ch.basic_ack(delivery_tag=method.delivery_tag)
# 当前消费者只处理一个消息,处理完之后生产者再发送新的消息过来
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='work_fair', on_message_callback=callback)
channel.start_consuming()
4)执行
- 先执行两个消费者,在执行生产者
- 结果:消费速度快的消费者,会消费多一些消息;慢的则消费少一些(生产者根据“能力”去分发消息)