既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如何确保消息正确地发送至RabbitMQ? 如何确保消息接收方消费了消息?
RabbitMQ概述
什么是 RabbitMQ?
- RabbitMQ是一个由Erlang开发的,在AMQP(高级消息队列协议)基础上完成的消息队列。
- 消息队列用于应用间的异步协作
- 最大的特点就是消费并不需要确保提供方存在,实现了服务之间的高度解耦。
说一说RabbitMQ中的AMQP
AMQP,Advanced Message Queuing Protocol,高级消息队列协议
它是一个提供统一消息服务的**应用层二进制协议,**为面向消息的中间件设计。它是基于TCP/IP协议构造的协议
基于此协议的客户端与消息中间件可传递消息,并不受不同平台、开发语言和操作系统的影响,即可跨平台传输。
下面是AMQP模型的简要示意图:
AMQP的主要处理流程
生产者是将消息发送到Exchange,Exchange根据路由规则Routing Key将消息路由到不同的Queue上,如果Queue上有消费者监听,则消费者可以获得消息。
生产者在生产消息的时候是不知道消费者的状态的,消费者在消费消息时也是不知道消息是从哪个生产者来的,即生产者与消费者之间的完全解耦的。
为什么要用RabbitMQ?有什么好处?场景是什么?
- **异步处理:**对于一些不需要立即生效的操作,可以拆分出来,异步执行,使用消息队列实现,性能高
- **流量消峰:**订单系统使用消息队列做缓冲,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作
- **应用解耦:**比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成
RabbitMQ 中有哪些重要的角色?
RabbitMQ 中重要的角色有:生产者、消费者和代理。
- **生产者:**消息的创建者,负责创建和推送数据到消息服务器;
- **消费者:**消息的接收方,用于处理数据和确认消息;
- **代理:**就是 RabbitMQ 消息队列本身,用于扮演“快递”的角色,本身不生产消息,只是扮演“快递”的角色。
RabbitMQ的优缺点
优点:
- 有管理界面,方便使用;
- 功能丰富,支持消息持久化、消息确认机制、多种消息分发机制
- 可靠性高;
缺点:
- 使用erlang实现,不利于二次开发和维护;
- 性能较kafka差,持久化消息和ACK确认的情况下生产和消费消息单机吞吐量大约在1~2w左右,kafka单机吞吐量在十万级别。
RabbitMQ的组件和构造
- 生产者Publisher:生产消息,就是投递消息的一方。消息一般包含两个部分:消息体(payload)和标签(Label)
- 消费者Consumer:消费消息,也就是接收消息的一方。消费者连接到RabbitMQ服务器,并订阅到队列上。消费消息时只消费消息体,丢弃标签。
- Broker服务节点:表示消息队列服务器实体。一般情况下一个Broker可以看做一个RabbitMQ服务器。
- Queue:消息队列,用来存放消息。一个消息可投入一个或多个队列,多个消费者可以订阅同一队列,这时队列中的消息会被平摊(轮询)给多个消费者进行处理。
- Exchange:交换器,接受生产者发送的消息,根据路由键将消息路由到绑定的队列上。
- Routing Key: 路由关键字,用于指定这个消息的路由规则,需要与交换器类型和绑定键(Binding Key)联合使用才能最终生效。
- Binding:绑定,通过绑定将交换器和队列关联起来,一般会指定一个BindingKey,通过BindingKey,交换器就知道将消息路由给哪个队列了。
- Connection :网络连接,比如一个TCP连接,用于连接到具体broker
- Channel: 信道,AMQP 命令都是在信道中进行的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接,一个TCP连接可以用多个信道。客户端可以建立多个channel,每个channel表示一个会话任务。
- Message:消息,由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
- Virtual host:虚拟主机,用于逻辑隔离,表示一批独立的交换器、消息队列和相关对象。一个Virtual host可以有若干个Exchange和Queue,同一个Virtual host不能有同名的Exchange或Queue。最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段
工作模式
RabbitMQ的工作模式
https://blog.csdn.net/qq_33406883/article/details/124513956
RabbitMQ 提供了 6 种工作模式:
- 简单模式
- work queues
- Publish/Subscribe 发布与订阅模式
- Routing路由模式
- Topics 主题模式
- RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。
👉 简单模式(Hello World)
- P:生产者,也就是要发送消息的程序
- C:消费者:消息的接收者,会一直等待消息到来
- queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息
👉 工作队列模式(Work queues)
- Work Queues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。消费者之间对于同一个消息的关系是
竞争
的关系。 - 应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
👉 订阅模式( Pub/Sub)
在订阅模型中,多了一个 Exchange 角色,而且过程略有变化:
- P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
- C:消费者,消息的接收者,会一直等待消息到来
- Queue:消息队列,接收消息、缓存消息
Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
👉 路由模式(Routing )
队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key)
消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey
Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的Routingkey 与消息的 Routing key 完全一致,才会接收到消息
👉 通配符模式(Topics)
Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.* 只能匹配 item.insert
路由
消息是如何路由的?
- 消息提供方 ——> 路由 ——> 一至多个队列
- 消息发布到交换器(Exchange)时,消息将拥有一个路由键(routing key),在消息创建时设定
- 通过队列路由键,可以把队列绑定到交换器上
- 消息到达交换器后,RabbitMQ会将消息的路由键与队列的路由键进行匹配(针对不同的交换器类型有不同的路由规则),常见的交换机类型有四种:direct、fanout、topic、headers
Exchange四种类型
Exchange分发消息时,根据类型的不同分发策略不同。
目前共四种类型:direct、fanout、topic、headers
- direct:
Routing Key==Binding Key
, 消息中的路由键 Routing Key 如果和 Binding 中的 Binding Key 完全匹配,交换器就将消息发到对应的队列中。是基于完全匹配、单播的模式 - **fanout:**把所有发送到fanout交换器的消息,路由到所有绑定该交换器的队列 Queue 中,fanout 类型转发消息是最快的
- **topic:**通过模糊匹配的方式对消息进行路由,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上
- **headers:**不依赖于routing key与binding key的匹配规则,而是根据发送消息内容中的headers属性进行匹配;除此之外 headers 交换器和 direct 交换器完全一致,但性能差很多(目前几乎用不到了)
👉 Direct
direct交换机会将消息路由到binding key 和 routing key完全匹配的队列中。它是完全匹配、单播的模式。
👉 fanout
所有发到 fanout 类型交换机的消息都会路由到所有与该交换机绑定的队列上去。fanout 类型转发消息是最快的。
👉 topic
topic交换机使用routing key和binding key进行模糊匹配,匹配成功则将消息发送到相应的队列。routing key和binding key都是句点号“. ”分隔的字符串
- routing_key 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开,这些单词可以是任意单词,比如说:
"stock.usd.nyse"
单词列表最多不能超过 255 个字节 - 替换符:binding key中可以存在两种特殊字符“*”与“##”,其中
*
用于匹配一个单词,##
用于匹配多个单词 - 当一个队列绑定键是
#
,那么这个队列将接收所有数据,就有点像 fanout,如果队列绑定键当中没有#
和*
出现,那么该队列绑定类型就是 direct
👉 headers
headers交换机是根据发送的消息内容中的 headers属性 进行路由的。
- 在绑定Queue与Exchange时,指定一组键值对;
- 当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;
- 如果完全匹配则消息会路由到该Queue
- 否则不会路由到该Queue。
消息分发
消息基于什么传输?
- 由于TCP连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ 使用 信道 的方式来传输数据
- 信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制
为什么需要信道?为什么不是TCP直接通信?
- TCP的创建和销毁开销大,创建需要三次握手,销毁需要四次分手
- 如果不使用信道,那么引用程序就会使用TCP的方式连接到rabbitmq,高峰时每秒成千上万条连接会造成资源的巨大浪费(一条tcp消耗资源,成千上万的tcp会非常消耗资源),而且操作系统每秒处理TCP连接数量也是有限的,必定会造成性能瓶颈
- 信道的原理是一条线程一条信道,多条线程多条信道共同使用一条TCP连接。一条TCP连接可以容纳无限的信道,及时每秒造成成千上万的请求也不会造成性能瓶颈
消息属性和有效载荷(消息主体)
AMQP模型中的消息 (Message)对象是带有 属性(Attributes) 的。有些属性非常常见,例如:
Content type
: 内容类型Content encoding
: 内容编码Routing Key
: 路由键Delivery mode
: 投递方式(持久化 or 非持久化)Message priority
: 消息优先权Message publishing timestamp
: 消息发布的时间戳Expiration period
: 消息的有效期Publisher application id
: 发布应用的id
有些属性是被 AMQP代理所使用的,比如 Routing Key
,但是大多数是对给接收消息的消费者使用的,有些属性是可选为做消息头的。它们与HTTP协议的 X-headers
很相似,比如 Content type
、Content encoding
。
AMQP 的 消息除属性外,还含有一个消息体,即消息实际携带的数据,它对AMQP代理不透明。broker 不会检查或修改消息体,但是消息可以只包含属性而不携带消息体。
消息分发机制
主要有三种分发机制:轮训分发、不公平分发、预值分发
- 轮训分发
RabbitMQ默认采用的轮训分发,当消费者有多个,且处理速度不相等(例如一个快一个慢)的时候不适用
- 不公平分发
通过设置参数 channel.basicQos(1)
实现不公平分发策略,能者多劳
- 预值分发
当消息被消费者接收后,但是没有确认,此时这里就存在一个未确认的消息缓冲区,用于存储非被确认的消息,该缓存区的大小是没有限制的。
通过使用basic.qos
方法设置“预取计数”值定义通道上允许的未确认消息的最大数量
说说消息pull模式
pull模式主要是通过channel.basicGet方法来获取消息
// 从消息队列中获取消息
GetResponse response = channel.basicGet(QUEUE_NAME, false);
System.out.println(new String(response.getBody()));
// ACK应答
channel.basicAck(response.getEnvelope().getDeliveryTag(),false);
怎么设置消息的过期时间?
过期时间就是TTL,TTL 全称 Time To Live(存活时间/过期时间)。
当消息到达存活时间后,还没有被消费,会被自动清除。
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。
设置过期时间有两个办法:
- 在生产端发送消息时,给消息设置过期时间,单位毫秒(ms)
// 设置"tyson"消息时间为3000毫秒
Message msg = new Message("tyson".getBytes(), mp);
msg.getMessageProperties().setExpiration("3000");
- 在消息队列创建队列时,指定队列的TTL,从消息入队列开始计算,超过该时间的消息将会被移除。
消息丢失
如何确保消息不丢失?
消息丢失场景:生产者生产消息到RabbitMQ Server消息丢失、RabbitMQ Server存储的消息丢失和RabbitMQ Server到消费者消息丢失。
消息丢失从三个方面来解决:生产者确认机制、消费者手动确认消息和持久化。
👉 生产者确认机制
生产者发送消息到队列,无法确保发送的消息成功的到达server。
解决方法:
- 事务机制。在一条消息发送之后会使发送端阻塞,等待RabbitMQ的回应,之后才能继续发送下一条消息。性能差。
- 开启生产者确认机制(confirm),只要消息成功发送到交换机之后,RabbitMQ就会发送一个ack给生产者(即使消息没有Queue接收,也会发送ack)。如果消息没有成功发送到交换机,就会发送一条nack消息,提示发送失败。异步,性能好。
👉 路由不可达消息
生产者确认机制只确保消息正确到达交换机,对于从交换机路由到Queue失败的消息,会被丢弃掉,导致消息丢失。
对于不可路由的消息,有两种处理方式:Return消息机制和备份交换机。
- Return消息机制,提供了回调函数 ReturnCallback,当消息从交换机路由到Queue失败才会回调这个方法
- 备份交换机,alternate-exchange 是一个普通的exchange,当你发送消息到对应的exchange时,没有匹配到queue,就会自动转移到备份交换机对应的queue,这样消息就不会丢失。
👉 消费者手动消息确认
有可能消费者收到消息还没来得及处理MQ服务就宕机了,导致消息丢失。因为消息者默认采用自动ack,一旦消费者收到消息后会通知MQ Server这条消息已经处理好了,MQ 就会移除这条消息。
解决方法:
- 消费者设置为手动确认消息,即消费者处理完逻辑之后再给broker回复ack,表示消息已经成功消费,可以从broker中删除。
- 当消息者消费失败的时候,给broker回复nack,根据配置决定重新入队还是从broker移除,或者进入死信队列。只要没收到消费者的 ack,broker 就会一直保存着这条消息,但不会 requeue,也不会分配给其他 消费者。
👉 持久化
如果RabbitMQ服务异常导致重启,将会导致消息丢失。
RabbitMQ 提供了持久化的机制,将内存中的消息持久化到硬盘上,即使重启RabbitMQ,消息也不会丢失。
消息持久化需要满足****以下条件
- 消息设置持久化。发布消息前,设置投递模式
delivery mode
为2,表示消息需要持久化。 - Queue队列设置持久化。
- 交换机设置持久化。
持久化消息消费和恢复流程
- 当发布一条消息到交换机上时,RabbitMQ 会先把消息写入持久化日志文件,然后才向生产者发送响应。
- 一旦消费者从持久队列中消费了一条持久化消息并且做了确认,RabbitMQ会在持久化日志中把这条消息标记为等待垃圾收集,从而移除这条消息。
- 如果持久化消息在被消费之前RabbitMQ重启,服务器会自动重建交换机和队列(以及绑定),并重新加载持久化日志中的消息到相应的队列或者交换机上,保证消息不会丢失。
什么是死信队列?
消费失败的消息存放的队列。
消息消费失败的原因:
- 消息被拒绝并且消息没有重新入队(requeue=false)
- 消息超时未消费
- 达到最大队列长度
当普通队列中有死信时,RabbitMQ 就会自动的将这个消息重新发布到设置的死信交换机去,然后被路由到死信队列。可以监听死信队列中的消息做相应的处理。
Rabbitmq如何实现延迟队列
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
Q 就会自动的将这个消息重新发布到设置的死信交换机去,然后被路由到死信队列。可以监听死信队列中的消息做相应的处理。
Rabbitmq如何实现延迟队列
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
[外链图片转存中…(img-iPfralHu-1715300506252)]
[外链图片转存中…(img-ndhqxq3e-1715300506252)]
[外链图片转存中…(img-3ysbpDUA-1715300506253)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新