Python RabbitMQ

前言

什么是消息队列?#

MQ 全称为 Message Queue 消息队列 (MQ)是一种应用程序的通信方法, MQ是生产-消费者模型的一个经典代表,一端往消息队列中不断写入消息,另一端不断可以读取队列中的消息。

消息队列出现的原因?#

消息队列主要是解决应用中解耦 异步消息 流量削锋等问题, 实现高性能 高可用 可伸缩的最终一致性架构。

RabbitMQ 使用

RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。目前使用最多的消息队列。

RabbitMQ 安装#

# centos 7 中安装
yum install rabbitmq-server
# 启动
systemctl enable rabbitmq-server.service
systemctl start rabbitmq-server.service

RabbitMQ 工作模式#

简单模式#

生产者#

"""生产者
    生产者流程:
        1. 链接 rabbitmq
        2. 创建队列
        3. 向指定的队列插入数据
"""

import pika

# 创建凭证
# credentials = pika.PlainCredentials("admin","123456")

# 连接 rabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('172.27.135.11'))
channel = connection.channel()

# 创建一个消息队列
channel.queue_declare(queue='hello')

# 向指定队列插入数据
channel.basic_publish(exchange='',  # 简单模式
                      routing_key='hello',  # 指定队列
                      body=b"Hello World!")

print(" [x] Sent 'Hello World!'")

消费者#

import pika

# 创建一个连接
connection = pika.BlockingConnection(pika.ConnectionParameters('172.27.135.11'))
channel = connection.channel()

# 创建一个消息队列
channel.queue_declare(queue='hello')

# 确定回调参数
def callback(ch, method, properties, body):
    """
    前三个参数先不用考虑
    :param body:
    :return:
    """
    print(" [x] Received %r" % body)

# 确定监听队列
channel.basic_consume(queue='hello',
                      auto_ack=True,
                      on_message_callback=callback)

print(' [*] Waiting for messages. To exit press CTRL+C')
# 开始消费 到此才真正执行
channel.start_consuming()

参数#

应答参数#

应答参数分为 自动应答手动应答

出现这两种模式的原因是:如果消费者取出消息队列中的数据后,消费者内部程序出现错误,

修复消费者的 bug 后想要再次取出消息队列中的数据,但此时的消息队列中的数据已经不存在了。

所以手动应答 需要消费者 callback后,消息队列才会将消费者取出的队列删除。

消费者示例
def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)
    
channel.basic_consume(queue='hello1',
                      # 默认应答:会造成数据丢失问题:auto_ack=True;
                      # 默认应答需要改为手动应答: auto_ack=False,
                      # 手动应答,意思为 MQ 队列中的数据,需要 消费者回调删除
                      # 手动应答适用于 安全
                      # 默认应答适用于 性能
                      auto_ack=False,
                      on_message_callback=callback)

print(' [*] Waiting for messages. To exit press CTRL+C')
# 开始消费 到此才真正执行
channel.start_consuming()

持久化参数#

持久化参数:让消息队列中的数据存储在硬盘上 (队列持久化)

目的: 解决 rabbitMQ 的服务崩溃从而数据丢失问题

生产者
# 创建一个消息队列
channel.queue_declare(queue='hello5', durable=True)  
# durable 用于持久化队列 不能持久化队列中的消息

# 向指定队列插入数据
channel.basic_publish(exchange='',  # 简单模式
                      routing_key='hello5',  # 指定队列
                      body="Hello World!",
                      properties=pika.BasicProperties(
                          delivery_mode=2  # make message persistent
                        )
                      )
# properties 指的是消费者端 callback 函数中的 properties 参数;
# delivery_mode=2  # make message persistent 持久化消息
消费者
channel.queue_declare(queue='hello5', durable=True)
# 确定监听队列
channel.basic_consume(queue='hello5',
                      auto_ack=False,
                      on_message_callback=callback)

分发参数#

轮询分发 一个生产者产生的数据 轮询分发给消费者。

# 消费者中增加一句 公平分发:谁快谁先多得到数据
channel.basic_qos(prefetch_count=1)

例如:生产者分别产生 111, 222, 333 。

消费者 1 接收到 111;

消费者 2 接收到 222;

消费者 3 接收到 333;

交换机模式#

发布订阅模式#

模式说明:生产者创建交换机 消费者创建唯一消息队列并将自己的消息队列绑定在交换机中,当生产者发布信息时,所有绑定交换机的消息队列都会收到一份数据。

img

生产者
""" 生产者
    1. 连接rabbitMQ
    2. 确认交换机
    3. 发布信息
    4. 关闭
"""

import pika

# 连接rabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='172.27.135.11'))
channel = connection.channel()

# 交换机模式
channel.exchange_declare(
    exchange='logs',  # 交换机的名称
    exchange_type='fanout'  # 交换机的模式 这里为发布订阅模式
)

message = "info: Hello World"
channel.basic_publish(
    exchange='logs',
    routing_key='',
    body=message
)

connection.close()
消费者
"""消费者模式
    1. 连接rabbitMQ
    2. 确认交换机 没有则创建
    3. 确认唯一消息队列
    4. 绑定交换机
"""

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='172.27.135.11'))
channel = connection.channel()


# 确认交换机
# 消费者也要确认交换机 目的是为了防止生产者后产生交换机而无法绑定
channel.exchange_declare(
    exchange='logs',  # 交换机名称
    exchange_type='fanout'  # 交换机模式类型
)

# 确认消息队列; exclusive=True 是为了随机产生不重复的消息队列名称
result = channel.queue_declare("", exclusive=True)
# 获取队列名称
queue_name = result.method.queue
# 消息队列绑定交换机
channel.queue_bind(exchange='logs',
                   queue=queue_name)

def callback(ch, method, properties, body):
    """
    body 是就收的数据
    """
    print(" [x] %r" % body)

# 确认消费者模式
channel.basic_consume(queue=queue_name,
                      auto_ack=True,
                      on_message_callback=callback)
# 真正开始消费
channel.start_consuming()

关键字模式#

发布-订阅模式:会出现一个问题:当某个消费者不需要一些数据时,可是交换机还是会给消费者发送。

关键字模式:为了这一问题,出现关键字模式, 我们需要对消费者和生产者定义关键字,只有关键字相互匹配,消费者才接收当前数据。

img

  • 解释图中信息
    • P:生产者
    • C1,C2: 消费者
    • X:交换机, type=direct 关键字模式
    • error info warning: 声明的关键字
    • amqp.gen-S...: 队列名
生产者
import pika

# 连接rabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='172.27.135.11'))
channel = connection.channel()


# 交换机模式
channel.exchange_declare(
    exchange='logs2',  # 交换机的名称
    exchange_type='direct'  # 交换机的模式 这里为关键字模式
)

message = "info: Hello World"
channel.basic_publish(
    exchange='logs2',
    routing_key='info',
    body=message,
)
print(" [x] Sent %r" % message)
connection.close()
消费者
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='172.27.135.11'))
channel = connection.channel()

# 确认交换机
# 消费者也要确认交换机 目的是为了防止生产者后产生交换机而无法绑定
channel.exchange_declare(
    exchange='logs2',  # 交换机名称
    exchange_type='direct'  # 交换机模式类型 关键字模式
)

# 确认消息队列; exclusive=True 是为了随机产生不重复的消息队列名称
result = channel.queue_declare("", exclusive=True)
# 获取队列名称
queue_name = result.method.queue
# 给消息队列绑定交换机 并声明接收关键字为info的数据
channel.queue_bind(exchange='logs2',
                   queue=queue_name,
                   routing_key="info")
                   
# 给消息队列绑定交换机 并声明接收关键字为error的数据
channel.queue_bind(exchange='logs2',
                   queue=queue_name,
                   routing_key="error")


def callback(ch, method, properties, body):
    print(" [x] %r" % body)

# 确认消费者模式
channel.basic_consume(queue=queue_name,
                      auto_ack=True,
                      on_message_callback=callback)
# 真正开始消费
channel.start_consuming()

通配符模式#

通配符交换机与之前的路由模式相比,它将信息的传输类型的key更加细化,以“key1.key2.keyN....”的模式来指定信息传输的key的大类型和大类型下面的小类型,让消费者可以更加精细的确认自己想要获取的信息类型。而在消费者一段,不用精确的指定具体到哪一个大类型下的小类型的key,而是可以使用类似正则表达式(但与正则表达式规则完全不同)的通配符在指定一定范围或符合某一个字符串匹配规则的key,来获取想要的信息。

通配符交换机(Topic Exchange)将路由键和某模式进行匹配。此时队列需要绑定在一个模式上。符号“#”匹配一个或多个词,符号“”仅匹配一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.”只会匹配到“audit.irs”。(这里与我们一般的正则表达式的“*”和“#”刚好相反,这里我们需要注意一下。)

下面是一个解释通配符模式交换机工作的一个样例

img

举例

# 生产者的 routing_key 
routing_key = "china.news"
routing_key = "china.foods"
routing_key = "usa.news"
routing_key = "usa.foods"

# 消费者想要获取所有的 news
routing_key = "#.news"

# 消费者想要获取关于 china 的所有信息
routing_key = "china.#"

# 消费者想要获取关于 usa 的信息
routing_key = "usa.#"
生产者
import pika

# 连接rabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='172.27.135.11'))
channel = connection.channel()

# 交换机模式
channel.exchange_declare(
    exchange='logs3',  # 交换机的名称
    exchange_type='topic'  # 交换机的模式 这里为通配符模式
)

message = "info: this is a china time"
channel.basic_publish(
    exchange='logs3',
    routing_key='china.time',
    body=message,
)
print(" [x] Sent %s" % message)
connection.close()
消费者
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='172.27.135.11'))
channel = connection.channel()

# 确认交换机
# 消费者也要确认交换机 目的是为了防止生产者后产生交换机而无法绑定
channel.exchange_declare(
    exchange='logs3',  # 交换机名称
    exchange_type='topic'  # 交换机模式类型
)

# 确认消息队列; exclusive=True 是为了随机产生不重复的消息队列名称
result = channel.queue_declare("", exclusive=True)
# 获取队列名称
queue_name = result.method.queue
# 消息队列绑定交换机
channel.queue_bind(exchange='logs3',
                   queue=queue_name,
                   routing_key="china.#")

def callback(ch, method, properties, body):
    print(" [x] %r" % body)

# 确认消费者模式
channel.basic_consume(queue=queue_name,
                      auto_ack=True,
                      on_message_callback=callback)
# 真正开始消费
channel.start_consuming()

相关面试题

  1. RabbitMQ 如何在消费者获取任务后未处理完前就挂掉,保证数据不丢失?
为了预防消息丢失,rabbitmq提供了auto_ack:应答参数;
即工作进程在收到消息并处理后,callback:发送ack给rabbitmq,告知rabbitmq这时候可以把该消息从队列中删除了。
如果工作进程挂掉了,rabbitmq没有收到ack,那么会把该消息,重新分发给其他工作进程。
不需要设置timeout,即使该任务需要很长时间也可以处理。

auto_ack = Fales 是设置为手应答;
  1. RabbitMQ 如何对消息做持久化?
# 创建队列时声明队列为持久化队列
# 向队列中插入消息时,声明消息为持久化消息

# 创建一个消息队列
channel.queue_declare(queue='hello5', durable=True)  

# 向指定队列插入数据
channel.basic_publish(exchange='',  # 简单模式
                      routing_key='',  # 指定关键字
                      body="Hello World!",
                      properties=pika.BasicProperties(
                          delivery_mode=2  # make message persistent
                        )
                      )
  1. RabbitMQ 如何控制消息被消费顺序?
# 可以将消息队列拆分为多个 queue, 保证一个 queue 中的消息只被一个消费者所消费。
  1. 以下 RabbitMQ 的 exchange type 分别代表什么意思? 如 fanout, direct, topic ?
fanout: 发布订阅模式;
    此模式由生产者创建交换机,每个消费者分别创建自己唯一的消息队列,(消息队列)并绑定交换机。
    当生产者产生一条数据,所有的绑定在此交换机的队列都会收到一份消息。
direct: 关键字模式;
    此模式由生产者这创建交换机,每个消费者分别创建自己的消息队列,声明 routing_key(消息队列)并绑定交换机。
    当生产者产生一条数据,向指定关键字(routing_key)中消息队列中插入数据
topic:  通配符模式;
    此模式和关键字模式类似,routing_key 从完全匹配改为模糊匹配,比关键字模式更加灵活。
posted @   隔江千万里  阅读(67)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
主题色彩