zoukankan      html  css  js  c++  java
  • [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进阶面试训练营》的笔记 -- 中华石杉

  • 相关阅读:
    leetcode66 plusOne
    park/unpark 阻塞与唤醒线程
    leetcode55 jumpGame贪心算法
    ACID特性与事务的隔离级别
    PCB ODB++(Gerber)图形绘制实现方法
    PCB 所建不凡 AWS 技术峰会2018 • 深圳站 2018.9.20
    PCB SQL SERVER 位运算应用实例
    PCB SQL SERVER 枚举分割函数(枚举值分解函数)
    PCB SQL SERVER 正则应用实例
    PCB Genesis 外形加内角孔实现方法
  • 原文地址:https://www.cnblogs.com/fyql/p/Java.html
Copyright © 2011-2022 走看看