也就是不需要同步处理
请求先入消息队列,而不是由业务处理系统直接处理,做了一次缓冲,服务器A、B根据各自处理能力,去消息队列取相应数量的请求进行处理,暂时没有处理的请求就在消息队列中等待,避免了大流量冲垮系统。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V3ibOCuc-1589255031784)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1588218418837.png)]
也称P2P模式,包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。
点对点模式特点:
包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。
发布/订阅模式特点
常用的四种MQ:RabbitMQ/ActiveMQ/RocketMQ/Kafka,各有优缺点。因业务量并发量可能并不高,本文后半篇主要介绍rabbitmq的使用
RabbitMQ
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。
ActiveMQ
ActiveMQ是由Apache出品,ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现。它非常快速,支持多种语言的客户端和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能。
ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。
RocketMQ
RocketMQ出自 阿里公司的开源产品,用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的一些改进,消息可靠性上比 Kafka 更好。RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。
文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码,适合有一定技术实力的公司。
Kafka
Apache Kafka是一个分布式消息发布订阅系统。它最初由LinkedIn公司基于独特的设计实现为一个分布式的提交日志系统( a distributed commit log),之后成为Apache项目的一部分。
kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。
下面列一个简单的一个消息推送到接收的流程,提供一个简单的图
由生产者生产消息,可选择交换机和队列进行推送
消费者消费消息时,需要选择队列消费。也可进行提前注册,达到监听消息的目的。
以下特点结合实际使用场景,通过在代码过程配置实现以下功能
rabbitmq有可视化监控界面
可以持久化队列消息,在rabbitmq服务挂了的时候,重启服务不会出现数据丢失
点对点消费,在消费者消费完消息后,消息即从队列中移除
对于消费过程,可以选择轮询模式和公平分发模式
轮询模式:多个消费者监听同一消息的时候,该消息可以轮流分发到消费者
公平分发模式:可以理解为,在各个消费者端,通过配置perfetch=1,告诉RabbitMQ在这个消费者当前消息还没处理完的时候就不要再给我发新消息了
消费确认,在消息消费未完成,没有向服务器发送确认信息之前,消息不会从队列中移除。避免消费过程中断导致消息丢失,该功能需要在写代码时修改配置
常用的交换机有以下三种,因为消费者是从队列获取信息的,队列是绑定交换机的(一般),所以对应的消息推送/接收模式也会有以下几种:
Direct Exchange
直连型交换机,根据消息携带的路由键将消息投递给对应队列。
大致流程,有一个队列绑定到一个直连交换机上,同时赋予一个路由键 routing key 。
然后当一个消息携带着路由值为X,这个消息通过生产者发送给交换机时,交换机就会根据这个路由值X去寻找绑定值也是X的队列。Fanout Exchange
扇型交换机,这个交换机没有路由键概念,就算你绑了路由键也是无视的。 这个交换机在接收到消息后,会直接转发到绑定到它上面的所有队列。
Topic Exchange
主题交换机,这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的。
简单地介绍下规则:
- (星号) 用来表示一个单词 (必须出现的)
(井号) 用来表示任意数量(零个或多个)单词
通配的绑定键是跟队列进行绑定的,举个小例子
队列Q1 绑定键为 .TT. 队列Q2绑定键为 TT.#
如果一条消息携带的路由键为 A.TT.B,那么队列Q1将会收到;
如果一条消息携带的路由键为TT.AA.BB,那么队列Q2将会收到;主题交换机是非常强大的,为啥这么膨胀?
当一个队列的绑定键为 “#”(井号) 的时候,这个队列将会无视消息的路由键,接收所有的消息。
当 * (星号) 和 # (井号) 这两个特殊字符都未在绑定键中出现的时候,此时主题交换机就拥有的直连交换机的行为。
所以主题交换机也就实现了扇形交换机的功能,和直连交换机的功能。
引入支持rabbitmq的支持包
pip intall pika
pika模块是python对rabbitmq的api接口,在消费消息的时候,提供了一个可自定义的回调函数入口,在回调函数里,就可以自由发挥了。想实现什么功能私人定制
一个生产者,三个消费者同时注册到同一个队列上,然后轮询消费消息。在消费过程中需要考虑队列信息持久化,和消费者异常挂掉消息丢失的情况
在消息传递过程中使用直联机模式
创建producer.py
import pika
python_exchange = 'python_exchange'
routingKey = 'python_routing_key'
python_queue = 'python_queue'
# 1 建立rabbit连接通道
credentials = pika.PlainCredentials('guest', 'guest') # 用户名,密码
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost', 5672, '/', credentials))
channel = connection.channel()
# 2 定义交换机名称和交换机类型
# exchange: 交换机名称,可以自定义
# exchange_type: 交换机类型,默认就是直连的
channel.exchange_declare(exchange=python_exchange, exchange_type='direct')
# 定义路由键信息
# 3 定义队列,绑定交换机 durable=True:队列持久化
channel.queue_declare(queue=python_queue, durable=True, auto_delete=True)
channel.queue_bind(exchange=python_exchange, queue=python_queue, routing_key=routingKey)
# 4 发布消息
# properties=pika.BasicProperties(delivery_mode=2, ) 持久化队列中的消息
test_msg = 'this is a test msg........'
channel.basic_publish(exchange=python_exchange, routing_key=routingKey, body=test_msg,
properties=pika.BasicProperties(delivery_mode=2, ))
# 关闭连接
connection.close();
同样的代码创建三份:consumer1.py consumer2.py consumer3.py 同样干了以下几件事
- 创建连接通道
- 声明队列
- 定义回调函数
- 注册消费者到指定队列,开始消费,消费完后向rabbitmq发送确认消息
import pika
python_queue = 'python_queue1'
# 1 建立rabbit连接通道
credentials = pika.PlainCredentials('guest', 'guest') # 用户名,密码
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost', 5672, '/', credentials))
channel = connection.channel()
# 3 定义队列,只需关注需要监听的队列
channel.queue_declare(queue=python_queue, durable=True, auto_delete=True)
def callback(ch,method,properties,body):
print("消费者1收到消息如下 %r" % body)
print("--------------开始后续的业务扩展-----------------")
# 注册消费者,开始消费
channel.basic_consume(python_queue, callback, True)
channel.start_consuming()
注:durable=True, auto_delete=True在生产者和消费者中要保持一直,不然会报错
启动producer.py,访问http://localhost:15672/ 可以看到队列、交换机、和消息都已发送至rabbit
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NFlq3ads-1589255031792)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1588147367448.png)]
分别启动consumer1.py consumer2.py consumer3.py。可以看到消费者轮询接收到消息
一个生产者,三个消费者同时注册到同一个队列上,然后轮询消费消息,当自身消费不了消息时会通知rabiitmq不要再继续发送消息。
在消息传递过程中使用直联机模式
如果Rabbit只管按顺序把消息发到各个消费者身上,不考虑消费者负载的话,很可能出现,一个机器配置不高的消费者那里堆积了很多消息处理不完,同时配置高的消费者却一直很轻松。为解决此问题,可以在各个消费者端,配置perfetch=1,意思就是告诉RabbitMQ在我这个消费者当前消息还没处理完的时候就不要再给我发新消息了。
只需要修改消费者配置:
- 在消费者注册之前添加: channel.basic_qos(prefetch_count=1)
- 在回调函数中添加: ch.basic_ack(delivery_tag =method.delivery_tag) 作用是生产者端消息持久后, 保证消息被消费后,消费端发送一个ack,然后服务端从队列删除该消息
- 关闭消费者注册时的自动消息确认(不然会报错),因为在回调函数时已经主动完成确认
生产者代码可参考3.1 生产者的代码
消费者代码如下:
import pika
import time
python_queue = 'python_queue1'
# 1 建立rabbit连接通道
credentials = pika.PlainCredentials('guest', 'guest') # 用户名,密码
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost', 5672, '/', credentials))
channel = connection.channel()
# 3 定义队列,只需关注需要监听的队列
channel.queue_declare(queue=python_queue, durable=True, auto_delete=True)
def callback(ch,method,properties,body):
print("消费者1收到消息如下 %r" % body)
time.sleep(body.count(b'.'))
print("--------------开始后续的业务扩展-----------------")
ch.basic_ack(delivery_tag=method.delivery_tag)
# 注册消费者,开始消费
channel.basic_qos(prefetch_count=1)
channel.basic_consume(python_queue, callback)
channel.start_consuming()