RabbitMQ能用来解决什么问题
1.数据量大或耗时长的操作,mq实现异步通信,减少客户端等待,提升响应速度。
2.对于改动影响大的系统,mq实现解耦,减少系统之间直接依赖(如:订单取消后增加库存、退还支付金额、发送消息通知)。
3.对于瞬间的流量峰值系统,mq实现流量削峰,达到保护应用和数据库的目的(如:商品抢购预定)。
为什么要使用RabbitMQ
1、使用简单、功能强大
2、基于amqp协议:除了qpid,rabbitmq是唯一一个实现amqp标准的消息服务器;
3、可靠性:Rabbitmq的持久化,保证消息稳定性;
4、集群部署简单:因为erlang使rabbitmq集群部署变得很简单;
5、社区活跃、文档完善,rabbitmq也是首选;
6、高并发性能好,rabbitmq使用erlang语言开发,erlang是电话交换机开发的语言,自带高并发和高可用特性;
7、springboot默认已集成rabbitmq;
为什么不适用TCP直接发送命令
对于操作系统来说创建和销毁tcp会话是非常昂贵的开销,假设高峰期每有成千上万条连接,每一个连接都要创建一条tcp会话,就会造成tcp连接的巨大浪费,
并且操作系统每秒能创建的tcp也是有限的,很快会遇到系统瓶颈。如果我们每个请求都是用一条tcp连接,即满足的性能需求,又能保证每个连接的私密性,
这就是引入信道的原因。
RabbitMQ基本概念
Broker:
简单来说就是消息队列服务器实体
Exchange:
消息交换机,它指定消息按什么规则,路由到哪个队列
Queue:
消息队列载体,每个消息都会被投入到一个或多个队列
Binding:
绑定,它的作用就是把exchange和queue按照路由规则绑定起来
Routing Key:
路由关键字,exchange根据这个关键字进行消息投递
VHost:
vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。
Producer:
消息生产者,就是投递消息的程序
Consumer:
消息消费者,就是接受消息的程序
Channel:
消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务
RabbitMQ的工作模式
simple模式(即最简单的收发模式)
消息产生消息,将消息放入队列
消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,
自动从队列中删除(隐患:消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。
work工作模式(资源的竞争)
消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)。
publish/subscribe发布订阅(共享资源)
每个消费者监听自己的队列;
生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。
routing路由模式
消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息;
根据业务功能定义路由字符串
从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中。
业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误;
topic 主题模式(路由模式的一种)
星号井号代表通配符
星号代表多个单词,井号代表一个单词
路由功能添加模糊匹配
消息产生者产生消息,把消息交给交换机
交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费
如何保证消息的顺序性
- 拆分多个 queue(消息队列),每个 queue(消息队列) 一个 consumer(消费者),就是多一些 queue(消息队列)而已,确实是麻烦点
- 或者就一个 queue (消息队列)但是对应一个 consumer(消费者),然后这个 consumer(消费者)内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
死信队列和延迟队列
死信消息:
消息被拒绝(Basic.Reject或Basic.Nack)并且设置 requeue 参数的值为 false
消息过期了
队列达到最大的长度
过期消息:
在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信 消息。
队列设置: 在队列声明的时候使用 x-message-ttl 参数,单位为 毫秒
单个消息设置: 是设置消息属性的 expiration 参数的值,单位为 毫秒
可以使用RabbitMQ的插件“rabbitmq_dead_letter_exchange”,它提供了一个死信交换机(Dead Letter Exchange),可以将无法被处理的消息路由到指定的死信队列中
延时队列:
- 在rabbitmq中不存在延时队列,但是我们可以通过设置消息的过期时间和死信队列来模拟出延时队列。消费者监听死信交换器绑定的队列,而不要监听消息发送的队列。
- 可以使用RabbitMQ的插件“rabbitmq_delayed_message_exchange”,它提供了一个延迟交换器(Delayed Message Exchange),可以将消息路由到指定的队列中等待指定的延迟时间
RabbitMQ 中的 broker 是指什么?cluster 又是指什么?
broker 是指一个或多个 erlang node 的逻辑分组,且 node 上运行着 RabbitMQ 应用程序。
cluster 是在 broker 的基础之上,增加了 node 之间共享元数据的约束。
RabbitMQ 概念里的 channel、exchange 和 queue 是逻辑概念,还是对应着进程实体?分别起什么作用?
Queue 具有自己的 erlang 进程;
exchange 内部实现为保存 binding 关系的查找表;
channel 是实际进行路由工作的实体,即负责按照 routing_key 将 message 投递给 queue 。由 AMQP 协议描述可知,channel 是真实 TCP 连接之上的虚拟连接,所有 AMQP 命令都是通过 channel 发送的,且每一个 channel 有唯一的 ID。一个 channel 只能被单独一个操作系统线程使用,故投递到特定 channel 上的 message 是有顺序的。但一个操作系统线程上允许使用多个 channel
vhost 是什么?起什么作用?
== vhost== 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。
消息基于什么传输
由于TCP连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ使用信道的方式来传输数据。信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制。
消息如何分发
若该队列至少有一个消费者订阅,消息将以循环(round-robin)的方式发送给消费者。每条消息只会分发给一个订阅的消费者。
消息怎么路由
从概念上来说,消息路由必须有三部分:交换器、绑定、路由。生产者把消息发布到交换器上;绑定决定了消息如何从路由器路由到特定的队列;消息最终到达队列,并被消费者接收。
常用的交换器主要分为一下三种:
direct:如果路由键完全匹配,消息就被投递到相应的队列
fanout:如果交换器收到消息,将会广播到所有绑定的队列上
topic:可以使来自不同源头的消息能够到达同一个队列。使用topic交换器时,可以使用通配符。
比如:“*” 匹配特定位置的任意文本, “.” 把路由键分为了几部分,“#” 匹配所有规则等。
特别注意:发往topic交换器的消息不能随意的设置选择键(routing_key),必须是由"."隔开的一系列的标识符组成。
什么是元数据?元数据分为哪些类型?包括哪些内容?与 cluster 相关的元数据有哪些?元数据是如何保存的?元数据在 cluster 中是如何分布的?
- 在非 cluster 模式下,元数据主要分为 Queue 元数据(queue 名字和属性等)、Exchange元数据(exchange 名字、类型和属性等)、Binding 元数据(存放路由关系的查找表)、Vhost元数据(vhost 范围内针对前三者的名字空间约束和安全属性设置)。
- 在 cluster 模式下,还包括 cluster 中 node 位置信息和 node 关系信息。元数据按照 erlang node 的类型确定是仅保存于 RAM 中,还是同时保存在 RAM 和 disk 上。元数据在 cluster 中是全 node 分布的。
在单node 系统和多 node 构成的 cluster 系统中声明 queue、exchange ,以及进行 binding 会有什么不同
在单 node 上声明 queue 时,只要该 node 上相关元数据进行了变更,你就会得到 Queue.Declare-ok 回应;而在 cluster 上声明 queue ,则要求 cluster 上的全部 node 都要进行元数据成功更新,才会得到 Queue.Declare-ok 回应。
如何确保消息正确地发送至RabbitMQ?
RabbitMQ使用发送方确认模式,确保消息正确地发送到RabbitMQ。
发送方确认模式:将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(not acknowledged,未确认)消息。发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。
如何确保消息接收方消费了消息?
接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。
下面罗列几种特殊情况:
如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要根据bizId去重)
如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。
如何保证 RabbitMQ 不被重复消费?
先说为什么会重复消费:正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除;
但是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将消息分发给其他的消费者。
针对以上问题,一个解决思路是:
- 保证消息的唯一性,就算是多次传输,不要让消息的多次消费带来影响;
- 保证消息等幂性;比如:在写入消息队列的数据做唯一标示,消费消息时,根据唯一标识判断是否消费过;
如何避免消息重复投递或重复消费?
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列;在消息消费时,要求消息体中必须要有一个bizId(对于同一业务全局唯一,如支付ID、订单ID、帖子ID等)作为去重和幂等的依据,避免同一条消息被重复消费。
如何保证 RabbitMQ 消息的可靠传输
消息不可靠的情况可能是消息丢失,劫持等原因;
丢失又分为:生产者丢失消息、消息列表丢失消息、消费者丢失消息;
生产者丢失消息:
从生产者弄丢数据这个角度来看,RabbitMQ 提供 transaction 和 confirm 模式来确保生产者不丢消息;
transaction 机制就是说:发送消息前,开启事务(channel.txSelect()), 然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()), 如果发送成功则提交事务(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;
confirm 模式用的居多:一旦 channel 进入 confirm 模式,所有在该信道上发布的消息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后;rabbitMQ 就会发送一个 ACK 给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了;如果 rabbitMQ 没能处理该消息,则会发送一个 Nack 消息给生产者,生产者可以进行重试操作。
消息队列丢数据:
消息持久化:处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和 confirm 机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个 Ack 信号。这样,如果消息持久化磁盘之前,rabbitMQ 阵亡了,那么生产者收不到 Ack 信号,生产者会自动重发。
那么如何持久化呢?
将 queue 的持久化标识 durable 设置为 true, 则代表是一个持久的队列
发送消息的时候将 deliveryMode=2
这样设置以后,即使 rabbitMQ 挂了,重启后也能恢复数据
消费者丢失消息:
消费者丢数据一般是因为采用了自动确认消息模式,改为手动确认消息即可!
什么说保证 message 被可靠持久化的条件是 queue 和 exchange 具有durable 属性,同时 message 具有 persistent 属性才行?
binding 关系可以表示为 exchange – binding – queue 。从文档中我们知道,若要求投递的 message 能够不丢失,要求 message 本身设置 persistent 属性,要求 exchange和 queue 都设置 durable 属性
多个消费者监听一个队列时,消息如何分发?
轮询分配
当有多个消费者同时监听一个队列时,RabbitMQ默认将消息逐一顺序分配给各消费者,该消息分配机制称为轮询(Round-Robin)
消息预取
RabbitMQ尽可能快速地将消息推送至客户端,由客户端缓存本地,而并非在消息消费时才逐一确定。再加入新的消费者时,队列已经为空,即使前面的消费者未处理完消息,新加入的消费者也不会接收到
公平分配
每个消费者都根据自身处理能力合理分配消息处理任务,既无挤压也无空闲,新加入的消费者也能分担消息处理任务,使系统的处理能力能够平行扩展。
RabbitMQ客户端可通过Channel类的basicQos(int prefetchCount)设置消费者的预取数目,即消费者最大的未确认消息的数目
消息积压了怎么办?
增加消费者
增加消费者是解决消息积压问题的常见方法之一。通过增加消费者的数量,可以提高消息的处理速度,从而缓解消息积压的情况。
提高消费者处理能力
除了增加消费者数量,我们还可以通过提高消费者的处理能力来解决消息积压问题。可以通过优化消费者的代码逻辑、增加消费者的处理线程数、提升消费者服务器的性能等方式来提高消费者的处理能力。
设置消费者的负载均衡
在多个消费者同时消费同一个队列的情况下,可以使用消费者负载均衡的方式来解决消息积压问题。通过将消息均匀地分配给多个消费者,可以提高消息的处理效率,避免某个消费者被过多的消息压力所影响。
设置消息超时时间
设置消息的超时时间,即在一定时间内未被消费者处理的消息将被丢弃。通过设置适当的超时时间,可以避免消息在队列中过长时间的堆积。
增加队列容量
增加队列的容量可以缓解消息积压的问题。通过增加队列的容量,可以存储更多的消息,避免消息因为队列容量不足而被丢弃或堆积。
监控与报警
建立监控系统,实时监控队列的长度、消费者的处理速度等指标,当达到一定的阈值时触发报警机制,及时采取相应的措施来解决消息积压问题。
消息传输保证层级?
1、 At most once:最多一次。消息可能会丢失,单不会重复传输。
2、 At least once:最少一次。消息觉不会丢失,但可能会重复传输。
3、 Exactly once:恰好一次,每条消息肯定仅传输一次。
事务机制?
RabbitMQ 客户端中与事务机制相关的方法有三个:
channel.txSelect 用于将当前的信道设置成事务模式。
channel.txCommit 用于提交事务 。
channel.txRollback 用于事务回滚,如果在事务提交执行之前由于 RabbitMQ 异常崩溃或者其他原因抛出异常,通过txRollback来回滚。
routing_key 和 binding_key 的最大长度是多少?
255 字节
消费者获取消息的方式?
push(推):客户端与服务器建立连接后,当服务器有消息时,服务器将消息推送到客户端
consumer把轮询过程封装了,并注册到MessageListener监听器中,对于offset进行自动保存。取到消息后,唤醒MessageListener的consumeMessage()来消费
pull(拉):客户端不断轮询请求服务端,来获取新的消息
这种方法非常少用,它在取消息的过程中需要用户自己手动操作。首先在要消费的Topic中拿到MessageQueue的集合,遍历MessageQueue集合,然后针对每一个MessageQueue批量取消息,每取完一次,记录该队列下一次要取的开始offset,直到取完一整个MessageQueue,再换另一个MessageQueue。
消息如何被优先消费?
生产者
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-max-priority",10);
消费者
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.priority(5) // 优先级,默认为5,配合队列的 x-max-priority 属性使用
集群节点类型有几种?
内存节点:保存状态到内存,但持久化的队列和消息还是会保存到磁盘;
磁盘节点:保存状态到内存和磁盘,一个集群中至少需要一个磁盘节点
集群负载均衡算法?
轮询法
加权轮询法:不同的后端服务器的配置可能和当前系统的负载并不相同,因此他们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请求,对于配置低、负载高的集群,给其分配较低的权重,降低其系统负载,按照请求顺序和权重分配到后端。
随机法:通过随机算法,根据后端的服务器的列表大小值来随机选取其中的一台服务器进行访问。随着客户端调用服务端的次数增多,其实实际效果越来越接近与平均分配调用量到后端的每一台服务器,也就是轮询的结果。
加权随机法:与加权轮询法一样,加权随机法也根据后端机器的配置、系统的负载分配不同的权重。不同的是,他按照权重随机请求后端服务器,而非顺序。
源地址哈希法(IP hash):思想是根据获取的客户端IP地址,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客户端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。
最小连接数法:由于后端服务器的配置不尽相同,对于请求的处理有快有慢,他根据后端服务器当前的链接情况,动态地选取其中当前挤压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负载合理地分流到每一台服务器。
RabbitMQ的集群模式有几种?
主备模式
称为 Warren (兔子窝) 模式。实现 rabbitMQ 的高可用集群,一般在并发和数据量不高的情况下,这种模式非常的好用且简单。也就是一个主/备方案,主节点提供读写,备用节点不提供读写。如果主节点挂了,就切换到备用节点,原来的备用节点升级为主节点提供读写服务,当原来的主节点恢复运行后,原来的主节点就变成备用节点,
远程模式
远程模式可以实现双活的一种模式,简称 shovel 模式,所谓的 shovel 就是把消息进行不同数据中心的复制工作,可以跨地域的让两个 MQ 集群互联,远距离通信和复制。
镜像模式
mirror 镜像队列,目的是为了保证 rabbitMQ 数据的高可靠性解决方案,主要就是实现数据的同步,一般来讲是 2 - 3 个节点实现数据同步。对于 100% 数据可靠性解决方案,一般是采用 3 个节点。
多活模式
实现异地集群的都是采用这种双活 或者 多活模型来实现的。这种模式需要依赖 rabbitMQ 的 federation 插件,可以实现持续的,可靠的 AMQP 数据通信,多活模式在实际配置与应用非常的简单。
生产者消息如何运转?
- 生产者连接到 RabbitMO Broker,建立一个连接(Connection),开启一个信道(Channel)。
- 生产者声明一个交换器,并设置相关属性,比如交换机类型、是否持久化等。
- 生产者声明一个队列并设置相关属性,比如是否排他、是否持久化、是否自动删除等。
- 生产者通过路由键将交换器和队列绑定起来。
- 生产者发送消息至 RabbitMO Broker,其中包含路由键、交换器等信息。
- 相应的交换器根据接收到的路由键查找相匹配的队列。
- 如果找到,则将从生产者发送过来的消息存入相应的队列中。
- 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者。
- 关闭信道。
- 关闭连接。
消费者消息如何运转?
- 消费者连接到 RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)。
- 消费者向 RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作
- 等待 RabbitMQ Broker 回应并投递相应队列中的消息,消费者接收消息。
- 消费者确认(ack)接收到的消息。
- RabbitMQ 从队列中删除相应已经被确认的消息。
- 关闭信道。