什么是消息队列
消息队列是一种在应用程序之间传递消息的通信模式。它提供了一种异步的、可靠的方式来处理分布式系统中的消息传递。在消息队列中,消息发送者(Producer)将消息发送到队列(Queue)中,而消息接收者(Consumer)则从队列中获取消息进行处理。消息队列作为中间件,解耦了消息的发送者和接收者,使它们可以独立地进行操作。
消息队列通常应用场景
- 异步任务处理:将耗时的任务或业务逻辑作为消息发送到队列中,在后台异步处理,提高系统的响应速度和并发处理能力。
- 解耦系统组件:不同的系统组件之间通过消息队列进行通信,实现解耦,使得系统的各个组件可以独立地进行扩展、修改和替换,提高系统的灵活性。
- 系统解耦和削峰填谷:将请求发送到消息队列中,由另一个系统或服务来处理请求,减轻系统的负载压力,实现削峰填谷。
- 日志收集和处理:将系统产生的日志消息发送到消息队列中,再由消费者进行处理、分析和存储,方便日志的集中管理和实时监控。
- 事件驱动架构:通过消息队列来实现系统的事件驱动架构,不同的系统组件可以通过消息的发布和订阅机制进行解耦和通信。
- 消息广播和通知:将消息广播到多个订阅者,实现实时通知、广播和推送功能,例如实时聊天系统、新闻订阅等。
- 分布式事务:通过消息队列来实现分布式事务的最终一致性,确保不同系统之间的数据一致性。
- 应用解耦和系统集成:将不同的应用程序之间通过消息队列进行集成,实现数据的传递和共享。
RabbitMQ简介
RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法。常用于在分布式系统中存储、转发消息,从而实现系统之间的解耦,支持多种客户端,包括Python、Ruby、.NET、Java等。RabbitMQ官方地址:http://www.rabbitmq.com
RabbitMQ工作原理
RabbitMQ结构图
组成部分说明
- Producer(消息生产者):Producer是消息的发送者,它将消息发送到RabbitMQ中的交换机(Exchange)。Producer可以将消息发送到特定的交换机,并指定一个路由键(Routing Key)来标识消息的路由规则。
- Exchange(交换机):Exchange是消息的接收和分发中心。Producer将消息发送到交换机,交换机根据指定的路由键将消息路由到一个或多个绑定的队列中。
- Direct Exchange(直连交换机):根据消息的路由键与绑定的队列进行精确匹配。
- Topic Exchange(主题交换机):根据消息的路由键与绑定的队列进行模式匹配,支持通配符的路由键。
- Fanout Exchange(扇形交换机):将消息广播给所有绑定的队列,忽略路由键。
- Queue(队列):Queue是消息的存储地点。它是RabbitMQ中的核心组件,用于存储待处理的消息。消息发送到队列后,等待消费者(Consumer)从队列中获取并处理消息。
- Binding(绑定):绑定是交换机和队列之间的关联关系。它定义了交换机如何将消息路由到特定的队列。绑定包括交换机名称、队列名称和可选的路由键。
- Consumer(消息消费者):Consumer是消息的接收者,它从队列中获取消息并进行处理。当有消息到达队列时,RabbitMQ将消息发送给一个或多个消费者进行处理。
- Connect(连接):Connect是Producer和Consumer与RabbitMQ之间建立的网络连接。每个应用程序都可以建立一个或多个Connect,用于与RabbitMQ进行通信。
- Channel(通道):Channel是建立在Connection上的虚拟连接。Producer和Consumer使用Channel来进行消息的发送和接收。通过使用多个Channel,可以实现并行处理多个消息的能力。
- Virtual Host(虚拟主机):Virtual Host是逻辑上的概念,用于对RabbitMQ进行逻辑分区。每个Virtual Host拥有自己的独立的交换机、队列、绑定等资源,实现了逻辑上的隔离。
- Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue
生产者发送消息流程
-
建立连接:生产者首先与 RabbitMQ 建立连接。连接包含 RabbitMQ 服务器的地址、端口和认证凭据等信息。
-
创建通道:在建立连接后,生产者必须创建一个通道(Channel)。通道是建立在连接上的虚拟连接,用于进行消息的发送和接收。通过通道,可以实现并发处理多个消息。
-
声明交换机:生产者需要声明要发送消息的交换机(Exchange)。交换机负责接收消息,并根据指定的路由键将消息路由到一个或多个绑定的队列。
-
声明队列(可选):如果生产者发送的消息需要直接发送到特定的队列,而不经过交换机的路由规则,那么生产者需要声明要发送消息的队列。如果队列不存在,RabbitMQ将自动创建该队列。
-
发布消息:生产者使用通道的 basicPublish 方法来发布消息。在发布消息时,需要指定以下参数:
- 交换机名称:消息将被发送到的交换机。
- 路由键(Routing Key):用于将消息路由到特定的队列。
- 消息内容:要发送的实际消息数据。
-
关闭通道和连接:在完成消息发送后,生产者应该关闭通道和连接,释放资源。
消费者接收消息流程
-
建立连接:消费者首先与 RabbitMQ 建立连接。连接包含 RabbitMQ 服务器的地址、端口和认证凭据等信息。
-
创建通道:在建立连接后,消费者必须创建一个通道(Channel)。通道是建立在连接上的虚拟连接,用于进行消息的发送和接收。通过通道,可以实现并发处理多个消息。
-
声明队列:消费者需要声明要接收消息的队列。如果队列不存在,RabbitMQ将自动创建该队列。
-
绑定队列和交换机:消费者将队列绑定到特定的交换机上,以便从交换机接收消息。绑定需要指定交换机名称、队列名称和可选的路由键。
-
消息消费:消费者使用通道的 basicConsume 方法来开始消费消息。在消费消息时,需要指定以下参数:
- 队列名称:要消费的队列。
- 消费回调函数:当消费者接收到消息时,会调用该回调函数进行处理。
-
消费者通过订阅队列并注册回调函数,当有消息到达队列时,RabbitMQ会将消息发送给消费者进行处理。
-
处理消息:消费者在收到消息后,通过回调函数对消息进行处理。处理可以包括解析消息内容、执行特定的业务逻辑等操作。
-
消息确认(可选):消费者可以选择确认消息的接收,以确保消息已经成功处理。确认可以通过调用通道的 basicAck 方法来实现。
-
关闭通道和连接:在完成消息处理后,消费者应该关闭通道和连接,释放资源。
六种消息模型
基本消息模型
基本消息模型示意图:
- P:生产者,就是发送消息的应用程序
- C:消费者:就是接收消息的应用程序,它会一直等待消息的到来
- queue:消息队列,图中红色长条部分,可以缓存消息;生产者向队列中投递消息,消费者从队列中获取出消息。
work消息模型
工作队列或者竞争消费者模示意图:
work queues与基本消息模型对比,多了一个消费者,两个消费者共同消费同一个队列中的消息,但是一个消息只能被一个消费者获取,两者是竞争关系
P:生产者:发送消息的应用程序
C1:消费者1:接收消息并且完成相应的任务,假设完成速度较慢(耗时10s)
C2:消费者2:接收消息并且完成相应的任务,假设完成速度较快(耗时2s)
这时生产者发送了5条消息,C1消费了1条,C2消费了4条,体现了能者多劳的关系,现实中对应机器设备处理任务的速度快慢。
Publish/subscribe模型
交换机类型:Fanout,也称为广播
Publish/subscribe模型示意图 :
P:生产者:发送消息的应用程序
X:交换机:与队列进行绑定
Queue:消息队列,图中红色长条部分,可以缓存消息;生产者向队列中投递消息,消费者从队列中获取出消息
C1:消费者1:接收消息并且完成相应的任务
C2:消费者2:接收消息并且完成相应的任务
与前面两种模式不同
生产者与交换机(Exchange)进行绑定,不再与队列(Queue)直接绑定
生产者发送消息到交换机(Exchange),不再直接发送到队列(Queue)
这是生产者发送一条消息,消费者1、消费者2都能接收到消息,就跟广播一样,播放音乐大家都能听到
Routing 路由模型
交换机类型:direct
Routing模型示意图:
P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
C1:消费者1,接收到所在队列指定了routing key 为 error 的消息
C2:消费者2,接收到所在队列指定了routing key 为 info、error、warning 的消息
这时生产者发送消息,交换机会根据routing key发送到特定的队列中,不同的routing key可以绑定到相同的队列中。
Topics 通配符模型
交换机类型:topics
Topics模型示意图:
P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
X:Exchange(交换机),Exchange类型为Topic Exchange,接收生产者的消息,然后把消息递交给与通配符routing key匹配(跟正则匹配差不多)的队列
C1:消费者1,接收到Q1队列的消息
C2:消费者2,接收到Q2队列的消息
这时生产者将消息发给broker,由交换机根据通配符routingkey来转发消息到指定的队列,每个消费者监听自己的
队列。
Routingkey一般都是有一个或者多个单词组成的,多个单词之间是以"."作为分割,例如:inform.order
通配符规则:
#:匹配一个或多个词
*:匹配1个词
举例:
inform.#:能够匹配inform.user.name 或者 audit.order
audit.*:只能匹配audit.order
保证消息的稳定性
消息持久化
RabbitMQ的消息默认存在内存中的,一旦服务器意外挂掉,消息就会丢失
消息持久化需做到三点
- Exchange设置持久化
- Queue设置持久化
- Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息
ACK确认机制
多个消费者同时收取消息,收取消息到一半,突然某个消费者挂掉,要保证此条消息不丢失,就需要acknowledgement机制,就是消费者消费完要通知服务端,服务端才将数据删除
这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。
设置集群镜像模式
我们先来介绍下RabbitMQ三种部署模式:
1)单节点模式:最简单的情况,非集群模式,节点挂了,消息就不能用了。业务可能瘫痪,只能等待。
2)普通模式:默认的集群模式,某个节点挂了,该节点上的消息不能用,有影响的业务瘫痪,只能等待节点恢复重启可用(必须持久化消息情况下)。
3)镜像模式:把需要的队列做成镜像队列,存在于多个节点,属于RabbitMQ的HA方案
为什么设置镜像模式集群,因为队列的内容仅仅存在某一个节点上面,不会存在所有节点上面,所有节点仅仅存放消息结构和元数据。下面自己画了一张图介绍普通集群丢失消息情况
消息补偿机制
持久化的消息,保存到硬盘过程中,当前队列节点挂了,存储节点硬盘又坏了,消息丢了,怎么办?
产线网络环境太复杂,所以不知数太多,消息补偿机制需要建立在消息要写入DB日志,发送日志,接受日志,两者的状态必须记录。
然后根据DB日志记录check 消息发送消费是否成功,不成功,进行消息补偿措施,重新发送消息处理。
如何实现延迟队列
RabbitMQ本身没有延迟队列,需要靠TTL和DLX模拟出延迟的效果
TTL(Time To Live)
RabbitMQ可以针对Queue和Message设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter
RabbitMQ针对队列中的消息过期时间有两种方法可以设置。
A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
B: 对消息进行单独设置,每条消息TTL可以不同。
如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter
DLX (Dead-Letter-Exchange)
RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由。
x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
x-dead-letter-routing-key:指定routing-key发送
队列出现dead letter的情况有:
- 消息或者队列的TTL过期
- 队列达到最大长度
- 消息被消费端拒绝(basic.reject or basic.nack)并且requeue=false
利用DLX,当消息在一个队列中变成死信后,它能被重新publish到另一个Exchange。这时候消息就可以重新被消费。