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

  • 相关阅读:
    hdu 1017 A Mathematical Curiosity 解题报告
    hdu 2069 Coin Change 解题报告
    hut 1574 组合问题 解题报告
    hdu 2111 Saving HDU 解题报
    hut 1054 Jesse's Code 解题报告
    hdu1131 Count the Trees解题报告
    hdu 2159 FATE 解题报告
    hdu 1879 继续畅通工程 解题报告
    oracle的系统和对象权限
    oracle 自定义函数 返回一个表类型
  • 原文地址:https://www.cnblogs.com/fyql/p/Java.html
Copyright © 2011-2022 走看看