Java面试—消息队列

消息队列面试题

题目来自于中华石杉,解决方案根据本身的思路来总结而得。 题目主要以下: 3duWCT.pngjava

1. 为何要引入消息队列?

消息队列的引入能够解决3个核心问题:面试

  • 解耦
  • 异步
  • 削峰
  1. 解耦 在一个项目中,若是一个模块A产生的一个关键数据,须要调用其余模块接口服务;而须要调用的接口不少,又不肯定以后是否还须要将数据传给其余模块的接口时。这时可使用消息队列,使用了消息队列以后,模块A不须要在对接各个模块,而是直接对接消息队列。这样一来。当其余的模块须要这个数据时,也不用再修改A数据,而是去到MQ中订阅这个Topic。使得模块A与其余模块之间耦合度下降。 解耦
  2. 异步 在一个项目中,若是模块A的请求处理须要20ms,而模块A又依赖了模块B,模块C ,模块D 。在A请求处理结束后,A须要调用 模块B,C,D的对应请求处理,这时B请求处理须要100ms,C 须要200ms,D 须要400ms。这样一来,整体一个请求的总时长为 20 + 100 + 200 + 400 = 720 ms 远远大于模块A的请求处理时间。另外一方面,模块B 模块C 模块D之间 并无顺序关系。 这时能够引入消息队列 ,模块A在请求处理结束后,将本身的数据发送给消息队列MQ,由B,C ,D去消息队列获取数据,自行处理 模块A在处理完成后直接返回给客户端处理结果,而不须要等待B,C,D处理结束,如此一来,一个请求处理的就只须要计算 模块A的处理时间=20ms,大大提升了用户体验。 异步
  3. 削峰 若是一个系统只能一秒钟处理5000个请求(MySQL通常只能2000QPS),而在特殊时期就只要1个小时的时间段内,请求量暴涨,一秒钟来了1W个请求。从而系统会之间宕机。但这种状况可能在平时不会发生,不须要升级相应的服务器配置。 问题在于在1小时的时间段,如何把请求从10000QPS 降低到5000QPS 使得系统可以正常运转而不发生宕机。 这时候能够引入消息队列,假设模块A负责处理请求,模块B负责将数据持久化到数据库。模块A在接受到请求时,把请求交给消息队列,由消息队列来缓解对应的请求压力,相似于Buffer创建一个缓冲区,模块B根据本身的请求处理速度去到消息队列中去消费数据。这样一来就解决了对应特定时间段的削峰问题。 削峰
  4. 要结合实际项目来讲明: 体现上述的三个点。

2. MQ有什么缺点?

任何技术都是一把双刃剑,在引入消息队列的同时一定也会伴随着相应的问题,正如《人月神话》中所说,没有银弹。 消息队列的引入会带来3个核心的问题:spring

  1. 系统可靠性下降 在引入MQ时,MQ做为了中间层,这就使得模块与对应的MQ是紧耦合的关系。一旦MQ宕机,下游服务即便正常运行,但整个系统却没法使用。这个问题的解决方式是 MQ的高可用,使得MQ高可用,不会那么容易宕机达到5个9。 3dFX8A.png
  2. 系统复杂度提升 引入MQ,须要考虑的问题变复杂,随之而来的问题是
    1. 消息丢了怎么办
    2. 重复消费消息问题
    3. 消息的顺序问题 这几个问题都有对应的解决方案。
      3dFjgI.png
  3. 处理结果的最终一致性问题 引入MQ会致使处理结果的最终一致性问题,由于模块A与其余模块之间解耦,从而模块A不知道其余模块的处理结果 这就致使模块A觉得处理结果OK,但实际上可能模块B处理结果失败,这也是异步化所带来的最终一致性问题。 3dFvvt.png

3. 你都了解过哪些MQ? 他们之间有什么区别吗?

这个问题能够延伸为技术选型问题,关于这个问题能够认为凭什么你选择了这个MQ,而没有选择其余MQ。对应能够扩展为spring-security 与shiro 都是安全框架都实现了Oauth2协议,并且shiro是轻量级的,为何你选择了Spring-Security这个安全框架这个问题比较考察技术广度。数据库

首先我了解过的MQ有:activeMQ,RabbitMQ,RocketMQ,Kafka缓存

activeMQ RabbitMQ RocketMQ Kafka
并发量 万级 万级 十万级 万级
处理时长 毫秒 微妙 毫秒 毫秒
开发语言 Java ErLang Java Java Scala
功能完备 完备 完备 且提供了插件与管理界面 完备 完备
经常使用场景 小型项目demo * * 大数据领域日志处理、实时计算
社区活跃度 较低

activeMQ社区活跃度较低,不建议使用。 RabbitMQ社区活跃度高,更新版本频繁,但使用开发语言为ErLang , 当有定制化需求没法进行扩展 RocketMQ 阿里出品,但存在着后续项目不更新状况,这就使得企业自行维护相应的功能或者定制化功能 Kafka 大数据领域 主要用来进行实时计算,日志采集的消息队列,功能相比于其余MQ少,可是kafka是大数据领域公认的消息队列 若是对功能有要求,小公司能够选择RabbitMQ, 有技术团队的大公司可使用RocketMQ,大数据生态为了与其余组件配合因此使用Kafka安全

4. 如何保证MQ的高可用

  1. RabbitMQ的HA RabbitMQ的解决方式为=>集群模式 + 镜像 3dkDGd.png 普通集群模式:
    queue建立以后,若是没有其它policy(策略),则queue就会按照普通模式集群。对于Queue来讲,消息实体只存在于其中一个节点,A、B两个节点仅有相同的元数据,即队列结构,但队列的元数据仅保存有一份,即建立该队列的rabbitmq节点(A节点),当A节点宕机,你能够去其B节点查看,./rabbitmqctl list_queues发现该队列已经丢失,但声明的exchange还存在。 当消息进入A节点的Queue中后,consumer从B节点拉取时,RabbitMQ会临时在A、B间进行消息传输,把A中的消息实体取出并通过B发送给consumer,因此consumer应平均链接每个节点,从中取消息。 该模式存在一个问题就是当A节点故障后,B节点没法取到A节点中还未消费的消息实体。若是作了队列持久化或消息持久化,那么得等A节点恢复,而后才可被消费,而且在A节点恢复以前其它节点不能再建立A节点已经建立过的持久队列;若是没有持久化的话,消息就会失丢。这种模式更适合非持久化队列。 只有该队列是非持久的,客户端才能从新链接到集群里的其余节点,并从新建立队列。 假如该队列是持久化的,那么惟一办法是将故障节点恢复起来。 镜像集群模式:
    核心在于:镜像集群会同步消息
    该模式解决了上述问题,其实质和普通模式不一样之处在于,消息实体会主动在镜像节点间同步,而不是在consumer取数据时临时拉取。该模式带来的反作用也很明显,除了下降系统性能外,若是镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通信大大消耗掉。因此在对可靠性要求较高的场合中适用, 一个队列想作成镜像队列,须要先设置policy,而后客户端建立队列的时候,rabbitmq集群根据“队列名称”自动设置是普通集群模式或镜像队列。但这不是分布式存储,而是多节点主备存储。相比于Kafka的分布式架构会多消耗资源。
  2. Kafka实现高可用 3dkrRA.png Kafka的高可用主要是经过Kafka经过把每一个Topic中的消息分红多个Partition,每一个Partition作一个集群。而每个Partition内部集群经过选举获得一个leader,其余是follower,leader复制消息的读写,并把消息复制到follower。Kafka在发送消息时,只有当每一个partition的follower复制到了消息才确认消息已经被存储。

5. 如何保证消息不会丢失?

  1. RabbitMQ方式 1.1 生产者方面
    • 开启RabbitMQ的事务方式txSelect()。当生产者写入消息失败时,采起重试机制。但这个写入是同步的。
    • 使用confirm方式: 其中confirm也可使用三种方式:
    • 普通confirm
    • 批量confirm
    • 异步confirm 设置监听器
channel.confirmSelect();
	//普通confirm
	if (channel.waitForConfirms()) {
    System.out.println("消息发送成功" );
	}
	//批量confirm 失败会报错
	channel.waitForConfirmsOrDie();
	//异步监听confirm
	channel.addConfirmListener(new ConfirmListener() {
    @Override
    public void handleNack(long deliveryTag, boolean multiple) throws IOException {
        System.out.println("未确认消息,标识:" + deliveryTag);
    }
    @Override
    public void handleAck(long deliveryTag, boolean multiple) throws IOException {
        System.out.println(String.format("已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
    }
});

1.2 RabbitMQ方面 RabbitMQ在内存缓存消息,当MQ宕机时,会发生消息丢失现象。这一点须要RabbitMQ将消息持久化到硬盘上。减少消息丢失的可能 1.3 消费者方面 RabbitMQ默认是autoAck=true,这样就使得消费者在接受到消息时,立马告知MQ我消费了数据,但尚未来得及处理。因此须要把autoAck=false,以后当消息处理完成以后手动提交。服务器

2 kafka方面网络

  1. 生产端 数据丢失发送在leader向follower同步消息的时候,leader宕机使得消息丢失。
    解决方案是设置4个参数:
    1. replication.factor = n > 1 设置partition有多少个副本数
    2. kafka咋服务端设置mini.sync.replicas=1 即一个leader至少保证有一个follower存活
    3. producer在发送消息时,设置acks=all 设置全部消息必须所有写入replication后返回成功。
    4. retries=Integer.MAX 把重试次数调制最大
  2. 消费端 在消费者端关闭自动确认消息,这样须要手动确认 保证消息不会丢失。

6. 如何保证消息不会重复消费?

  1. 在消费者消费消息时,把对应的惟一值放入HashSet 或者Redis来避免同一条消息消费屡次
  2. 使用数据库的惟一键约束来报错处理

7. 如何保证消费者消费消息时消息的顺序性?

消息队列自己就是为了把多个任务解耦并行化处理,若是要保证消息的顺序消息,实际上就是取消并行处理,改为串行处理。架构

  1. RabbitMQ 将一组消息放入同一个queue,这样就只会有一个consumer来消费这个queue中的数据,在consumer内部采用队列的方式来处理顺序消息
  2. Kafka 生产者写消息时,每次写入一个topic,将partition对应key值修改同一key,这样消息只会进入一个partition,也只有一个消费者在进行消费,以后也采用内存队列的方式顺序处理消息。

8. 消息队列积压太多,目前消费者处理太慢,如何改进?

3dESAS.png 目前的消费者难以在短期内处理这么多条消息,考虑引入多个消费者,但引入这多个消费者只用于消费当前消息,并非长期使用。假定长期使用的消费者为A、B、C 订阅的Topic是Order。新申请3 * 10~20 = 30~60个机器 做为新的消费者组GroupNew。 GroupNew 订阅新的Topic - OrderFast,从这个topic中获取消息并消费。将原有长期使用的消费者组修改代码,不处理消息 直接将消息写入到新的Topic - OrderFast并发

9. 消息队列的机器磁盘快被消息写满了怎么办?

  1. 能够采起上述方案,不一样的是写入到另一台MQ中,而不是在本机的Topic
  2. 下一个消息直接丢弃不处理,到消息队列机器恢复以后,将生产者与消费者之间经过代码查询出对应的缺失部分,再进行补偿式操做。

10. 消息队列消费消息太慢,消息过时失效怎么办?

  1. 生产环境设置消息不过时
  2. 若是已通过期失效,那么须要查出过时失效的消息,从新进行补偿式操做

本文由博客一文多发平台 OpenWrite 发布!

相关文章
相关标签/搜索