RabbitMQ队列

113 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

一、简单队列

1)介绍

image.png

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)执行

  • 先启动生产者

image.png image.png

  • 再启动消费者对队列中的消息进行消费【正常情况应该是先启动消费者再启动生产者的】

image.pngimage.png image.png

二、轮询策略【默认】

1)介绍

  • 什么是轮询策略

即不管消费者处理信息的效率,队列给所有消费者轮流发送一条信息,直至消息发送完毕

  • 假如:生产者发送了10条信息,那么:
    • 消费者A收到的信息:1,4,7,10
    • 消费者B收到的信息:2,5,8
    • 消费者C收到的信息:3,6,9
  • 场景模拟
    • 消息生产者的能力大于消费者能力
    • 有2个消费节点,其中一个模拟消费能力差的节点。
    • 两个节点处于竞争关系,生产者生产的消息会平均分摊给这两个消费节点
  • 缺点
    • 由于两个消费节点被分配的消息数量是一样的,那样消费能力差的节点所需时间会更长,整体速度则会变慢
  • 图示

image.png

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)执行

  • 先执行两个消费者,在执行生产者
  • 结果:消费速度快的消费者,会消费多一些消息;慢的则消费少一些(生产者根据“能力”去分发消息)