rabbitMQ面试题

rabbitMQ面试题

1.什么是MQ?

1.MQ全称Message Queue,是在消息的传输过程中保存消息的容器。多用于分布式系统之间的通信

2.MQ的优劣势?

优势:

  • 应用解耦:系统耦合性越高,容错性就越低,同时也会影响系统的可维护性
  • 异步提速:提升用户体验和系统吞吐量,用户可以很快得到响应结果
  • 削峰填谷:使用MQ后,可以限制消费的速度为1000,这样,高峰期产生的数据必然会积压在MQ中,高峰就被消掉了,而消费速度还是维持在1000,高峰期过后就可以慢慢消费消息,这叫做填谷

劣势:会带来一系列其他问题

  • 系统可用性降低:系统引入的外部依赖越多,系统稳定性越差,一旦MQ宕机,就会对业务造成影响。无法保证高可用性
  • 系统复杂度提高:MQ引入大大增加了系统复杂性,以前是系统之间的同步远程调用,现在是MQ异步调用,如何保证消息不被重复消费,消息丢失的处理,消息传递的正确性都是需要处理的问题。
  • 一致性问题:A系统处理完业务,通过MQ给BCD三个系统发消息数据,如果BC处理成功,D处理失败,如何保证数据处理的一致性问题

3.RabbitMQ的相关概念?

  • 1.Broker: 接受和分发消息的应用,RabbitMQ的server就是 Message Broker
  • 2.Virtual Host: 处于多租户和安全设计,把AMQP的基本组件分到一个虚拟组中,类似网络中namespace。当多个不同的用户使用相同的一个RabbitMQ server提供服务时,

可以划分多个vhost,每个用户在自己的vhost上创建exchange/queue

  • 3.Connection:publisher/consumer和broker之间的tcp连接
  • 4.Channel: 如果每次访问MQ都建立一个Connection,在大量消息的时候将会花费巨大的开销。效率低,Channel是在connecntion内部建立的逻辑连接,

如果应用程序支持多线程,通常每个thread建立单独的channel通讯,AMQP method包含了channel id帮助客户端和message broker识别了channel,所以channel之间是完全隔离的。

Channel作为轻量级的connection极大减少了操作系统建立TCP连接的开销

  • 5.exchange: message到达broker的第一站,根据发布规则,匹配查询表的roouting key,发布消息到queue中
  • 6. queue:消息最终会被发送到这里等待consumer取走
  • 7.binding:exchange和queue之间的虚拟连接,binding中可以包含routingkey。binding信息被保存到exchange中的查询表,用于消息的分发凭证

4.Rabbit的工作模式?

1.RabbitMQ提供了6种工作模式:简单模式,work queues,pub/sub发布订阅模式,routing路由模式,topics主题模式,rpc远程调用模式。

work queues:

  • 与简单模式相比,多了一个或一些消费端,多个消费端共同消费一个队列中的消息
  • 应用场景:对于任务过重或者任务较多的场景可以使用工作队列提高任务的处理速度
  • 特点:一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争关系

pub/sub发布订阅模式:

  • 1.订阅模式中,多了exchange角色,而且过程略变化

  P:生产者,也就是发送消息的程序,但是不再发送到队列而是发送给交换机
  C:消费者
  Queue:队列
exchange: 交换机,一方面接受生产者发送的消息,另一方面,知道如何处理消息,例如交给某个特别队列,递交给所有队列,或者将消息抛弃,如何操作取决于交换机的类型

  2.交换机的类型:
  a.fanout:广播,把消息交给绑定的交换机队列
  b.direct: 定向,把消息交给符合指定routingKey的队列
  c. Topic:通配符:把消息交给符合routingkey pattern的队列

  3.exchange只负责转发消息,不负责存储消息的能力,因此如果没有任何队列与交换机绑定,或者没有符合路由规则的队列,那么消息就会丢失

routing路由模式:

  • 队列与交换机绑定,不再是任意绑定,而是与一个指定的routingKey绑定
  • 消息的发送方在向exchange发送消息的时候,必须指定消息的routingkey
  • exchange 不再把消息发送给每一个绑定的队列,而是根据接收到的routingkey进行判断,只有队列的routingKey与消息的routingKey 一致才会接受队列

topics主题模式:

  • Topic主题模式可以实现与订阅模式,routingkey模式一样的功能,只是在配置routingKey的时候可以使用通配符,显得更加灵活

4.什么是JMS?

  1. JMS 即java消息服务应用程序接口,类似jdbc,是一个java平台关于面向消息中间件的API
  2. JMS 是javaEE的规范
  3. 很多消息中间件都实现了JMS规范,但是RabbitMQ官方没有实现,社区有

5 RabbitMQ的可靠性投递

1.消息的可靠性投递:
在使用RabbitMQ的时候,作为消息的发送方,希望杜绝任何消息发送失败或者投递失败的场景。RabbitMQ提供了两种方式来控制消息的投递可靠性模式
a. confirm模式
b. return 模式

2.rabbitMQ 整个消息投递路径是:
producer->broker-> exvhange-> queue->consumer
-- 消息从producer到exchange则会返回一个confirmCallback
-- 消息从exchange到queue会返回一个returnCallBack
我们利用这两个callBack控制消息的可靠性投递

2.设置 ConnectionFactory的 publisher-confirms= true开启,然后重写回调方法,在方法中判断ack,true表示成功发送,同同理开启return 模式

 

6.Consumer Ack机制?

1.Ack指的是Acknowledge,确认,代表消费端的确认方式,有三种:

  • a. 自动确认:acknowledge="none"

-- 指的是,消息一旦被consunmer接受,则自动确认收到,并将message融rabbitMQ移除。但是实际业务中,很可能消息收到,但是业务处理异常,消息就会丢失

  • b. 手动确认: acknowledge="manual"

-- 设置手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,出现异常则调用channel.basicNack(),让其重新发送

  • c.根据异常确认: acknowledge="auto"(不推荐)

7 RabbitMQ 消费端限流

  • 1.在rabbit:listener-container 中配置prefetch,消费端一次拉取多少消息
  • 2.消费端的确认模式一定为手动模式

8.什么是TTL?

1. TTL 全称 Time to live(过期时间)

2.当消息达到存活时间后,还没被消费就会被自动清除

3. RabbitMQ可以对消息设置过期时间,也可以对整个队列设置过期时间

4.设置TTL使用参数:x-message-ttl,单位毫秒,对整个队列有效

5.设置消息ttl:使用参数expiration,当消息在队列头时,hi单独判断是否失效

6.如果两个都设置则以短的为准

 

9.什么是死信队列?

  1. 死信队列:英文简写:DLX。 dead letter Exchange,当消息称为Dead Message后,可以被重新发送到另外一个交换机,这个交换机就是DLX。
  2. 消息成为死信的三种情况:

a. 队列消息长度达到长度限制:
b. 消费者拒绝接受消费信息,basicReject 并且不把消息重新放回原队列,requeue= false
c.原队列存在消息过期设置,消息到达超时时间未被消费

3.队列绑定死信交换机:

给队列设置参数:x-dead-letter-exchange 和x-dead-letter-routing-key

10.RabbitMQ如何实现延迟对列?

1.什么是延迟队列?

- 即消息进入队列之后不会被立即消费,而是到达指定时间后才会被消费

2.需求:
a. 下单后,30分钟未支付,取消订单,回滚库存。
b. 新用户注册成功后7天,发送短信问候
实现方式:定时器,延迟队列(更加优雅)

3.使用TTL+死信队列实现延迟队列的效果

 

11.消息的可靠性保障?

1.消息补偿机制:可以将消息持久化到数据库

 

12.消息的幂等性保障?

1.幂等:在MQ中,消费多条消息,得到与消费该消息一次相同的结果

2.消息的幂等性保障
a.乐观锁机制


场景1:消费者干的事是拿一条数据往数据库写一条,如果消息重复两次就写了两条,导致数据出错。

解决场景1:同一条消息消息到第二次时判断一下是否已消费过,若是则直接扔掉,一条数据出现两次但是数据库只有一条,这就保证了系统的幂等性

方法1:唯一ID + 指纹码机制,利用数据库主键去重

思路:根据消息生成一个全局唯一ID,然后加上一个指纹码。指纹码可以系统生成也可以根据某些规则自定义拼接,目的是确定本次才做唯一,将ID+指纹码作为拼接好的值作为主键就可以去重了,在消费消息前先去数据库查看这条消息指纹码是否存在,没有就插入有就忽视。

高并发写数据库性能瓶颈:可以跟进ID进行分库分表策略,采用一些路由算法进行分流,要保证ID通过这种算法消息即使投递多次都落在同一数据库分片上,这样就由单台数据库幂等变成多库的幂等。

方法1:利用Redis的原子性去实现

redis是单线程的,但是性能好也有很多原子性的命令,比如setnx命令,在接收到消息后将消息ID作为key去执行setnx命令,如果执行成功则表示没有执行过这条消息,可以进行消费(setnx命令特点:当且仅当key不存在,将key值设为value值;若key已存在该命令不做任何操作)

方法3:使用全局ID区分消息,解决幂等性(常用)

对于方法3还是以生产者和消费者代码举例:

生产者:在请求头设置消息id(messageId),可以用随机ID比如UUID.randomUUID(),也可以用业务逻辑唯一ID

 

13.消息怎么路由?

消息提供方->路由->一至多个队列消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。通过队列路由键,可以把队列绑定到交换器上。消息到达交换器后,
RabbitMQ 会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则);
常用的交换器主要分为一下三种:

  • 1 fanout:如果交换器收到消息,将会广播到所有绑定的队列上
  • 2.direct:如果路由键完全匹配,消息就被投递到相应的队列
  • 3.topic:可以使来自不同源头的消息能够到达同一个队列。 使用 topic 交换器时,可以使用通配符

14.消息基于什么传输?

  由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ使用信道的方式来传输数据。信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的
信道数量没有限制。

15.如何保证RabbitMQ消息的可靠传输?

消息不可靠的情况可能是消息丢失,劫持等原因;
丢失又分为:生产者丢失消息、消息列表丢失消息、消费者丢失消息;
1. 生产者丢失消息:从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息;

  • transaction机制就是说:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务

(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;

  • confirm模式用的居多:一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;

rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;
如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
2. 消息队列丢数据:消息持久化。
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。
这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。
这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。
那么如何持久化呢?
这里顺便说一下吧,其实也很容易,就下面两步
1.将queue的持久化标识durable设置为true,则代表是一个持久的队列
2.发送消息的时候将deliveryMode=2
这样设置以后,即使rabbitMQ挂了,重启后也能恢复数据
3.消费者丢失消息:消费者丢数据一般是因为采用了自动确认消息模式,改为手动确认消息即可!
消费者在收到消息之后,处理消息之前,会自动回复RabbitMQ已收到消息;
如果这时处理消息失败,就会丢失该消息;
解决方案:处理消息成功后,手动回复确认消息。

 

16.如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,怎么办?

  • 消息积压处理办法:临时紧急扩容:

先修复 consumer 的问题,确保其恢复消费速度,然后将现有 cnosumer 都停掉。
新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不
做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。
这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费
数据。
等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消
息。

  • MQ中消息失效:假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL。如

果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是
第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。我们可以采取
一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当
时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户
都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,
然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。假设 1 万个订单积压
在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出
来,手动发到 mq 里去再补一次。

  • mq消息队列块满了:如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满

了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数
据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上
再补数据吧。

17.mq保证消息的消费顺序

首先保证消息生产的顺序性要满足两个条件:

  1.单一生产者:消息生产的顺序只支持单一生产者,不同的生产者分布在不同的系统中,即使设置了相同的消息分组,不同生产者之间的消息也无法判定其先后顺序。

  2.串行发送:如果生产者使用多线程并行发送消息,则不同的线程之间发送的消息无法判定其顺序。

如需保证消息的顺序性,则需要满足以下几个条件:

  1.投递顺序:业务端消费的时候要严格按照接受->处理->应答的语义来处理消息,避免因为异步处理导致消息乱序。

  2.有限重试:像RocketMQ的顺序消息投递只在重试次数范围内,如果一条消息一直重试失败,超过重试次数之后,就不会重试了,而是跳过这个消息,导致乱序。

所以对于严格要求保证消息顺序的场景,需要合理的设置重试次数。

最后建议:

  1.串行消费,避免批量消费导致乱序

  2.消息组尽量打散,避免消息集中

  1.拆分成多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式取消费。

  2.或者就一个queue但是对应一个consumer,然后这个consumer内部用阻塞队列做排队,然后分发给底层不同的worker来处理

 

posted @ 2023-03-23 16:32  杨阳洋^_^!  阅读(178)  评论(0编辑  收藏  举报