[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进阶面试训练营》的笔记 -- 中华石杉

posted @ 2019-11-10 15:12  潜林  阅读(407)  评论(0编辑  收藏  举报