RabbitMQ那点事(更详细请文集:观看MQ消息队列那点事)

2019-05-27  本文已影响0人  久伴_不离

1.简述RabbitMQ中的几种Exchange的作用。

答:有四种exchange

1)direct exchange:只有当消息的routing key和exchange binding的routing key完全一致的时候,exchange才会将消息发送到binding对应的队列去。

2)topic exchange:当消息的routing key和exchange binding的routing key模糊匹配的时候,exchange才会将消息发送到binding对应的队列去。

3)fanout exchange:和routing key无关,exchange将消息转发给所有binding的队列去。

4)header exchange:和routing key无关,但是需要配置exchange header的匹配参数以及匹配方式(例如any/all等),当匹配了exchange header的参数和方式的时候,exchange将消息转发到binding的队列去。

备注:header中也是key value 的形式的 匹配的时候  根据key value 有相同的发送


2.在RabbitMQ中,一个队列上所有的消费者可以消费同一条消息么?

不可以消费同一条消息,如果要消费同一条消息需要使用exchange配置多个Queue进行绑定,配合使用消息的routing key可以实现群发


3.如果生产环境有问题,需要查询哪里?

答:查看日志/var/log/rabbitmq/XXX  找到对应的报错信息在进行对应的处理

查docker中Rabbitmq的日志在哪?


4.RabbitMQ的集群内同步什么信息?

答:RabbitMQ的集群分为两种,一种是普通模式,一种是镜像模式。默认策略是同步exchange的信息,不同步队列消息,需要增加额外的policy才能同步队列消息。


5.RabbitMQ的queue和ActiveMQ的queue有什么区别?

答:ActiveMQ的queue只允许一个生产者和一个消费者,RabbitMQ的queue允许多个生产者和多个消费者。


6.RabbitMQ的queue和ActiveMQ的topic有什么区别?

答:ActiveMQ的topic会将同一个消息发送给所有订阅的消费者,RabbitMQ的queue只将同一个消息发送给所有订阅的消费者之一。


7.Rabbit如何保证消息的可靠性传输(如何处理消息丢失的问题)?

      7.1生产者弄丢了数据

生产者将数据发送到RabbitMQ的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。

此时可以选择用RabbitMQ提供的事务功能,就是生产者发送数据之前开启RabbitMQ事务channel.txSelect,然后发送消息,如果消息没有成功被RabbitMQ接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit。

// 开启事务

channel.txSelect

try {

    // 这里发送消息

} catch (Exception e) {

    channel.txRollback

 // 这里再次重发这条消息

}

// 提交事务

channel.txCommit

但是问题是,RabbitMQ事务机制(同步)一搞,基本上吞吐量会下来,因为太耗性能

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

事务机制和cnofirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息RabbitMQ接收了之后会异步回调你的一个接口通知你这个消息接收到了。

所以一般在生产者这块避免数据丢失,都是用confirm机制的。

        7.2 RabbitMQ 弄丢了数据

就是 RabbitMQ 自己弄丢了数据,这个你必须开启 RabbitMQ 的持久化,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,可能导致少量数据丢失,但是这个概率较小。

设置持久化有两个步骤:

创建 queue 的时候将其设置为持久化

这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。

第二个是发送消息的时候将消息的 deliveryMode 设置为 2

就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。

必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。

注意,哪怕是你给 RabbitMQ 开启了持久化机制,也有一种可能,就是这个消息写到了 RabbitMQ 中,但是还没来得及持久化到磁盘上,结果不巧,此时 RabbitMQ 挂了,就会导致内存里的一点点数据丢失。

所以,持久化可以跟生产者那边的 confirm 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 ack 了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到 ack,你也是可以自己重发的。

        7.3消费端弄丢了数据

RabbitMQ如果丢失了数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,RabbitMQ认为你都消费了,这数据就丢了。

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


8.RabbitMQ消息基于什么传输?

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


9.消息怎么路由?

消息提供方->路由->一至多个队列

消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。通过队列路由键,可以把队列绑定到交换器上。

消息到达交换器后,RabbitMQ会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则);

交换器分四中其中常用的交换器主要分为以下三种:

fanout:如果交换器收到消息,将会广播到所有绑定的队列上

direct:如果路由键完全匹配,消息就被投递到相应的队列

topic:可以使来自不同源头的消息能够到达同一个队列。 使用topic交换器时,可以使用通配符


10.如何确保消息接收方消费了消息?

接收方消息确认机制:消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。

这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。

下面罗列几种特殊情况:

如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要根据bizId去重)

如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。


11.如何确保消息正确地发送至RabbitMQ?

RabbitMQ使用发送方确认模式,确保消息正确地发送到RabbitMQ。

发送方确认模式:将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack(not acknowledged,未确认)消息。

发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。


12.为什么要引入MQ系统,直接读写数据库不行吗?

其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么?

面试官问你这个问题,期望的一个回答是说,你们公司有个什么业务场景,这个业务场景有个什么技术挑战,如果不用 MQ 可能会很麻烦,但是你现在用了 MQ 之后带给了你很多的好处。

先说一下消息队列常见的使用场景吧,其实场景有很多,但是比较核心的有 3 个:解耦、异步、削峰。

解耦:多系统多进程的数据交换,用pub/sub

异步:把大数据量的同步处理改为异步

削峰:一般的A 系统使用 MySQL,扛到每秒 2k 个请求就差不多了,如果每秒请求到 5k 的话,可能就直接把 MySQL 给打死了,导致系统崩溃,用户也就没法再使用系统了。如果使用 MQ, 每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最 大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉,这又涉及请求排队的问题。


13.如何解决消息队列的延时以及过期失效问题?

其实本质针对的场景,都是说,可能你的消费端出了问题,不消费了;或者消费的速度极其慢,造成消息堆积了,MQ存储快要爆了,甚至开始过期失效删除数据了。

针对这个问题可以有事前、事中、事后三种处理

事前:开发预警程序,监控最大的可堆积消息数,超过就发预警消息(比如短信),不要等出生产事故了再处理。

事中:看看消费端是不是故障停止了,紧急重启。

事后:需要对消费端紧急扩容 ,增加处理消费者进程,如扩充10倍处理,但其实这也有个问题,即数据库的吞吐是有限制的,如果是消费到数据库也是没办法巨量扩容的,所以还是要在吞吐能力支持下老老实实的泄洪消费。

所以事前预防还是最重要的。否则出发删除过期数据。


14.RabbitMQ 上的一个 queue 中存放的 message 是否有数量限制?

答:可以认为是无限制,因为限制取决于机器的内存,但是消息过多会导致处理效率的下降。


15.为什么不应该对所有的 message 都使用持久化机制? 

答:首先,必然导致性能的下降,因为写磁盘比写 RAM 慢的多,message 的吞吐量可能有 10 倍的差距。其次,message 的持久化机制用在 RabbitMQ 的内置 cluster 方案时会出现“坑爹”问题。矛盾点在于,若 message 设置了 persistent 属性,但 queue 未设置 durable 属性,那么当该 queue 的 owner node 出现异常后,在未重建该 queue 前,发往该 queue 的 message 将被 blackholed ;若 message 设置了 persistent 属性,同时 queue 也设置了 durable 属性,那么当 queue 的 owner node 异常且无法重启的情况下,则该 queue 无法在其他 node 上重建,只能等待其 owner node 重启后,才能恢复该 queue 的使用,而在这段时间内发送给该 queue 的 message 将被 blackholed 。所以,是否要对 message 进行持久化,需要综合考虑性能需要,以及可能遇到的问题。若想达到 100,000 条/秒以上的消息吞吐量(单 RabbitMQ 服务器),则要么使用其他的方式来确保 message 的可靠 delivery ,要么使用非常快速的存储系统以支持全持久化(例如使用 SSD)。另外一种处理原则是:仅对关键消息作持久化处理(根据业务重要程度),且应该保证关键消息的量不会导致性能瓶颈。


16.RabbitMQ 中的 broker 是指什么?cluster 又是指什么?

broker 是指一个或多个 erlang node 的逻辑分组,且 node 上运行着 RabbitMQ 应用程序。cluster 是在 broker 的基础之上,增加了 node 之间共享元数据的约束。


17.消息如何分发?

若该队列至少有一个消费者订阅,消息将以循环(round-robin)的方式发送给消费者。每条消息只会分发给一个订阅的消费者(前提是消费者能够正常处理消息并进行确认)。

通过路由可实现多消费的功能


18.如何避免消息重复投递或重复消费?

在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;

在消息消费时,要求消息体中必须要有一个bizId(对于同一业务全局唯一,如支付ID、订单ID、帖子ID等)作为去重的依据,避免同一条消息被重复消费。


19.ActiveMQ与RabbitMQ的区别

1. ActiveMQ/ApolloMQ

  优点:老牌的消息队列,使用Java语言编写。对JMS支持最好,采用多线程并发,资源消耗比较大。如果你的主语言是Java,可以重点考虑。

  缺点:由于历史悠久,历史包袱较多,版本更新很缓慢。集群模式需要依赖Zookeeper实现。最新架构的产品被命名为Apollo,号称下一代ActiveMQ,目前案例较少。

2. RabbitMQ

  优点:生态丰富,使用者众,有很多人在前面踩坑。AMQP协议的领导实现,支持多种场景。淘宝的MySQL集群内部有使用它进行通讯,OpenStack开源云平台的通信组件,最先在金融行业得到运用。

缺点:Erlang代码你Hold得住不? 虽然Erlang是天然集群化的,但RabbitMQ在高可用方面做起来还不是特别得心应手,别相信广告。


20.MQ销峰场景


未完待续。。。。将不定时更新

上一篇下一篇

猜你喜欢

热点阅读