Python- rabbitMQ消息队列的使用
消息(Message)是指在应用间传输的数据
但是,如果你没有时间看英文文档,或者想看到一些总结性的内容,还是可以继续读下去的。
首先,来看一下整体的架构图,并介绍一些基本概念:
-
channel: 信道是生产者,消费者和 RabbitMQ 通信的渠道,是建立在 TCP 连接上的虚拟连接。一个 TCP 连接上可以建立成百上千个信道,通过这种方式,可以减少系统开销,提高性能。
-
Broker: 接收客户端连接,实现 AMQP 协议的消息队列和路由功能的进程。
-
Virtual Host: 虚拟主机的概念,类似权限控制组,一个 Virtual Host 里可以有多个 Exchange 和 Queue,权限控制的最小粒度是 Virtual Host。
-
Exchange: 交换机,接收生产者发送的消息,并根据 Routing Key 将消息路由到服务器中的队列 Queue。
-
ExchangeType: 交换机类型决定了路由消息的行为,RabbitMQ 中有三种 Exchange 类型,分别是 direct、fanout、topic。
-
Message Queue: 消息队列,用于存储还未被消费者消费的消息,由 Header 和 body 组成。Header 是由生产者添加的各种属性的集合,包括 Message 是否被持久化、优先级是多少、由哪个 Message Queue 接收等,body 是真正需要发送的数据内容。
-
BindingKey: 绑定关键字,将一个特定的 Exchange 和一个特定的 Queue 绑定起来。
安装准备工具
1.下载Eralng,底下连接已提供otp_win64_20.2.exe链接: .提取码:3gpa
2.下载rabbitmq,底下链接已提供rabbitmq-server-3.7.4.exe链接: .提取码:vzar
安装步骤(图文)
1.第一步:安装otp_win64_20.2.exe右键以管理员身份运行
接着一直点击下一步傻瓜式安装2.第二步:安装rabbitmq-server-3.7.4.exe双击文件rabbitmq-server-3.7.4.exe,傻瓜式安装,(注意不要安装在包含中文和空格的目录下!安装后window服务中就存在rabbitMQ了,并且是启动状态。 )接着安装管理界面(插件)
-
进入rabbitMQ安装目录的sbin目录
-
点击上方的路径框输入cmd,按下回车键
-
输入命令点击回车
-
rabbitmq-plugins enable rabbitmq_management
第三步:1.重启服务,双击rabbitmq-server.bat(双击后可能需要等待一会)
2.打开浏览器,地址栏输入http://127.0.0.1:15672 ,即可看到管理界面的登陆页
输入用户名和密码,都为guest 进入主界面:
最上侧的导航以此是:概览、连接、信道、交换器、队列、用户管理
安装链接python的驱动
pip install pika
消费者生产者:
基本使用
说明:一对多模式,一个生产者,多个消费者,一个队列,每个消费者从队列中获取唯一的消息。
简单模式
生产者:
import pika # 1.连接rabbitmq服务器 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) # 2.创建队列 channel = connection.channel() # 3.声明一个名为'hello'的队列 channel.queue_declare(queue='hello', durable=True) # 4.如果exchange为空,则是简单模式:向hello队列中插入字符串hello world! channel.basic_publish(exchange='', routing_key='hello', body='hello world!', # 消息持久化 properties=pika.BasicProperties( delivery_mode=2, )) print('send hello world!') # 5.关闭连接 channel.close()
消费者:
import pika # 1.连接rabbitmq服务器 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) # 2.创建队列 channel = connection.channel() # 3.声明一个名为'hello'的队列 谁先发的请求谁就先创建队列,队列不会重复创建 channel.queue_declare(queue='hello', durable=True) # 4.构建回调函数 def callback(ch, method, properties, body): print(" [x] Received %r" % body) # 发送应答信号,表示数据已经处理完毕,可以删除 ch.basic_ack(delivery_tag=method.delivery_tag) # 5.确认监听队列queue:hello,一旦有值出现,则出发回调函数,callback # 在订阅消息的时候可以指定应答模式,当auto_ack等于true的时候,表示当消费者一收到消息就表示消费者收到了消息, # 消费者收到了消息就会立即从队列中删除。 channel.basic_consume(queue='hello', auto_ack=False, on_message_callback=callback) # 6. 持续监听 print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming()
描述:将交换器、队列和消息都设置持久化之后就能保证数据不会被丢失吗?当然不是,多个方面:
-
消费者端: 消费者订阅队列将autoAck设置为true,虽然消费者接收到了消息,但是没有来得及处理就宕机了,那数据也会丢失,解决方案就是以为手动确认接收消息,待处理完消息之后,手动删除消息
解决方案:应答模式
-
自动ACK:消息一旦被接收,消费者自动发送ACK
-
手动ACK:消息接收后,不会发送ACK,需要手动调用
-
- 如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便
- 如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。
# 发送应答信号,表示数据已经处理完毕,可以删除 # 构建回调函数 def callback(ch, method, properties, body): print(" [x] Received %r" % body) # 发送应答信号,表示数据已经处理完毕,可以删除 ch.basic_ack(delivery_tag=method.delivery_tag) # 在订阅消息的时候可以指定应答模式,当auto_ack等于True的时候,表示自动,False表示手动, channel.basic_consume(queue='hello', auto_ack=False, on_message_callback=callback)
-
-
在rabbitmq服务端,如果消息正确被发送,但是rabbitmq未来得及持久化,没有将数据写入磁盘,服务异常而导致数据丢失,解决方案,可以通过rabbitmq集群的方式实现消息中间件的高可用性
持久化
# 持久化是为提高rabbitmq消息的可靠性,防止在异常情况(重启,关闭,宕机)下数据的丢失 # durable:设置是否执行持久化。如果设置为true,即durable=true,持久化实现的重要参数 channel.queue_declare(queue='hello', durable=True) # 消息持久化delivery_mode=2 2是存在在硬盘 1是默认存储在内存 properties=pika.BasicProperties( delivery_mode=2, )
发布/订阅模式
说明:生产者将消息发送给 broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。消费者监听自己的队列并进行消费。
发布订阅模式图解:
代码:
发布者:
import pika import sys ''' 什么是发布/订阅: 在上一个实例中,我们搭建了一个工作队列,每个任务只分发给一个工作者(worker)。 在本实例中,我们要做的跟之前完全不一样 即:分发一个消息给多个消费者(consumers)。这种模式被称为“发布/订阅”。 ''' # 发布订阅--发布者 # 建立一个链接 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) # 链接rabbitmq channel = connection.channel() # 在调用queue_declare方法的时候,不提供queue参数就可以创建一个随机名的队列 # result = channel.queue_declare() # 使用交换机,这里并没有使用指定的一个队列,而是使用交换机,这里使用的交换机是fanout交换机,并且指定交换机名为logs channel.exchange_declare(exchange='logs',exchange_type='fanout') # 获取消息发布 message = "info: Hello World!" # 开始发布消息 # 你会注意到routing_key为空,这是因为,它的值会被扇型交换机(fanout exchange)忽略。 channel.basic_publish(exchange='logs', routing_key='', body=message) print(" [x] Sent %r"%message) # 关闭链接 connection.close()
订阅者:
import pika import time connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.exchange_declare(exchange='logs', exchange_type='fanout') # 因为发布订阅用的是随机名队列,所以当与消费者断开连接的时候,这个队列应当被立即删除。exclusive标识符即可达到此目的。 result = channel.queue_declare('', exclusive=True) # 在之前的实例中我们都要事先知道,队列的名字,这里采用的就是默认获取 queue_name = result.method.queue # 开始绑定:将消费者绑定到和交换机同一个频道,这样logs交换机发来的消息,消费者就可以收到了 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] Received %r" % body.decode()) time.sleep(body.decode().count('.')) print(" [x] Done") channel.basic_consume(queue=queue_name, on_message_callback=callback) channel.start_consuming() # rabbitmqctl list_bindings 列出所有现存的绑定。 # 绑定的另外一种理解: # 绑定(binding)是指交换机(exchange)和队列(queue)的关系。 # 可以简单理解为:这个队列(queue)对这个交换机(exchange)的消息感兴趣。
路由模式 Routing
说明:生产者将消息发送给 broker,由交换机根据 routing_key
分发到不同的消息队列,然后消费者同样根据 routing_key
来消费对应队列上的消息。
图解:
代码
生产者:
#!/usr/bin/env python import pika import sys # 连接rabbitmq connection = pika.BlockingConnection( pika.ConnectionParameters(host='localhost')) # 创建关系 channel = connection.channel() # 声明交换机模式 channel.exchange_declare(exchange='direct_logs', exchange_type='direct') # 判断是那个关键字 severity = sys.argv[1] if len(sys.argv) > 1 else 'info' message = ' '.join(sys.argv[2:]) or 'Hello World!' # 向交换机发数据 routing_key交换机向那个关键字队列发送数据 channel.basic_publish( exchange='direct_logs', routing_key=severity, body=message) print(" [x] Sent %r:%r" % (severity, message)) connection.close()
消费者:
#!/usr/bin/env python import pika import sys # 连接rabbitmq connection = pika.BlockingConnection( pika.ConnectionParameters(host='localhost')) # 创建关系 channel = connection.channel() # 声明交换机模式 channel.exchange_declare(exchange='direct_logs', exchange_type='direct') # 创建队列 result = channel.queue_declare(queue='', exclusive=True) queue_name = result.method.queue # 设置关键字 severities = sys.argv[1:] if not severities: sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0]) sys.exit(1) # routing_key = [info] [warning] [error] 其中一个 # 进行队列的绑定 for severity in severities: channel.queue_bind( exchange='direct_logs', queue=queue_name, routing_key=severity) print(' [*] Waiting for logs. To exit press CTRL+C') # 回调函数 def callback(ch, method, properties, body): print(" [x] %r:%r" % (method.routing_key, body)) # 开始发布消息 channel.basic_consume( queue=queue_name, on_message_callback=callback, auto_ack=True) channel.start_consuming()
主题模式 Topics
图解
说明:其实,主题模式应该算是路由模式的一种,也是通过 routing_key
来分发,只不过是 routing_key
支持了正则表达式,更加灵活。
# 表示可以匹配0个或多个单词
* 表示只能匹配一个单词
代码:
生产者:
#!/usr/bin/env python import pika import sys # 连接 connection = pika.BlockingConnection( pika.ConnectionParameters(host='localhost')) channel = connection.channel() # 声明交换机 channel.exchange_declare(exchange='topic_logs', exchange_type='topic') routing_key = sys.argv[1] if len(sys.argv) > 2 else 'anonymous.info' message = ' '.join(sys.argv[2:]) or 'Hello World!' # 发布 channel.basic_publish( exchange='topic_logs', routing_key=routing_key, body=message) print(" [x] Sent %r:%r" % (routing_key, message)) connection.close()
消费者:
#!/usr/bin/env python import pika import sys connection = pika.BlockingConnection( pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.exchange_declare(exchange='topic_logs', exchange_type='topic') result = channel.queue_declare('', exclusive=True) queue_name = result.method.queue binding_keys = sys.argv[1:] if not binding_keys: sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0]) sys.exit(1) for binding_key in binding_keys: channel.queue_bind( exchange='topic_logs', queue=queue_name, routing_key=binding_key) print(' [*] Waiting for logs. To exit press CTRL+C') def callback(ch, method, properties, body): print(" [x] %r:%r" % (method.routing_key, body)) channel.basic_consume( queue=queue_name, on_message_callback=callback, auto_ack=True) channel.start_consuming()
RPC 模式 RPC
图解
说明:通过消息队列来实现 RPC 功能,客户端发送消息到消费队列,消息内容其实就是服务端执行需要的参数,服务端消费消息内容,执行程序,然后将结果返回给客户端。
代码
生产者代码:
#!/usr/bin/env python import pika import uuid class FibonacciRpcClient(object): def __init__(self): self.connection = pika.BlockingConnection( pika.ConnectionParameters(host='localhost')) self.channel = self.connection.channel() result = self.channel.queue_declare(queue='', exclusive=True) self.callback_queue = result.method.queue self.channel.basic_consume( queue=self.callback_queue, on_message_callback=self.on_response, auto_ack=True) def on_response(self, ch, method, props, body): if self.corr_id == props.correlation_id: self.response = body def call(self, n): self.response = None self.corr_id = str(uuid.uuid4()) self.channel.basic_publish( exchange='', routing_key='rpc_queue', properties=pika.BasicProperties( reply_to=self.callback_queue, correlation_id=self.corr_id, ), body=str(n)) while self.response is None: self.connection.process_data_events() return int(self.response) fibonacci_rpc = FibonacciRpcClient() print(" [x] Requesting fib(30)") response = fibonacci_rpc.call(30) print(" [.] Got %r" % response)
消费者代码:
#!/usr/bin/env python import pika connection = pika.BlockingConnection( pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.queue_declare(queue='rpc_queue') def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n - 1) + fib(n - 2) def on_request(ch, method, props, body): n = int(body) print(" [.] fib(%s)" % n) response = fib(n) ch.basic_publish(exchange='', routing_key=props.reply_to, properties=pika.BasicProperties(correlation_id=props.correlation_id), body=str(response)) ch.basic_ack(delivery_tag=method.delivery_tag) channel.basic_qos(prefetch_count=1) channel.basic_consume(queue='rpc_queue', on_message_callback=on_request) print(" [x] Awaiting RPC requests") channel.start_consuming()
总结
以上就是本文的全部内容,其中 Publish/Subscribe,Routing,Topics 三种模式可以统一归为 Exchange 模式,只是创建时交换机的类型不一样,分别是 fanout、direct、topic。
一般我们只会使用简单模式、发布订阅模式、路由模式,而主题模式和RPC模式很少使用了解即可。