参考:https://www.zhihu.com/question/54152397?sort=created

    什么是消息队列?为什么使用消息队列?
    消息:就是要传输的数据,可以是简单的文本,也可以是自定义的复杂格式。
    队列:是一种先进先出的数据结构,它是存放消息的容器消息从队尾入队,从队头出队,入队即发消息的过程,出队即接收消息的过程。
    随着业务的不断扩大,采用微服务的设计思想,分布式的部署方式。业务场景就会越来越复杂,很多场景的单机技术和中间件不够用了,对系统的友好性下降了,最后决定引入消息队列中间件。

    三个主要应用场景
    解耦:
    一个系统流程很多业务都会去调用接口,每多一个业务就要调用一个接口然后还要重新发布系统,如果要全部写在一起的话不单单是耦合问题,出现问题时排查也会很困难。引入消息队列中间件只需要走完自己的流程把自己的消息发给别的系统,他们收到了去处理就好了。

    异步:
    传统方式有串行和并行,传统方式的系统性能会有瓶颈,比如一些非必要业务以同步方式运行耗费太多时间,使用消息中间件以异步方式运行加快响应速度,性能提高。

    削峰:
    并发量大时,所有的请求都直接发给数据库,造成数据库连接异常,服务器,Redis,MySql各自的承受能力不一样,全部由服务器接收压力过大。引入消息中间件后可以把请求放到队列中以服务器处理能力处理就算慢一点以不至于打挂服务器。

    消息队列优缺点:
    缺点:
    系统可用性降低:引入的外部依赖越多越容易挂掉。
    系统复杂性提高。
    数据一致性问题:消息传给多个系统,部分执行成功,部分执行失败容易导致数据不一致。

    MQ选择:
    ActiveMQ:没经过大规模的吞吐量场景的验证,社区也不是很活跃,不推荐使用。
    RabbitMQ:虽然erlang语言阻止了大量的Java工程师去深入;但是开源,比较稳定的支持,活跃度也高,中 小型 公司可以使用。
    RocketMQ:阿里的Java语言编写,性能和稳定性很好,目前被广泛应用在订单,交易,消息推送,日志流式处理等场景。
    kafka:大数据领域的实时计算,日志采集等场景使用kafka是行内标准;社区活跃度高。

    ActiveMQ
    ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。
    ActiveMQ特性:
    ⒈ 多种语言和协议编写客户端。语言: Java,C,C++,C#,Ruby,Perl,Python,PHP。应用协议: OpenWire,Stomp REST,WS Notification,XMPP,AMQP
    ⒉ 完全支持JMS1.1和J2EE 1.4规范 (持久化,XA消息,事务)
    ⒊ 对Spring的支持,ActiveMQ可以很容易内嵌到使用Spring的系统里面去,而且也支持Spring2.0的特性
    ⒋ 通过了常见J2EE服务器(如 Geronimo,JBoss 4,GlassFish,WebLogic)的测试,其中通过JCA 1.5 resource adaptors的配置,可以让ActiveMQ可以自动的部署到任何兼容J2EE 1.4 商业服务器上
    ⒌ 支持多种传送协议:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA
    ⒍ 支持通过JDBC和journal提供高速的消息持久化
    ⒎ 从设计上保证了高性能的集群,客户端-服务器,点对点技术
    ⒏ 支持Ajax
    ⒐ 支持与Axis的整合
    ⒑ 可以很容易得调用内嵌JMS provider,进行测试

    RabbitMQ
    RabbitMQ 是一套开源的消息队列服务软件,由LShift提供的APMQ开源实现,由以高性能、健壮以及可伸缩性出名的。
    RabbitMQ 多做了一层抽象,在发消息者和队列之间加入了交换器,这样发消息者和队列没有直接联系,转变成发消息这把消息发给转换器再由转换器调度后转发给队列。

    1. Channel(信道):多路复用连接中的一条独立的双向数据通道,建立在真实的TCP连接内的虚拟连接。
    2. Producer(消息生产者):向消息队列发布消息的客户端应用程序。
    3. Consumer(消息的消费者):从消息队列获取消息的客户端应用程序。
    4. Message(消息):由消息头和消息体组成,消息体不透明,消息头由一系列可选属性组成。
    5. Routing Key(路由键):消息头的属性,标记路由规则,决定交换机的转发路径。
    6. Queue(消息队列):存储消息的数据结构,是消息的容器也是消息的终点。一个消息可以投入多个队列,消息一直在队列中等待消费者连接到这个队列并将消息取走。当多个消息订阅同一个队列时,这个队列中的消息会被平分给多个消费者处理,每条消息只能被一个订阅者接收。
    7. Exchange(交换器 | 路由器):在发消息者和队列之间,交换器用于转发消息,不会存储消息。如果没有队列绑定转化器会直接丢掉发消息者发送的消息。转发消息有四种消息调度策略:

    Fanout(订阅模式 | 广播模式)
    Direct(路由模式):精确匹配
    Topic(通配符模式):按正则表达式模糊匹配
    Headers(键值对模式)

    Kafka
    Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群机来提供实时的消费。
    有如下特性:
    通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。(文件追加的方式写入数据,过期的数据定期删除)
    高吞吐量:即使是非常普通的硬件Kafka也可以支持每秒数百万的消息
    支持通过Kafka服务器和消费机集群来分区消息
    支持Hadoop并行数据加载
    Kafka相关概念:
    Broker:
    Kafka集群包含一个或多个服务器,这种服务器被称为broker
    Topic:
    每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)
    Partition:
    Parition是物理上的概念,每个Topic包含一个或多个Partition.
    Producer:
    负责发布消息到Kafka broker
    Consumer:
    消息消费者,向Kafka broker读取消息的客户端。
    Consumer Group:
    每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。

    高可用性:
    RabbitMQ:单机模式、普通集群模式、镜像集群模式(高可用)。
    开启镜像集群模式方法:管理控制台,Admin页面下,新增一个镜像集群模式的策略,指定的时候可以要求数据同步到所有节点,也可以要求同步到指定数量的节点,再次创建 queue 的时候 ,应用这个策略,就会自动将数据同步到其他的节点上去,无论是 元数据还是队列里的消息都会存在于多个实例上。
    缺点:① 性能开销太大,消息需要同步到所有的机器上,导致网络压力和性能消耗过大。②不是分布式的,没有办法线性扩展queue。
    Kafka高可用框架:
    天然的分布式消息队列,一个topic数据式分散放在多个机器上的,每个机器就存放一部分数据。Kafka 0.8之后才提供了HA机制;每个partition的数据都会同步到其他机器上,形成自己的副本,所有的replica会选举一个leader。生产者和消费者都和leader打交道;写的时候leader负责同步数据,读的时候就直接读取leader上的数据。只能读写,因为可以随意读写的话数据的一致性就不能得到保证,系统复杂度太高容易出现问题。

    消息队列的重复数据:
    按照数据进入Kafka的顺序,Kafka会给每条消息分配一个offset代表消息顺序的序号。消费者消费时就按照这个顺序去消费。消费完会提交offset 表示已经消费了。但消费者不是消费完一条数据就立马提交offset 的,而是定期提交一次,如果消费者消费量消息准备提交但是还没有提交的时候挂机了或者重启了。Kafka就不会知道这个消息消费了。Kafka就会再把这个offset 发给消费者,就出现了消息重复。
    保证MQ重复消费幂等性:
    幂等:一个数据或一个请求重复来多次需要确保对应的数据不会改变;
    怎么保证消息队列消费的幂等性?
    1、拿数据写库时,首先检查主键,如果有数据则不进行插入,进行update操作。
    2、如果是写redis,每次都是set操作,是天然的幂等性。
    3、生产者发送消息时带上一个唯一的id,消费者拿到id 后现根据id 去redis 查询,之前没有消费过就进行处理,并把id写入redis,
    4、使用数据库的唯一主键来保证重复数据不会重复插入多条,因为唯一键约束,重复插入会报错不会导致数据库中出现脏数据。

    MQ 的数据丢失:
    RabbitMQ:

    • 生产者写消息过程中消息再网络过程中就丢了,或者到了rabbitMQ 但是因为内部原因没有保存起来。
    • RabbitMQ接收到消息之后先暂存在主机的内存中,结果消费者还没来得及消费,RabbitMQ 自己先挂掉了就导致暂存的数据丢失了。
    • 消费者消费了这个消息,但是还没来得及处理就挂掉了,RabbitMQ 会认为消费者已经处理完这个数据了。

    解决:

    1. 事务机制:同步的,生产者发送消息会同步阻塞等待,会导致生产者发送的吞吐量下降,一般不使用。

    confirm机制:异步模式,不会阻塞,吞吐量高。

    1. - 先把channel 设置成confirm 模式。
    2. - 发送一个消息到RabbitMQ,发送完后就不用管了
    3. - rabbirMQ 接受到消息会回调生产者本地的接口,通知收到消息。
    4. - rabbitMQ 如果在接收时报错了也会回调一个接口通知消息接受失败,需要再次发送。
    1. 持久化到磁盘:创建时设置为持久化或发送时设置为持久化,rabbitMQ 就会将消息持久化到磁盘,但必须设置两个持久化。缺点是可能会有一点点丢失数据的可能。
    2. 如果消费者打开了autoAck 机制,需要关闭autoAck 机制;自己处理完一条消息后在发送ack 给rabbitMQ,如果没有处理完就死机了,此时rabbitMQ 没有接收到ack消息,rabbitMQ 就会将这条消息重新分配给其他消费者处理。

    Kafka:

    • 消费者消费消息后就自动提交offset,如果还没有处理就挂掉了,消息就丢失了。这时就要关闭自动提交offset,在处理完消息后手动提交就可以保证不会在这里丢失数据。但是还是有可能出现重复消费。
    • Kafka 在重新选举leader 时,其他follower 还有数据没有同步leader 就挂了,然后会重新选举leader,就会丢掉之前leader 里未同步的数据。所以此时一般是要设置4个参数:
      • 给topic 设置 replication.factor :必须大于1
      • 给Kafka 服务端设置min.insync.replicas:必须大于1
      • 在生产者端设置acks=all:要求每条数据必须是写入所有replica之后才认为成功。
      • 在生产者段设置retries=MAX:一旦写入失败就无限重试,卡在这里避免消息丢失。

    消息队列的顺序性:
    RabbitMQ:一个queue(队列)多个消费者(consumer)会导致消息顺序错乱
    拆分多个队列,一个queue 对应一个consumer,consumer 内部使用内存队列做排队,分发给底层处理,但是会比较麻烦。
    Kafka:一个topic,一个partition,一个consumer,内部多线程,也乱了
    内部单线程消费,但是吞吐量太低一般不会使用。
    写N个内存queue,具有相同的key的数据都存放到同一个队列queue。对于N个线程,每个线程分别消费一个内存queue即可,这样就能保证顺序性。

    队列的延迟和过期问题:
    RabbitMQ是可以设置过期时间的,如果在队列中积压过超过一定的时间就会被清理掉,数据就没了。就需要写一个程序将丢失的数据查出来,然后重新灌入MQ 中,把数据补回来。
    积压 处理:
    先修复consumer 的问题,确保恢复消费速度,然后将现有的consumer 都停掉。
    新建一个topic,临时建立一个比原先多几十倍的queueu 数量。
    然后写一个临时的分发数据consumer 程序,这个程序部署消费积压的程序,消费之后不做耗时处理,直接均匀写入临时建立好的queue 中去。
    接着临时征用机器来部署consumer,每一批consumer 消费一个临时的queue数据。
    这种做法相当于临时将queue 资源和consumer 资源扩大N倍,以正常速度的N倍消费
    等快速消费完积压数据后,恢复原先部署的架构,重新用原先的consumer 机器消费。

    如何设计一个消息队列中间件架构:
    设计MQ 需要支持课伸缩性,快速扩容。设计一个分布式的MQ,broker->topic->partition。每个partition 都放一个机器,就存放一部分的数据,如果资源不够就额给topic 增加partition,然后做数据迁移,增加机器。
    MQ数据落地磁盘,保证线程挂后数据不会丢失,按顺序写就不会有磁盘读写开销,
    MQ的高可用性,多副本->leader & follower->broker 挂了重新选举leader 。
    数据0丢失。