RbbitMQ消息队列及python实现
1、简介
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。
所有主要的编程语言均有与代理接口通讯的客户端库。官网:http://www.rabbitmq.com/
RabbidMQ是一个消息代理:它接受和转发消息。你可以把它想象成一个邮局:当你把你想要寄出的邮件放在一个邮箱里时,你可以确定,邮递员先生或女士最终会把邮件交给你的收件人。
在这个类比中,rabbitmq是一个邮箱、一个邮局和一个邮递员。
应用场景:异步处理、应用解耦、流量削峰
帮助文档:http://www.rabbitmq.com/getstarted.html
2、特点及概念
Publisher:
消息的生产者,也是一个向交换器发布消息的客户端应用程序
Broker:
标识消息队列服务器实体.
Virtual Host:
虚拟主机。标识一批交换机、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost本质上就是一个mini版的RabbitMQ服务器,
拥有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在链接时指定,RabbitMQ默认的vhost是 /。
Exchange:
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列.
Banding:
绑定,用于消息队列和交换机之间的关联。一个绑定就是基于路由键将交换机和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
Queue:
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
Connection:
网络连接,比如一个TCP连接。
Channel:
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟链接,AMQP命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,
这些动作都是通过信道完成。因为对于操作系统来说,建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接。
Consumer:
消息的消费者,表示一个从一个消息队列中取得消息的客户端应用程序。
Message:
消息,消息是不具名的,它是由消息头和消息体组成。消息体是不透明的,而消息头则是由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(优先级)、
delivery-mode(消息可能需要持久性存储[消息的路由模式])等。
Exchange
Exchange类似于数据通信网络中的交换机,提供消息路由策略。rabbitmq中,producer不是通过信道直接将消息发送给queue,而是先发送给Exchange。一个Exchange可以和多个Queue进行绑定,
producer在传递消息的时候,会传递一个ROUTING_KEY, Exchange会根据这个ROUTING_KEY按照特定的路由算法,将消息路由给指定的queue。和Queue一样,Exchange也可设置为持久化,临时或者自动删除。
Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别:
-
Direct
直接交换器,工作方式类似于单播,Exchange会将消息发送完全匹配ROUTING_KEY的Queue -
fanout
广播是式交换器,不管消息的ROUTING_KEY设置为什么,Exchange都会将消息转发给所有绑定的Queue。 -
topic
主题交换器,工作方式类似于组播,Exchange会将消息转发和ROUTING_KEY匹配模式相同的所有队列,比如,ROUTING_KEY为user.stock的Message会转发给绑定匹配模式为 * .stock,user.stock,* . * 和#.user.stock.#的队列。( * 表是匹配一个任意词组,#表示匹配0个或多个词组) -
headers
消息体的header匹配(ignore)
Binding
所谓绑定就是将一个特定的 Exchange 和一个特定的 Queue 绑定起来。Exchange 和Queue的绑定可以是多对多的关系。
virtual host
虚拟主机。标识一批交换机、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、
交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在链接时指定,RabbitMQ默认的vhost是 /。
Broker:
标识消息队列服务器实体 Exchange:
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列
通信过程
假设P1和C1注册了相同的Broker,Exchange和Queue。P1发送的消息最终会被C1消费。基本的通信流程大概如下所示:
- P1生产消息,发送给服务器端的Exchange
- Exchange收到消息,根据ROUTINKEY,将消息转发给匹配的Queue1
- Queue1收到消息,将消息发送给订阅者C1
- C1收到消息,发送ACK给队列确认收到消息
- Queue1收到ACK,删除队列中缓存的此条消息
Consumer收到消息时需要显式的向rabbit broker发送basic.ack消息或者consumer订阅消息时设置auto_ack参数为true。在通信过程中,队列对ACK的处理有以下几种情况:
- 如果consumer接收了消息,发送ack,rabbitmq会删除队列中这个消息,发送另一条消息给consumer。
- 如果cosumer接受了消息, 但在发送ack之前断开连接,rabbitmq会认为这条消息没有被deliver,在consumer在次连接的时候,这条消息会被redeliver。
- 如果consumer接受了消息,但是程序中有bug,忘记了ack,rabbitmq不会重复发送消息。
- rabbitmq2.0.0和之后的版本支持consumer reject某条(类)消息,可以通过设置requeue参数中的reject为true达到目地,那么rabbitmq将会把消息发送给下一个注册的consumer。
3、安装
Erlang与RabbitMQ,安装路径都应不含空格符。
4、测试
4.1、启用管理插件
2、激活 RabbitMQ's Management Plugin:
服务安装成功
4、创建用户,密码,绑定角色
查看已有用户及用户的角色:rabbitmqctl.bat list_users
新增用户:rabbitmqctl.bat add_user 用户名 密码
rabbitmqctl.bat add_user test1 123456
设置管理员身份:rabbitmqctl.bat set_user_tags user administrator
rabbitmqctl.bat set_user_tags test1 administrator
4.2、管理界面
打开浏览器输入:http://127.0.0.1:15672/
通过默认账户 guest/guest 登录,如果能够登录,说明安装成功。
添加Admin用户
配置权限
添加用户角色
1、超级管理员(administrator)
可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
2、监控者(monitoring)
可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
3、策略制定者(policymaker)
可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。
4、普通管理者(management)
仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
5、其他
无法登陆管理控制台,通常就是普通的生产者和消费者。
添加角色testhost
选中Admin用户,设置权限:
看到权限已加:
5、测试实例
安装pika模块,python使用rabbitmq服务,可以使用现成的类库pika、txAMQP或者py-amqplib,这里选择了pika。在命令行中直接使用pip命令:pip install pika
队列是位于rabbitmq中的邮箱的名称。尽管消息通过rabbitmq和在程序中传送,但它们只能存储在队列中。队列只受主机内存和磁盘限制的约束,
它本质上是一个大的消息缓冲区。许多生产者可以向一个队列发送消息,许多消费者可以尝试从一个队列接收数据。
请注意:生产者、消费者和代理不必驻留在同一主机上;实际上,在大多数应用程序中,它们不必驻留在同一主机上。应用程序也可以同时是生产者和消费者。
在本文章中,使用python编写两个小程序:一个发送单个消息的生产者(发送者)和一个接收并打印消息的消费者(接收者)。这是一个信息传递的“你好世界”。
在下图中,“P”是我们的生产者,“C”是我们的消费者。中间的框是一个队列-一个消息缓冲区,rabbitmq代表使用者保留该缓冲区。
生产者将消息发送到“hello”队列。消费者从该队列接收消息。
5.1、发送
我们的第一个程序send.py将向队列发送一条消息。我们需要做的第一件事是与rabbitmq服务器建立连接。
import pika connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel()
我们现在已经连接到本地机器上的一个代理,因此就是本地主机。如果我们想连接到另一台机器上的代理,我们只需在这里指定它的名称或IP地址。
接下来,在发送之前,我们需要确保收件人队列存在。如果我们向不存在的位置发送消息,rabbitmq将只删除该消息。让我们创建一个Hello队列,将消息传递到该队列:
channel.queue_declare(queue='hello')
此时,准备发送消息。第一条消息将只包含一个字符串hello world!把它发送到问候队列。
在rabbitmq中,消息永远不能直接发送到队列,它总是需要通过一个交换。但不要被这些细节拖累。现在需要知道的只是如何使用由空字符串标识的默认交换。
此交换是特殊的它允许我们精确地指定消息应该进入哪个队列。队列名称需要在路由routing_key参数中指定:
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!') print(" [x] Sent 'Hello World!'")
在退出程序之前,需要确保网络缓冲区已刷新,并且消息实际上已传递到rabbitmq。此时可以轻轻地关闭连接。
connection.close()
如果这是您第一次使用rabbitmq,并且您没有看到“已发送”消息,那么您可能会感到头疼,想知道可能出了什么问题。可能代理启动时没有足够的可用磁盘空间(默认情况下,它需要至少200 MB的可用空间),
因此拒绝接受消息。检查代理日志文件以确认并在必要时降低限制。配置文件文档将向您展示如何设置。
5.2、接收
我第二个程序receive.py将接收来自队列的消息,并将它们打印到屏幕上。同样,首先我们需要连接到rabbitmq服务器。负责连接到Rabbit的代码与以前相同。
下一步和前面一样,是确保队列存在。使用queue_declare创建队列是等幂的,我们可以根据需要多次运行该命令,并且只创建一个队列。
channel.queue_declare(queue='hello')
您可能会问我们为什么要再次声明队列我们已经在以前的代码中声明了队列。如果我们确定队列已经存在,就可以避免这种情况。例如,如果send.py程序以前运行过。但我们还不确定先运行哪个程序。
在这种情况下,最好在两个程序中重复声明队列。
如果希望看到rabbitmq有哪些队列以及其中有多少消息。可以使用rabbitmqctl工具(作为特权用户)执行此操作:
在linux上:sudo rabbitmqctl list_queues
在windows上:rabbitmqctl.bat list_queues
从队列接收消息更加复杂。它通过向队列订阅回调函数来工作。每当我们收到一条消息,这个回调函数就会被PIKA库调用。在我们的示例中,此函数将在屏幕上打印消息的内容。
def callback(ch, method, properties, body): print(" [x] Received %r" % body)
接下来,我们需要告诉rabbitmq这个特定的回调函数应该从hello队列接收消息:
channel.basic_consume(callback, queue='hello', no_ack=True)
要使该命令成功,我们必须确保要订阅的队列存在。幸运的是,我们相信我们已经使用上面queue declare创建了的队列。
最后,我们进入一个永不结束的循环,它等待数据并在必要时运行回调。
print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming()
完整的send.py代码
#!/usr/bin/env python import pika connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.queue_declare(queue='hello') channel.basic_publish(exchange='', routing_key='hello', body='Hello World!') print(" [x] Sent 'Hello World!'") connection.close()
完整的receive.py代码
#!/usr/bin/env python import pika connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) channel = connection.channel() channel.queue_declare(queue='hello') def callback(ch, method, properties, body): print(" [x] Received %r" % body) channel.basic_consume(callback, queue='hello', no_ack=True) print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming()
producer.py
#coding:utf-8 import json import random import pika def sendMessage1(): host = '101.20.4.21' port = 5672 username = 'user_test' password = 'user_test123456' queue='test_persistent' routing_key='test_persistent' exchange='test' credentials = pika.PlainCredentials(username,password) connection = pika.BlockingConnection(pika.ConnectionParameters(host=host, port=port, credentials=credentials)) # 定义连接池 channel = connection.channel() # 声明队列以向其发送消息消息 channel.exchange_declare(exchange=exchange,exchange_type='direct') channel.queue_declare(queue, durable=True) dict={'name': '你好', 'password': 'abc'} data= json.dumps(dict, ensure_ascii=False) channel.basic_publish(exchange=exchange, routing_key=routing_key, body=data, properties=pika.BasicProperties(delivery_mode=2)) connection.close() # 关闭连接 if __name__ == '__main__': sendMessage1()
Receive.py
import pika import time def getMessage1(): host = '101.20.4.21' port = 5672 username = 'user_test' password = 'user_test123456' queue='test_persistent' routing_key='test_persistent' exchange='test' credentials = pika.PlainCredentials(username,password) connection = pika.BlockingConnection(pika.ConnectionParameters(host=host, port=port,credentials=credentials)) channel = connection.channel() channel.exchange_declare(exchange=exchange,exchange_type='direct') channel.queue_declare(queue, durable=True) channel.queue_bind(queue, exchange, routing_key) def callback(ch, method, properties, body): '''回调函数,处理从rabbitmq中取出的消息''' message=body.decode('utf8') print(" [x] Received %r" % message) # time.sleep(5) ch.basic_ack(delivery_tag=method.delivery_tag) # 发送ack消息 channel.basic_qos(prefetch_count=1) channel.basic_consume(queue, callback) print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming() # 开始监听 接受消息 if __name__ == '__main__': getMessage1()
现在我们可以在终端上试用我们的程序。首先,让我们启动一个消费者,它将连续运行等待交付:
python receive.py
重要概念:
Broker:消息队列服务器实体。
Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
producer:消息生产者,就是投递消息的程序。
consumer:消息消费者,就是接受消息的程序。
channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
消息队列的使用过程:
1、客户端连接到消息队列服务器,打开一个channel。
2、客户端声明一个exchange,并设置相关属性。
3、客户端声明一个queue,并设置相关属性。
4、客户端使用routing key,在exchange和queue之间建立好绑定关系。
5、客户端投递消息到exchange。 exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。
send.py