AMQP协议
AMQP协议:即Advanced Message Queuing Protocol,是一个应用层标准高级消息队列协议,提供统一消息服务。是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。
AMQP中消息的路由过程和JMS存在一些差别。AMQP中增加了Exchange和Binding的角色。生产者把消息发布到Exchange上,消息最终到达队列并被消费者接收,而Binding决定交换器的消息应该发送到哪个队列。
rabbitmq整体架构
RabbitMQ各组件功能与核心概念
- Broker:标识消息队列服务器实体.
-
Virtual Host:虚拟主机。标识一批交换机、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在链接时指定,RabbitMQ默认的vhost是 /。(可以理解成一个数据库)
- 生产者(Producer):发送消息的应用。
- 消费者(Consumer):接收消息的应用。
- 队列(Queue):存储消息的缓存。
- 消息(Message):由生产者通过RabbitMQ发送给消费者的信息。
- 连接(Connection):连接RabbitMQ和应用服务器的TCP连接。
- 通道(Channel):连接里的一个虚拟通道。当你通过消息队列发送或者接收消息时,这个操作都是通过通道进行的。
- 交换机(Exchange):交换机负责从生产者那里接收消息,并根据交换类型分发到对应的消息列队里。要实现消息的接收,一个队列必须到绑定一个交换机。
- 绑定(Binding):绑定是队列和交换机的一个关联连接。
-
路由键(Routing Key):路由键是供交换机查看并根据键来决定如何分发消息到列队的一个键。路由键可以说是消息的目的地址。
- 用户(Users):在RabbitMQ里,是可以通过指定的用户名和密码来进行连接的。每个用户可以分配不同的权限,例如读权限,写权限以及在实例里进行配置的权限。(每个用户有不同的虚拟主机权限,就像mysql中不同用户有不同的数据库权限一样)
通信过程
- 生产者(producer)链接到Rabbit MQ Broker,创建connection,开启channel
- 生产者声明交换机类型,名称,是否持久化等。
- 生产者发送消息,并指定消息是否持久化等属性和routing key。
- 交换机(exchange)接收消息(message)后根据消息的routing key路由到跟当前交换机绑定(binding)的相匹配的队列里去。
- 消费者(consumer)监听接收到消息之后开始业务处理,然后发送一个ack确认告知消息已经被消费。
- RabbitMQ Broker收到ack之后将对应的消息从队列里删除。
交换机(Exchange)
交换机类型决定了消息路由模式:从生产者应用上接收消息,然后根据绑定和路由键将消息发送到对应的队列里。绑定是交换机和队列之间的一个关系连接。
交换机类型:
- 直接(Direct):直接交换机通过消息上的路由键直接对消息进行分发。
- 扇出(Fanout):一个扇出交换机会将消息发送到所有和它进行绑定的队列上(只看binding,不看路由键)。
- 主题(Topic):这个交换机会将路由键和绑定上的模式进行通配符匹配。
- 消息头(Headers):消息头交换机使用消息头的属性进行消息路由。(几乎用不到了)
交换机其他属性:
durable:持久化,创建交换机/队列 时指定durable=true,创建消息指定delivery_mode => 2 的都会存盘,所以服务重启后会自动恢复。
autoDelete:设置是否自动删除,当最后一个绑定到Exchange上的队列删除后,自动删除该Exchange,简单来说也就是如果该Exchange没有和任何队列Queue绑定则删除
internal :设置是否是RabbitMQ内部使用,默认false。如果设置为 true ,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。
RabbitMQ工作模式
简单模式:一个生产者,一个消费者
work模式:一个生产者,多个消费者,每个消费者获取到的消息唯一
订阅模式(p/s):一个生产者发送的消息会被多个消费者获取,且消息相同。可以理解为type=fanout。
路由模式:发送的消息到交换机并且要指定路由key,消费者将队列绑定到交换机时需要指定路由key(这个就是交换机控制的路由模式)
主题模式:将路由键和某模式进行匹配,此时队列需要绑定在一个模式上,‘#’匹配一个词或者多个词,‘*’匹配一个词
多个队列消息发布图:
RabbitMQ消费模式
- PUSH:RabbitMQ将消息主动推送给消费者
注册一个消费者后,RabbitMQ会在消息可用时,自动将消息进行推送给消费者。这种方式效率最高最及时
channel.basic_consume(queuename,autoAck=true,callback() )
- PULL:消费者去拉取消息
属于一种轮询模型,发送一次get请求,获得一个消息。如果此时RabbitMQ中没有消息,会获得一个表示空的回复。channel.basic_get(queuename,autoAck=true)
- ack(Message acknowledgment):消息确认机制
自动确认机制autoAck=true,则消费者拿到消息后这条消息直接被rabbitMQ从队列中删除。
手动确认机制autoAck=false,这种一般是为了保证消息成功完成消费。那就需要开发同学在消息成功消费后添加一个手动确认逻辑,channel.basic_ack(delivery_tag=method.delivery_tag())
没有ack会发生什么,这条消息不会删除,继续发给下一个消费者消费。
https://www.rabbitmq.com/tutorials/tutorial-two-python.html
RabbitMQ项目实践
【加好友配额中心】项目中,需要对用户做的操作实时的给予奖励。比如【处理好友信息】:每天及时处理好友信息,我们会给用户发5个好友额度奖励。
好友额度跟待处理发生了以下对话:
不属于一个功能模块,耦合性太高,维护困难,理解困难。而且用户只是处理一个请求,不需要把计算奖励额度的时间累加给待处理请求(待处理表示不同意!)
这时候,开发同学就想到了消息中间件~~~
待处理请求后,发一条消息到MQ。好友额度定时从MQ拿消息,拿到后就知道是用户处理了请求。再去做相应的额度奖励处理。这就不会有上面的问题了。
具体的message就根据业务需求来填充了。多种奖励类型,每种奖励都有一个自己的队列,所以需要使用routing_key属性;
发布消息:
msg={"uid2": 138262752, "uid": 138262060, "accepted": 3, "type": 1, "id": 46374, "frm": null, "ts": 1603178783.451986}
manager.mq2.publish_user_action(msg=msg, routing_key='network_contact_request_finished')
>>>>>>MQueue.publish(msg=msg, exchange='user_action', routing_key=routing_key) //发布消息的话,需要exchange,跟routing_key
接收消息:
from mmsdk.models.mqueue import MQueue
mqueue = MQueue(host=host, port=port, user=user, passwd=password)
msg = mqueue.basic_get(name='user_action_network_request_finished', no_ack=no_ack) //从队列取消息的话是直接根据queue_name拿消息。