1. 为什么要用MQ

解耦,异步,削峰

2. MQ的优点和缺点?

优点:

解耦、异步、削峰

缺点:

1. 系统可用性降低。 外部依赖越多,越容易挂。如果MQ挂了,怎么处理?

2. 系统复杂度提高。 怎么保证没有重复消费,怎么处理消费丢失,怎么保证消费顺序等等。

3. 一致性问题。通过MQ的消息,如果一个系统时子系统A通过MQ传消息给子系统BCD,某个处理需要ABCD处理都完成才算成功。如果子系统D失败,怎么办。

3. 几种MQ的技术选型和适用场景?

ActiveMQ、RabbitMQ、RocketMQ、Kafka各自优缺点。

ActiveMQ: 吞吐量万级,大家用的不多,没有大规模吞吐量场景的业内实践。

RabbitMQ: 吞吐量万级,MQ功能完善,管理界面好,可做集群但不是分布式,开源社区活跃,国内使用公司较多。

缺点Erlang国内少,很少看懂源码,公司对这个掌控很弱,只能依赖开源社区。

RocketMQ: 单机吞吐量10万级,topic可以达到几百,几千个,分布式架构,MQ功能完善,社区活跃度还不错,可以支持业务复杂吞吐量高的MQ场景。Java源码,可以阅读源码,可定制。

缺点:万一项目被阿里抛弃,没人维护。

Kafka: 单机吞吐量10万级,功能只能支持简单MQ,在大数据实时计算以及日志采集被大规模使用。易于扩展,加机器。

总结:

ActiveMQ: 不推荐。

中小公司,技术实力一般,技术挑战不高,用RabbitMQ。

中大公司,基础架构研发实力强,Java方面的,用RocketMQ。

大数据,Kafka业界标准。

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

4.1. RabbitMQ高可用

RabbitMQ 三种模式:单机,普通集群, 镜像集群模式

单机

做demo,学习用

普通集群

多台机器启动多个rabbitmq实例,每个机器一个,但是创建的queue只会放在一个rabbitmq实例上,每个实例都同步queue的元数据。

元数据:配置信息,其他节点会拉queue的元数据。

这个方式很麻烦,不是分布式,是普通集群。会导致消费者每次随机连接一个实例后拉取数据,可能只有元数据,没有实际数据。

唯一优点,可以从多个机器消费数据,提高消费者吞吐量。

缺点:1. 可能会在rabbitmq集群内部产生大量数据传输;2. 可用性没有保证,如果queue的数据所在节点挂了,数据也就丢失了。

镜像集群

queue的元数据和实际数据会在每个节点,任何一个节点宕机,其他节点还有queue的完整数据,别的消费者可以在其他节点消费。

缺点:不是分布式,如果queue的数据量大,大到机器上的容量无法容纳是,怎么办?

怎么开启镜像集群:管理平台新增一个策略,要求数据同步到所有节点,也可以同步指定节点,创建queue时应用这个策略。

4.2 Kafka高可用

天然分布式,多个broker组成,每个broker一个节点,创建一个topic,划分为多个partition,每个partition可存在与不同的broker,每个partition放一部分数据。

Kafka 0.8以前没有HA。之后每个节点有relpica副本。选举leader,写leader时会将数据同步到follower。生产者消费者都是在leader上。

一个节点挂了时,kafka自动感知leader挂了,会将follower选择为leader。生产者消费者会读写新的leader。

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

5.1. kafka可能出现重复消息的问题

按照数据进入kafka的顺序,kafka会给每条消息分配一个offset(如offset=N),代表这个数据的序号。

消费者从kafka消费时候,是按照这个顺序去消费的。消费者会提交offset,告诉kafka我已经消费到offset=N的数据了。Zookeeper会记录消费者当前消费到offset=N的那条消息。

消费者如果重启,告诉kafka把上次消费到的那条数据之后给传递过来。

坑:消费者不是消费完一条数据就提交offset,而是定时定期提交一次offset。假设消费者提交之前挂了,就没有提交offset。重启后kafka会把上一次offset的数据发来,就会有重复数据。

5.2. RabbitMQ类似

解决方案:

1. 生产者发送的每条消息包含一个全局唯一ID, 消费者拿到消息先判断用这个ID去Redis(或者内存的Set)查一下,

如果不存在,就处理(插入DB等等),然后再把这个ID写入Redis。如果存在的,就不处理了,保证了幂等性。

2. 还有基于数据库唯一键也可以。如果重复数据插入会报错。

6. 如何处理消息丢失的问题?

丢数据,MQ分两种,要么是MQ自动弄丢了,要么是消费的时候丢了。

6.1. 对于RabbitMQ消息丢失: (3种丢失情况)

生产者 -----> MQ  ----->  消费者

6.1.1 生产者写消息时丢失

解决方案:1.  选择用RabbitMQ的事务功能。生产者发送之前开启RabbitMQ事务(channel.txSelect), 如果消息没有被RabbitMQ成功收到,那么生产者会收到异常报错,就可以回滚,重试发送消息。如果RabbitMQ收到消息,就提交事务。

// 开启事务
channel.txSelect
try {
// 这里发送消息
} catch (Exception e) {
channel.txRollback
// 这里再次重发这条消息
}
// 提交事务
channel.txCommit

这种事务的缺陷:生产者发送的消息会同步阻塞等待RabbitMQ的成功失败,吞吐量下降。

2. 生产者先把Channel设置为Confirm模式,发送消息后不管。RabbitMQ如果收到消息,会回调生产者本地接口,通知消息成功与否。失败通知重发。

一般用Confirm模式,异步,不阻塞,吞吐量高。

6.1.2. RabbitMQ接收消息后消费者还没来得及消费时, MQ故障消息丢失:

解决方案:设置持久化。第一,创建queue时设置为持久化,保证rabbitmq持久化queue的元数据,但是不会持久化queue的实际数据。

第二,发送消息时将消息的deliveryMode设置为2(持久化消息)。这样才能将消息持久化到磁盘。

还有一种小概率情况,即使开启了持久化,消息已经写入rabbitmq内存,但还没有来得及写入到磁盘上挂了,会导致内存里的数据丢失。

6.1.3. 消费者消费到了消息但是还没来得及处理时挂了,MQ以为消费者已经处理完:

问题产生原因:消费者打开了autoAck,消费者收到数据后会自动通知RabbitMQ已经消费了,这时如果消费者宕机了,没有处理完,会丢失这条消息,

但是rabbitmq以为这个消息已经处理了。

解决方案:消费者关闭autoAck,自己确定处理完消息后才发送ack到Rabbit。

  6.2. 对于Kafka消息丢失

6.2.1. 消费端丢失消息

和RabbitMQ类似,kafka会自动提交offset,那么关闭自动提交offset,在处理完之后自己手动提交offset,可以保证数据不丢失。

6.2.2. kafka丢失消息

产生原因:生产者发送消息给某个broker,这个leader还没有来得及把数据同步到follower就挂了,这个之后选举产生的leader就少了这条数据。

设置4个参数:

1. topic设置replication.factor > 1。要求每个partition必须有至少2个副本

2. kafka服务端min.insync.replicas > 1。要求一个leader至少感知一个follower与自己保持联系。

3. 生产者 ack=all 。要求每条数据必须写入所有replica之后,才能认为时写入成功。

4. 生产者retries=MAX。一旦写入失败 ,无限重试,阻塞写入。

6.2.3. 生产者会不会弄丢消息

如果丢了,会自动重发,不会丢。

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

7.1. RabbitMQ:  一个queue,多个消费者,顺序乱了

解决方案:拆分多个queue,需要按顺序处理的消费放入一个queue,由一个消费处理,消费者内部用内存队列排队,然后处理,比如顺序插入库

7.2. kafka: 一个topic,一个partition,一个消费者,但一个消费者内部多线程,顺序乱了

解决方案:首先kafka中生产者写入一个partition中的数据一定是有序的。

生产者写数据时指定一个key,比如订单id为key,这个订单相关数据一定会被分发到一个partition上,而且这个parttion中数据一定时有序的。

消费者从partition中取数据一定时有序的。

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

 8.1. 第一个坑,大量消息在MQ里积压几个小时还没有解决?

临时紧急扩容。

1. 先修复消费者程序的问题,确保恢复消费者速度

2. 临时建好原先10倍或20倍queue的数量

3. 写临时分发数据的消费者程序,让这个程序去消费积压消息,均匀轮询到临时建好的10倍queue

4. 用临时创建的10倍机器来部署修复后的消费者程序,每一个消费者消费一个临时queue的数据

5. 等快速消费完积压数据之后,恢复原先的部署架构,重新利用原先的消费者机器来消费消息

8.2. 第二个坑,如果RabbitMQ设置了过期时间(TTL),(实际中应该禁止),消息在queue中积压超过过期时间后被RabbitMQ丢弃了,怎么办?

批量重导。等高峰期过后,写程序将丢失的数据一点点查出来(总有日志可以追踪),重新写入MQ,重新走一遍流程。

8.3. 第三个坑,消息积压MQ里,MQ快写满磁盘,怎么办?

写个临时程序,接入数据来消费,消费一个丢一个,快速消费积压的消息,减轻磁盘容量压力。

峰值过后,采用上述7.2的方案,到低峰期再补数据重走流程。

9. 如果让你来开发一个消息队列,该如何进行架构设计?说一下思路。

考点:1. 有没有对某个消息队列原理做过较深入了解,或整体把握一个MQ的架构

2. 查看设计能力,能不能从全局的把握一下整体架构设计,给出一些关键点

类似问题:让你来设计一个Spring框架/Mybatis框架/Dubbo框架/XXX框架,你怎么做?

1. 考虑可扩展,可伸缩。

设计分布式MQ,参考kafka,每个broker是机器上面一个节点,一个topic拆分多个partition, 每个partition放一台机器上,只放topic一部分数据。

资源不够就给topic增加partition, 做数据迁移,增加机器,扩容提高吞吐量。

2. 考虑消息持久化。

落磁盘才能保证进程挂了数据不丢。落磁盘怎么写?参考kafka,顺序写,就没有随机写的寻址开销,顺序写性能更高。

3. 考虑高可用。

参考kafka,多副本,leader 和follower,只有leader读写,follower同步leader数据,leader挂了选follower。

4. 考虑支持数据0丢失。

参考kafka如何设计数据0丢失。要求每个partition必须有至少2个副本。要求一个leader至少感知一个follower与自己保持联系。

要求每条数据必须写入所有replica之后,才能认为时写入成功。一旦写入失败 ,无限重试,阻塞写入。

5. 考虑消息顺序。

消费者如果多线程处理,加入内存队列分发,既能保证顺序又能增加吞吐量。

参考资料:

《互联网Java进阶面试训练营》的笔记 -- 中华石杉

[Java复习] MQ的更多相关文章

  1. java 复习003 之排序篇

    由java 复习003跳转过来的C语言实现版见some-sort-algorithms 快速排序(不稳定 O(n log n)) package vell.bibi.sort_algorithms; ...

  2. java 复习001

    java 复习001 比较随意的记录下我的java复习笔记 ArrayList 内存扩展方法 分配一片更大的内存空间,复制原有的数据到新的内存中,让引用指向新的内存地址 ArrayList在内存不够时 ...

  3. java复习(1)---java与C++区别

    [系列说明]java复习系列适宜有过java学习或C++基础或了解java初步知识的人阅读,目的是为了帮助学习过java但是好久没用已经遗忘了的童鞋快速捡起来.或者教给想快速学习java的童鞋如何应用 ...

  4. Java调用MQ队列

    IBM MQ 6.0中设置两个队列,(远程队列.通道之类都不设置). 队列管理器是XIR_QM_1502 队列名称是ESBREQ IP地址是10.23.117.134(远程的一台电脑,跟我的电脑不在一 ...

  5. Java复习11. 单例编程

    Java复习11. 单例编程 1.最简单的写法,那个方式是线程不安全的 public class Singleton {     private static Singleton instance; ...

  6. Java复习9网路编程

    Java 复习9网路编程 20131008 前言: Java语言在网络通信上面的开发要远远领先于其他编程语言,这是Java开发中最重要的应用,可以基于协议的编程,如Socket,URLConnecti ...

  7. Java复习8.多线程

    Java复习8 多线程知识 20131007 前言: 在Java中本身就是支持多线程程序的,而不是像C++那样,对于多线程的程序,需要调用操作系统的API 接口去实现多线程的程序,而Java是支持多线 ...

  8. Java复习10.Servlet编程

    Java复习10. Servlet编程知识 20131008 前言: 之前在大三下的时候,学习了一个月的JSP和Servlet知识,但是没有什么项目经验,把JSP Web开发学习实录看了前面几张,后面 ...

  9. Java复习6异常处理

    Java复习6.异常处理 20131005 前言: Java中的异常处理机制是非常强大的,相比C++ 来说,更加系统.但是我们开发人员没有很好的使用这一点.一些小的程序是没有什么问题的,但是对于大型项 ...

随机推荐

  1. Python使用jieba分词

    # -*- coding: utf-8 -*- # Spyder (python 3.7) import pandas as pd import jieba import jieba.analyse ...

  2. reGeorg+Proxifier使用

    reGeorg利用了socks5协议建立隧道,结合Proxifier可将目标内网代理出来. 项目地址: https://github.com/sensepost/reGeorg 该文件下支持php,a ...

  3. 《构建之法》第五次作业——Alpha项目测试

    博客开头 这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/2019autumnsystemanalysisanddesign?page=6 这个作业要求在 ...

  4. cookie和session基础知识学习

    一.session的简单使用 session是服务器端技术,服务器在运行时可以为每一个用户的浏览器创建一个独享的session对象.session的使用步骤: 获取session对象使用session ...

  5. JavaScript异步学习笔记——主线程和任务队列

    任务队列 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务.如果前一个任务耗时很长,后一个任务就不得不一直等着. 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕, ...

  6. ServletRequest、 HttpServletRequest、Request的联系与区别

    一. servlet理论上可以处理多种形式的请求响应形式 http只是其中之一 所以HttpServletRequest HttpServletResponse分别是ServletRequest和Se ...

  7. Linux - 网络配置( CentOS 64 )

    终于..今天我终于将linux的网络调试出来了,虽然之前看了一大堆教程,每一个都是一样的步骤,但是,在我这就是弄不好,所以经过不断尝试的我,今天发一个自己配置好的步骤,唉,太痛苦了. - 对了补充一句 ...

  8. 学到了林海峰,武沛齐讲的Day16完

    函数嵌套 foo()()() ====  foo()>>>gxr   gxr()>>>wsb    wsb()>>执行wsb函数 lambda   一行 ...

  9. vue上传大文件控件

    文件上传是 Web 开发肯定会碰到的问题,而文件夹上传则更加难缠.网上关于文件夹上传的资料多集中在前端,缺少对于后端的关注,然后讲某个后端框架文件上传的文章又不会涉及文件夹.今天研究了一下这个问题,在 ...

  10. bzoj 3999: [TJOI2015]旅游 LCT

    没啥难的,inf 的值设小了调了半天~ code: #include <bits/stdc++.h> #define N 50003 #define lson t[x].ch[0] #de ...