[Java复习] MQ
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的更多相关文章
- java 复习003 之排序篇
由java 复习003跳转过来的C语言实现版见some-sort-algorithms 快速排序(不稳定 O(n log n)) package vell.bibi.sort_algorithms; ...
- java 复习001
java 复习001 比较随意的记录下我的java复习笔记 ArrayList 内存扩展方法 分配一片更大的内存空间,复制原有的数据到新的内存中,让引用指向新的内存地址 ArrayList在内存不够时 ...
- java复习(1)---java与C++区别
[系列说明]java复习系列适宜有过java学习或C++基础或了解java初步知识的人阅读,目的是为了帮助学习过java但是好久没用已经遗忘了的童鞋快速捡起来.或者教给想快速学习java的童鞋如何应用 ...
- Java调用MQ队列
IBM MQ 6.0中设置两个队列,(远程队列.通道之类都不设置). 队列管理器是XIR_QM_1502 队列名称是ESBREQ IP地址是10.23.117.134(远程的一台电脑,跟我的电脑不在一 ...
- Java复习11. 单例编程
Java复习11. 单例编程 1.最简单的写法,那个方式是线程不安全的 public class Singleton { private static Singleton instance; ...
- Java复习9网路编程
Java 复习9网路编程 20131008 前言: Java语言在网络通信上面的开发要远远领先于其他编程语言,这是Java开发中最重要的应用,可以基于协议的编程,如Socket,URLConnecti ...
- Java复习8.多线程
Java复习8 多线程知识 20131007 前言: 在Java中本身就是支持多线程程序的,而不是像C++那样,对于多线程的程序,需要调用操作系统的API 接口去实现多线程的程序,而Java是支持多线 ...
- Java复习10.Servlet编程
Java复习10. Servlet编程知识 20131008 前言: 之前在大三下的时候,学习了一个月的JSP和Servlet知识,但是没有什么项目经验,把JSP Web开发学习实录看了前面几张,后面 ...
- Java复习6异常处理
Java复习6.异常处理 20131005 前言: Java中的异常处理机制是非常强大的,相比C++ 来说,更加系统.但是我们开发人员没有很好的使用这一点.一些小的程序是没有什么问题的,但是对于大型项 ...
随机推荐
- 如何修改配置文件:CentOS下SSH端口修改
CentOS各发行版中SSH端口默认为22,如果正式做站或其它用途,为了提高安全性就需要修改掉默认的SSH端口号,防止被有心人穷举密码.部分VPS提供商,若您的VPS服务器SSH遭受多次的暴力破解,可 ...
- 检查shell脚本
1.检查solr服务监控脚本: #/bin/bash starttime=$(date +%Y-%m-%d\ %H:%M:%S) http_code=$(curl -I -m -o /dev//sol ...
- linux网络编程之system v消息队列(二)
今天继续学习system v消息队列,主要是学习两个函数的使用,开始进入正题: 下面则开始用代码来使用一下该发送函数: 在运行之前,先查看一下1234消息队列是否已经创建: 用上次编写的查看消息队列状 ...
- python中is与==的区别,编码和解码
在介绍is与==的区别前,我们先来了解一些新的知识:内存地址.小数据池. 1.内存地址(is 比较的就是内存地址) 获取内存地址的方法:id() a = "str" 2.小数据池 ...
- PYTHON WEB开发学习路线
两年大数据广告项目测试:项目覆盖几千万用户前景还不错:只是在工作中感觉测试太无力,最近准备辞职转web开发:同时会离开成都,(/(ㄒoㄒ)/~~待了6年,要离开喜欢的城市,喜欢的女孩很难受) ps:列 ...
- Python 高级(二)
多继承以及MRO顺序 1. 单独调用父类的方法 # coding=utf-8 print("******多继承使用类名.__init__ 发生的状态******") class P ...
- BZOJ 3601 一个人的数论 (拉格朗日插值+莫比乌斯反演)
题意 略 题解 orz Freopen的博客 CODE #pragma GCC optimize (3) #include <bits/stdc++.h> using namespace ...
- [Angular 8] Custom Route Preloading with ngx-quicklink and Angular
In a previous lesson we learned about implementing a custom preloading strategy. That gives you a lo ...
- ElementUI 之 DatePicker 日期限制范围 disabledDate
需求: 时间选择器,只能选择 2000 年 - 至今的年份. <el-date-picker v-model="year" type="year" :pi ...
- 006——转载-MATLAB数字与字符之间的转换
(一)参考文献:https://jingyan.baidu.com/article/5bbb5a1bd8dcb113eba1799d.html (二)数字转换成字符串 第一步在我们的电脑上打开matl ...