Java进阶之消息队列常见问题

Q1. 为什么要用消息队列?(消息队列的应用场景?)

A:首先消息队列是一种“先进先出”的数据结构,其次使用消息队列的主要作用有:解耦、异步、削峰,接下来对上面三点作简要解释
消息队列简图
解耦前:现今互联网软件的架构设计已经不单单局限于传统和老旧的单体以及垂直架构设计模式了,SOA及分布式的架构设计越来越多的被各个大中小型企业所应用,服务之间不管是RPC调用还是RESTFUL的调用已经成为一种常态,A服务模块需要调用B、C等更多的模块来完成自己的业务需求,模块之间的耦合度越高、容错性便越低,被调用模块有一个失败,便会导致整个业务的失败,影响用户的使用和商家的销售,这是我们所不能接受的
解耦后:使用MQ消息队列后,模块之间的耦合度降低,容错性提高,假如某个被调用模块发生异常后,不会影响整体的业务,只需要服务修复后去队列中消费即可,大大提高用户体验

异步前:A服务自己处理自身业务花费20ms,调用B、C、D服务各花费200ms、300ms、400ms,完成整个业务调用共花费20+200+300+400=920ms,这是我们所不能容忍的
异步后:A服务自己处理自身业务花费20ms,发消息给MQ花费5ms,总耗时20+5=25ms,时间大大的缩短,提高响应速度,提升用户体验

削峰前:假如有一个秒杀的业务,在秒杀活动开始时,用户量猛增,大量的请求过来直接压垮系统,导致整个服务崩溃
削峰后:使用MQ消息队列,将大量的消息缓存起来,分散到一段相对较长的时间来处理,不会因为瞬间的请求压垮系统,保证了系统的可用性和用户体验

Q2. 各种消息队列产品的比较?

A:废话不多说,先上图
常用MQ产品对比
上图对市面上常见的ActiveMQ、RabbitMQ、RocketMQ、Kafka四款产品做了对比,主要从其开发语言、单机的吞吐量、时效性、可用性以及功能特性做了对比
基于上图给出个人的简单总结:
ActiveMQ:早期的一款MQ产品,没经过大规模吞吐量场景的验证,社区也不是很活跃,但是现在确实大家用的不多了,不推荐
RabbitMQ:开发语言 erlang 阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是RabbitMQ是开源的,比较稳定的支持,活跃度也高,如不考虑二次开发,追求性能和稳定性,推荐使用
RocketMQ:开发语言是Java,在阿里内部经受过高并发业务的考验,稳定性和性能均不错,考虑后期可能二次开发,推荐使用
Kafka:大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,社区活跃度很高,推荐使用。大数据领域日志采集等业务推荐使用

Q3. 消息队列的优点和缺点?

A:其实使用MQ的优点,在我们的问题一里面已经给出,就是解耦、异步、削峰,但是技术是把双刃剑,MQ也不例外,在解决了部分问题后也会带来其他的问题,下面就对使用MQ的缺点做简单陈述:

  • 系统的可用性降低:MQ是一个中间件产品,我们引入外部的中间件,势必就需要去花费额外的精力去维护它,假如MQ中间件宕机,则会导致整个系统的不可用(引出问题:如何保证消息队列的高可用,下面会解答)
  • 系统的复杂度提高:以前没有中间件时,系统之间是同步的远程调用,加入MQ之后,成了异步调用(引入问题:如何保证消息的不丢失、如何保证消息不被重复消费、如何保证消息的顺序性)
  • 一致性问题:A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,如果B系统、C系统处理成功,D系统处理失败(引入问题:如何保证消息数据处理的一致性)

Q4. 如何保证消息队列的高可用?

A:首先两个字:“集群”,其次针对不同的消息队列产品,其实现集群的方式也不尽相同
RabbitMQ基于主从模式实现高可用。RabbitMQ有三种模式:单机模式,普通集群模式,镜像集群模式。

  • 单机模式:
    单机模式就是demo级别的,生产中不会有人使用。
  • 普通集群模式
    普通集群模式就是在多台机器上启动多个rabbitmq实例,每个机器启动一个。但是创建的queue只会放在一个rabbitmq实例上面,但是其他的实例都同步了这个queue的元数据。在你消费的时候,如果连接到了另一个实例,他会从拥有queue的那个实例获取消息然后再返回给你
    RabbitMQ普通集群模式
  1. 在多台机器上分别启动RabbitMQ实例
  2. 多个实例之间可以相互通信
  3. 创建的Queue只会放在一个RabbitMQ上,其他实例都同步元数据
  4. 消费的时候,如果连接的没有Queue,那么当前实例会从queue所在实例拉取数据

特点:
没有真正做到高可用
有数据拉取的开销和单实例的瓶颈问题

一句话:这种模式并没有提供高可用,这种方案只是提高了吞吐量,也就是让集群中多个节点来服务某个queue的读写操作

  • 镜像集群模式
    这种模式,才是RabbitMQ提供是真正的高可用模式,跟普通集群不一样的是,你创建的queue,无论元数据还是queue里面是消息数据都存在多个实例当中,然后每次写消息到queue的时候,都会自动把消息到多个queue里进行消息同步
    RabbitMQ镜像集群
  1. 在多台机器上分别启动RabbitMQ实例
  2. 多个实例之间可以相互通信
  3. 每次生产者写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。每个RabbitMQ节点上都有Queue的消息数据和元数据
  4. 某一个节点宕机,其他节点依然保存完整数据,不影响客户端消费

这种模式的好处在于,任何一台机器宕机了,其他的机器还可以使用。
坏处在于:1、性能消耗太大,所有机器都要进行消息的同步,导致网络压力和消耗很大。2、没有扩展性可言,如果有一个queue负载很重,就算加了机器,新增的机器还是包含了这个queue的所有数据,并没有办法扩展queue。
如何开启镜像集群模式:在控制台新增一个镜像集群模式的策略,指定的时候可以要求数据同步到所有节点,也可以要求同步到指定节点,然后在创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上面去了

RocketMQ高可用-双主双从
RocketMQ高可用-双主双从

  1. 生产者通过Name Server发现Broker
  2. 生产者发送队列消息到2个Broker主节点
  3. Broker主节点分别和各自从节点同步数据
  4. 消费者从主或者从节点订阅消息

Kafka高可用
Kafka高可用
kafka一个最基本的架构认识:多个broker组成,每个broker是一个节点;你创建一个topic,这个topic可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition就放一部分数据。

这就是天然的分布式消息队列,就是说一个topic的数据,是分散放在多个机器上的,每个机器就放一部分数据。

实际上rabbitmq之类的,并不是分布式消息队列,他就是传统的消息队列,只不过提供了一些集群、HA的机制而已,因为无论怎么玩儿,rabbitmq一个queue的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个queue的完整数据。

kafka 0.8以前,是没有HA机制的,就是任何一个broker宕机了,那个broker上的partition就废了,没法写也没法读,没有什么高可用性可言。

kafka 0.8以后,提供了HA机制,就是replica副本机制。每个partition的数据都会同步到吉他机器上,形成自己的多个replica副本。然后所有replica会选举一个leader出来,那么生产和消费都跟这个leader打交道,然后其他replica就是follower。写的时候,leader会负责把数据同步到所有follower上去,读的时候就直接读leader上数据即可。只能读写leader?很简单,要是你可以随意读写每个follower,那么就要care数据一致性的问题,系统复杂度太高,很容易出问题。kafka会均匀的将一个partition的所有replica分布在不同的机器上,这样才可以提高容错性。

这么搞,就有所谓的高可用性了,因为如果某个broker宕机了,没事儿,那个broker上面的partition在其他机器上都有副本的,如果这上面有某个partition的leader,那么此时会重新选举一个新的leader出来,大家继续读写那个新的leader即可。这就有所谓的高可用性了。

写数据的时候,生产者就写leader,然后leader将数据落地写本地磁盘,接着其他follower自己主动从leader来pull数据。一旦所有follower同步好数据了,就会发送ack给leader,leader收到所有follower的ack之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)

消费的时候,只会从leader去读,但是只有一个消息已经被所有follower都同步成功返回ack的时候,这个消息才会被消费者读到。

实际上这块机制,讲深了,是可以非常之深入的,但是我还是回到我们这个课程的主题和定位,聚焦面试,至少你听到这里大致明白了kafka是如何保证高可用机制的了,对吧?不至于一无所知,现场还能给面试官画画图。要遇上面试官确实是kafka高手,深挖了问,那你只能说不好意思,太深入的你没研究过。

但是大家一定要明白,这个事情是要权衡的,你现在是要快速突击常见面试题体系,而不是要深入学习kafka,要深入学习kafka,你是没那么多时间的。你只能确保,你之前也许压根儿不知道这块,但是现在你知道了,面试被问到,你大概可以说一说。然后很多其他的候选人,也许还不如你,没看过这个,被问到了压根儿答不出来,相比之下,你还能说点出来,大概就是这个意思了。

Q5. 如何保证消息不丢失?

A:首先我们一起分析消息丢失的可能发生的情况
MQ消息丢失
1、在生产者向MQ队列发消息的时候,可能因为网络抖动等原因发生消息的丢失
2、MQ接收到生产者发送的消息,还未来得及进行持久化存储,断电等原因导致消息的丢失
3、消息的消费者从MQ拿到消息后,还未还得及对消息进行处理,消费者宕机造成消息的丢失
分析清楚可能引起消息丢失的原因,我们就可以针对不同阶段采取不同的策略来防止消息的丢失
保证消息不丢失
1、在生产者向MQ发送消息后,MQ需要进行confirm确认
2、MQ收到消息后及时进行消息持久化
3、消费者收到消息处理完毕后手动进行ack确认
4、MQ收到消费者ack确认后删除持久化的消息

Q6. 如何保证消息不被重复消费?(如何保证消息的幂等性?)

A:首先需要说明的是重复消息是不可避免的,因为消息重复的根本原因是网络的不可达,既然重复消息无法避免,我们要做的就是如何保证重复消息不被重复消费,即消息的幂等性

  • 发送时消息重复
    当一条消息已被成功发送到服务端,此时出现了网络闪断,导致服务端对客户端应答失败。 如果此时生产者意识到消息发送失败并尝试再次发送消息,消费者后续会收到两条内容相同的消息
    生产者重复发送消息
  • 消费时消息重复
    消息消费的场景下,消息已投递到消费者并完成业务处理,当消费方给MQ服务端反馈应答的时候网络闪断。 为了保证消息至少被消费一次,MQ服务端将在网络恢复后再次尝试投递之前已被消费方处理过的消息,此时消费者就会收到两条内容相同的消息
    消费者接收重复消息
    解决方案:
  1. 消息发送者发送消息时携带一个全局唯一的消息id
  2. 消费者获取消费后先根据id在redis/db中查询是否存在消费记录
  3. 如果没有消费过就正常消费,消费完毕后写入redis/db
  4. 如果消息消费过就直接舍弃或者Update

当然、这里只是提出一个基本的解决思路,针对不同的MQ产品有不同的解决方案,但是总体思路不会偏离上文,大家可以在网上找更多更详细的解决方案,这里就不在细说

Q7. 如何保证消息的顺序性?

A:消息有序指的是可以按照消息的发送顺序来消费。
例如:一笔订单产生了 3 条消息,分别是订单创建、订单付款、订单完成。消费时,要按照顺序依次消费才有意义。与此同时多笔订单之间又是可以并行消费的。

局部顺序消费
MQ局部顺序消费

  1. 生产者根据消息ID将同一组消息发送到一个Queue中
  2. 多个消费者同时获取Queue中的消息进行消费
  3. MQ使用分段锁保证单个Queue中的有序消费

Q8. 大量消息堆积处理怎么处理?

A:首先大量消息堆积可能的原因有

消费方出现了故障导致消息没有正常消费:

  1. 网络故障
  2. 消费方处理消息后没有给MQ Server正常应答
    消息堆积

处理方案:
消息堆积处理方案
3. 检查并修复消费方的正常消费速度
4. 将堆积消息转存到容量更大的MQ集群
5. 增加多个消费者节点并行消费堆积消息
6. 消费完毕后,恢复原始架构

Q9. 消息过期怎么处理

A:过期消息产生的原因?

  • 消息设置了过期时间
  • 消费方消费出现故障,导致消息一直未被处理,消息就会过期
    消息过期
    如何处理过期消息?
  • 设置死信队列,接受过期消息
  • 消费死信队列中的过期消息,记录日志
  • 重新查询过期消息发送到MQ
    处理过期消息
  1. 过期消息进入到死信队列
  2. 启动专门的消费者消费死信队列消息,并写入到数据库记录日志
  3. 查询数据库消息日志,重新发送消息到MQ

上面很多的资料引用了 黑马程序员西安中心的资料,在这里要感谢黑马的老师

发布了8 篇原创文章 · 获赞 6 · 访问量 351

猜你喜欢

转载自blog.csdn.net/lzycug/article/details/105059499