Java常见面试题(五、RabbitMQ)

五、RabbitMQ

1.rabbitmq 的使用场景有哪些?

  • 异步处理
    比如发短信和发送邮件,就可以先把信息存入数据库,然后写入消息队列。通过消费消息去发送短信和发送邮件。
  • 应用解耦
    订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
    库存系统:订阅下单的消息,获取下单消息,进行库操作。

2.rabbitmq 有哪些重要的角色?

生产者:消息的创建者,负责创建和推送数据到消息服务器
消费者:消息的接收方,用于处理数据和确认消息
代理:就是RabbitMQ本身,用于扮演快递的角色,本身并不生产消息

3.rabbitmq 有哪些重要的组件?

rabbitmq组件.png

Server:又称为broker,接受客户端连接,RabbitMQ 节点;
Connection: 连接,应用程序与brokder建立网络连接;
channel:网络通道,几乎所有的操作都是在channel中进行的,是进行消息对象的通道,客户端可以建立 多个通道,每一个channel表示一个会话任务
Virtual Host:虚拟机,一个节点由若干个虚拟机组成;
Exchange:交换机,一个虚拟机由若干个交换机组成;
Queue:消息队列,和交换机通过routing key绑定

4.rabbitmq 中 vhost 的作用是什么?

vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、绑定、交换器和权限控制;
vhost通过在各个实例间提供逻辑上分离,允许你为不同应用程序安全保密地运行数据;
vhost是AMQP概念的基础,必须在连接时进行指定,RabbitMQ包含了默认vhost:“/”;
当在RabbitMQ中创建一个用户时,用户通常会被指派给至少一个vhost,并且只能访问被指派vhost内的队列、交换器和绑定,vhost之间是绝对隔离的。
vhost可以理解为虚拟broker,即mini-RabbitMQ server,其内部均含有独立的queue、bind、exchange等,最重要的是拥有独立的权限系统,可以做到vhost范围内的用户控制。当然,从RabbitMQ全局角度,vhost可以作为不同权限隔离的手段(一个典型的例子,不同的应用可以跑在不同的vhost中)。

5.rabbitmq 的消息是怎么发送的?

生产者把生产的消息通过channel发送到Exchange上,Exchange通过绑定的router key来选择Queue,消费者监听到Queue上有新的消息,就消费调此消息;

6.rabbitmq 怎么避免消息丢失?

  • 消息持久化
    RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外crash掉,消息就会丢失。
    所以就要对消息进行持久化处理。如何持久化,下面具体说明下:
    要想做到消息持久化,必须满足以下三个条件,缺一不可。
    1) Exchange 设置持久化
    2)Queue 设置持久化
    3)Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息
  • ACK确认机制
    多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,超时了或者消费被停机或者网络断开链接),如何保证消息不丢?
    这个使用就要使用Message acknowledgment 机制,就是消费端消费完成要通知服务端,服务端才把消息从内存删除。
    这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。
  • 设置集群镜像模式
    1)单节点模式:最简单的情况,非集群模式,节点挂了,消息就不能用了。业务可能瘫痪,只能等待。
    2)普通模式:默认的集群模式,某个节点挂了,该节点上的消息不能用,有影响的业务瘫痪,只能等待节点恢复重启可用(必须持久化消息情况下)。
    3)镜像模式:把需要的队列做成镜像队列,存在于多个节点,属于RabbitMQ的HA方案
    -消息补偿机制
    消息补偿机制需要建立在消息要写入DB日志,发送日志,接受日志,两者的状态必须记录。
    然后根据DB日志记录check 消息发送消费是否成功,不成功,进行消息补偿措施,重新发送消息处理。

7.要保证消息持久化成功的条件有哪些?

声明队列必须设置持久化 durable 设置为 true.
消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久)。
消息已经到达持久化交换器。
消息已经到达持久化队列。

8.rabbitmq 持久化有什么缺点?

持久化的缺地就是降低了服务器的吞吐量,因为使用的是磁盘而非内存存储,从而降低了吞吐量。

9.rabbitmq 有几种广播类型?

direct(默认方式):最基础最简单的模式,发送方把消息发送给订阅方,如果有多个订阅者,默认采取轮询的方式进行消息发送。
headers:与 direct 类似,只是性能很差,此类型几乎用不到。
fanout:分发模式,把消费分发给所有订阅者。
topic:匹配订阅模式,使用正则匹配到消息队列,能匹配到的都能接收到。

10.rabbitmq 怎么实现延迟消息队列?

消息的TTL(Time To Live)
消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。
Dead Letter Exchanges
Exchage的概念在这里就不在赘述,一个消息在满足如下条件下,会进死信路由,记住这里是路由而不是队列,一个路由可以对应很多队列。

  1. 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。
  2. 上面的消息的TTL到了,消息过期了。
  3. 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。
    Dead Letter Exchange其实就是一种普通的exchange,和创建其他exchange没有两样。只是在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。
    实现延迟队列
    延迟任务通过消息的TTL和Dead Letter Exchange来实现。我们需要建立2个队列,一个用于发送消息,一个用于消息过期后的转发目标队列。
    实现延迟队列
    延迟任务通过消息的TTL和Dead Letter Exchange来实现。我们需要建立2个队列,一个用于发送消息,一个用于消息过期后的转发目标队列。
    生产者输出消息到Queue1,并且这个消息是设置有有效时间的,比如60s。消息会在Queue1中等待60s,如果没有消费者收掉的话,它就是被转发到Queue2,Queue2有消费者,收到,处理延迟任务。

11.如何保证消息消费时的幂等性?

实际上我们只要保证多条相同的数据过来的时候只处理一条或者说多条处理和处理一条造成的结果相同即可,但是具体怎么做要根据业务需求来定,例如入库消息,先查一下消息是否已经入库啊或者说搞个唯一约束啊什么的,还有一些是天生保证幂等性就根本不用去管,例如redis就是天然幂等性。
还有一个问题,消费者消费消息的时候在某些场景下要放过消费不了的消息,遇到消费不了的消息通过日志记录一下或者搞个什么措施以后再来处理,但是一定要放过消息,因为在某些场景下例如spring-rabbitmq的默认回馈策略是出现异常就没有提交ack,导致了一直在重发那条消费异常的消息,而且一直还消费不了。

12.如何保证消息的可靠性传输?

生产者弄丢了数据

生产者发送数据之前开启RabbitMQ事务(channel.txSelect),然后发送消息,如果消息没有成功被RabbitMQ接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit)。但是问题是,RabbitMQ事务机制一搞,基本上吞吐量会下来,因为太耗性能。

所以一般来说,如果你要确保说写RabbitMQ的消息别丢,可以开启confirm模式,在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了RabbitMQ中,RabbitMQ会给你回传一个ack消息,告诉你说这个消息ok了。如果RabbitMQ没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。

事务机制和cnofirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息RabbitMQ接收了之后会异步回调你一个接口通知你这个消息接收到了。
所以一般在生产者这块避免数据丢失,都是用confirm机制的。

RabbitMQ弄丢了数据

开启RabbitMQ的持久化功能。
设置持久化有两个步骤,第一个是创建queue的时候将其设置为持久化的,这样就可以保证RabbitMQ持久化queue的元数据,但是不会持久化queue里的数据;第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时RabbitMQ就会将消息持久化到磁盘上去。必须要同时设置这两个持久化才行,RabbitMQ哪怕是挂了,再次重启,也会从磁盘上重启恢复queue,恢复这个queue里的数据。

消费端弄丢了数据

这个时候得用RabbitMQ提供的ack机制,简单来说,就是你关闭RabbitMQ自动ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候,再程序里ack一把。这样的话,如果你还没处理完,不就没有ack?那RabbitMQ就认为你还没处理完,这个时候RabbitMQ会把这个消费分配给别的consumer去处理,消息是不会丢的。

13.如何保证消息的顺序性

拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理。

你可能感兴趣的:(Java常见面试题(五、RabbitMQ))