Java面试题消息队列

消息队列 

1、为什么使用消息队列? 

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

2、消息队列有什么优点和缺点? 

优点就是在特殊场景下有其对应的好处,解耦、异步、削峰。 缺点有以下几个: 

  系统可用性降低 
系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调 BCD 三个系统的接口就 好了,人 ABCD 系统好好的,没啥问题,你偏加个 MQ ,万一 MQ 挂了咋 MQ 一挂,整套系统溃的,你不就完了? 

  系统复杂度提高 
硬生生加个 MQ ,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么 保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已 

  一致性问题 
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要 

BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库败了,咋整?你这数据就不一致了。 

3KafkaActiveMQRabbitMQRocketMQ 都有什么区别,以及适合哪些场景? 

 

4、如何保证消息不被重复消费? 

首先,比如 RabbitMQRocketMQKafka,都有可能会出现消息重复消费的问题,正常。因为这问题通常不是 MQ 自己保证的是由我们开发来保证的。 

Kafka 实际上有 offset 的概念,就是每个消息写进去,都有一 offset,代表消息的序号,然后 consumer 消费了数据之后,每隔一段时间(定时定期),会把自己消费过的消息的 offset 提交一下“我已经消费过了,下次我要是重启啥的,你就让我继续从上次消费到的 offset 来继消费”。 

但是凡事总有意外,比如我们之前生产经常遇到的,就是你有时候重启系统,看你怎么重启了,如果碰到点着急的,直接 kill 进程了再重启。这会导致 consumer 有些消息处理了,但是没来得及提交 offset,尴尬了。重之后,少数消息会再次消费一次。 

5、如何保证消息消费的幂等性? 

其实重复消费不可怕,可怕的是你没考虑到重复消费之后,怎么保证幂等性一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性 

幂等性,通俗点说,就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错。 

其实还是得结合业务来思考: 

  比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,

update 一下好吧 

  比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。 

  比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,

里面加一个全局唯一的 id,类似订单 id 的东西,然后你这里消费到了之后,先根据

这个 id 去比 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 

id  Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可 

  比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复

数据插入只会报错,不会导致数据库中出现脏数据。 

6、如何保证消息的可靠性传输?(如何处理消息丢失的题) 

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

的,都有可能。 

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

RabbitMQ 弄丢了 

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

消费端弄丢了数据 
RabbitMQ 如果丢失数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程

挂了,比如重启了,那么就尴尬了,RabbitMQ 认为你都消费了,数据就丢了。 

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

Kafka 

消费端弄丢了数据 
唯一可能导致消费者弄丢数据的情况,就是说,你消费到了这个消息,然后消费者那边自动提交了 offset Kafka 以为你已经消费好了这个消息,但其实你才刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。 

Kafka 弄丢了数 
Kafka  leader 宕机了,将 follower 切换为 leader 之后就会发现说这个数据就丢了。 

所以此时一般是要求起码设置如下 4 个参数 

   topic  replication.factor 参数:这值必须大于 1,要求 partition 必须有至

 2 个副本 

   Kafka 服务端设置  min.insync.replicas 参数:这个值必须大 1,这个是要求一个 

leader 至少感知到至少一个 follower 跟自己保持联系,没掉队,这样才能确 

leader 挂了还有一 follower  

   producer 端设 acks=all:这个是要每条数据,必须是写入所有 replica 之后,才

能认为是写成功了。 

 

 producer 端设 retries=MAX(很大很大很大的一个值,无限次重试的意思):这个

是要求一旦写入失败,就无限重试,卡在这里了。 

7、如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路 

  首先这个 mq 得支持伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量, 那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就一部分数据。如果现在资源不够了,简单 啊,给 topic  partition,然后做数据移,增加机器,不就可以存放更多数据,提 供更高的吞吐量了? 

  其次你得考虑一下这个 mq 的数据要不要落磁盘吧?那肯定要了,落磁盘才能保证别进 程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址 开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。 

  其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解  kafka 的高可用保障制。多副 -> leader & follower -> broker 挂了重新选举  leader 即可对外服 

8、如何解决消息队列的延时以及过期失效问题 

大量消息mq里积了几个小时了还没解决 
一个消费者一秒1000条,一3个消费3000条,一分钟18万条,1000多万条,所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大1小时的时间才能恢复过来。 

一般这个时候,只能操作临时紧急扩容了,具体操作步骤和思路如下 
  先修consumer题,确保其恢复消费速度,然后将现cnosumer都停掉   新建一topicpartition是原来10临时建立好原1020queue 数量。 

  然后写一个临时的分发数据consumer,这个程序部署上去消费积压的数据,消费 之后不做耗时的处理,直接均匀轮询写入临时建立好10倍数量queue 

 

接着临时征10机器来部consumer,每一consumer费一个临queue

的数据。 

这种做法相当于是临时queue资源consumer资源扩10,以正常10倍速度来消费数据。等快速消费完积压数据之后,得恢复原先部署架构,重新用原先consumer机器来消费消息。 

消息队列过期失效问题 
假设你用的rabbitmqrabbitmq是可以设置过期时间的,就TTL,如果消息在queue中积压超过定的时间就会rabbitmq给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压mq里,而是大量的数据会直接搞丢。 

这个情况下,就不是说要增consumer积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是批量重导, 
这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌mq里面去,把白天丢的数据给他补回来。也只能是这样了 

9、消息队列满了以后该怎么处理? 

如果走的方式是消息积压mq里,那么如果你很长时间都没处理掉,此时导mq都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。 

10、消息如何分发 

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

11、消息怎么路由 

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

常用的交换器主要分为一下三种: 

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

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

 

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

用通配符 

12、消息基于什么传 

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

13、为什么不应该对 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)。另外一处理原则是:仅对关键消息作持久化处理(根据业务重要程度),且应该保证关键消息的量不会导致性能瓶颈。 

14、如何保证高可用RabbitMQ 的集 

RabbitMQ 是比较有表性的,因为是基于主从(非分布式)做高可用性的,我们就 RabbitMQ 为例子讲第一种 MQ 的高可性怎么实现。RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式。 

单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿?,没人生产用单机模式  普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例每个机器启动一个。你创建的 queue会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 数据(元数据可以认为是 queue 的一些配置信,通过元数据,可以找 queue 所在实例。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会 queue 所在实例拉取数据过来。这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某 queue 读写操作。 镜像集群模式:这种模式,才是所谓的 RabbitMQ 的高可用模式跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存于多个实例上,就是说,每个 RabbitMQ 都有这个 queue 个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自把消息同步到多个实例 queue 上。RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个略,就会自动将数据同步到其他的节点上去了。

这样的话,好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这 queue 的完整数据别的 consumer 以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!RabbitMQ 一个 queue 的数据都是放在一节点里的,镜像集群下,也是每个节点都放这 queue 的完整数据 

15RabbitMQ 作模式 

  simple模式(即最单的收发模式) 

  work工作模式(资源) 

  publish/subscribe布订(共享资源) 

  routing路由模 

  topic主题模式 

16、为什么需要消息MySQL不能满足需求吗? 

  解耦: 
允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束 

  冗余: 
消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 

  扩展性: 
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。 

  灵活性 & 峰值处理力: 

在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃 

  可恢复性: 
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理 

  顺序保证: 
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 的消息的有序性) 

  缓冲: 
有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。 

  异步通信: 
很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老歪不歪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值