消息队列 MQ 的简单汇总

160 阅读14分钟

消息队列

一、 为什么使用消息队列?

1. 思路 :

你们的业务中什么场景使用MQ,使用MQ给你们解决了什么问题。

2. 回答 :

1. 使用MQ的优点

  • 解耦 减少多个业务系统之间的强连接

    通常,强连接的业务系统会相对比较脆弱,其中一个系统发生异常,可能导致整个系统不可用,微服务架构为主要服务方式的今天,系统间解耦是非常有必要的。

  • 削峰 可以让峰值请求不直接作用到系统上造成压力过大

    MQ 的削峰逻辑很简单, 如果是请求直连系统,那么当遇到大量请求的时候,会直接作用于系统上,当请求数大于系统的最大处理能力,可能会造成系统宕机;通过MQ可以让大量的请求先保存在MQ中,系统从MQ中取请求处理,系统只会取自己能力之内的请求数,让峰值请求变得平缓。

  • 异步

    通常来说,对于某些较复杂逻辑的接口(比如业务逻辑比较复杂,或者需要多次查询数据库),如果设计成同步的,会极大的降低用户的使用体验 【感受下,点个按钮3秒没反应是啥感觉】;MQ的使用,让系统间的调用变成异步化,很大程度上的优化接口性能,提高用户的体验。

2. 使用MQ的缺点

  • 系统可用性降低

    系统引入的外部依赖越多,系统的可用性就越低,因为这意味着你可能会出问题的东西越来越多

  • 系统复杂性提高

    一样的思路,系统越复杂,意味着需要维护和保证的东西就越多,同时引入的三方工具可能会导致其他的问题,比如说丢消息,消息的顺序性,消息不会多次消费等问题就会随着MQ的引入出现

  • 一致性问题

    MQ 的引入往往伴随着异步,生产者将数据丢给MQ不代表消费者能成功消费,会导致数据不一致;如果是分布式的MQ, 往往还包含着分布式数据一致性的问题

二、 Kafka / ActiveMQ / RocketMQ / RabbitMQ 的区别

这里我们用一张表来展示:

特性ActiveMQRocketMQRabbitMQKafka
单机吞吐量万级十万级万级十万级
时效性ms级ms级us级 低延迟是RabbitMQ的一大优势ms级
可用性高 主从架构非常高 分布式架构高 主从架构非常高 分布式架构
topic 数量对吞吐量的影响随着topic增多到一定的级别,吞吐量会小幅下降随着topic增多到几百,性能会大幅下降
消息可靠性有一定概率丢失数据通过配置可以实现0丢失通过配置可以实现0丢失通过配置可以实现0丢失
核心特点MQ功能完善MQ功能较为完善,吞吐量较大基于erlang开发 性能好,并发能力强,延迟低功能简单,一般用于大数据实时计算场景,但是吞吐量非常大
总结功能强大,非常成熟,原来有很多企业都在用,但因为确实会偶尔丢失消息,现在使用的企业越来越少,社区维护也越来越少接口简单易用,曾经在阿里大规模使用过,支持分布式,高吞吐,高可用,易于扩展基于erlang开发,性能不错,提供的管理页面功能十分完善,延迟非常低,但是吞吐量不够仅仅提供较少的核心功能,但是吞吐量非常大,提供极高的可用性以及扩展性,但是必须结合zookeeper使用,增加了系统的复杂度

三、 如何保证消息队列的高可用性?

1. RabbitMQ 高可用
  • 普通集群模式

    在这个模式下 数据在一个queue中,其他的queue都是只有元数据,当请求发送到没有数据的queue中的时候,该queue会通过元数据找真实数据所在的实例queue然后拉取数据返回,该模式只能一定程度的提升吞吐量,可用性几乎没什么保障,只要主数据实例宕机还是会导致系统崩溃

  • 镜像集群模式

    这个模式下 ,每个节点都有queue的完整镜像,消费的时候请求到哪个queue ,那台就可以直接将数据消费给用户,这种模式下任何一个节点宕机,数据都还有备份,系统可用性比较高; 缺点是 数据多个备份意味着占用的存储空间会变大

2. Kafka 高可用
  • 每个 kafka实例会启动一个broker进程 我们认为一个实例就是一个broker 一个集群是由多个broker组成的
  • kafka中会有topic,一个topic中会划分很多个partition,partition才是真正存放消息的单元,一个partition中存放topic中的一部分数据,多个partition的数据合并起来就是整个topic的数据
  • 通过分布式架构,将数据分散在多个broker的同一个topic的不同partition中 ,这样如果一个broken宕机,丢失的最多是这个broker中该topic对应的partition的数据,不会数据全丢;接下来我们只要保证这个broker的数据不丢失就行了,而保证这个功能的就是replica副本机制
  • 通过replica 副本保证一个broker的数据高可用
    • 这个replica的副本机制你可以简单理解为主从,但是这里主副本提供读写,从副本只用来备份 主副本又称为 leader 从副本又称为follower
    • 在 Kafka中follower会定期通过fetchRequest向leader发送请求用于同步leader数据,满足条件的
      • 该follower通过心跳机制不停的将心跳发送给zookeeper,让集群知道follower存活
      • follower和leader的数据延迟小于一个配置值 replica.lag.time.max (ms)
        follower会进入一个小圈子,我们称其为ISR(In-Sync Replica),可想而知, leader肯定先要在这个圈子里。进入这个圈子的follower我们认为他和leader保持一致,也具备了参与选举leader的资格。就算有follower不小心掉队了,也会后续追赶,尽量加入这个圈子,从而保证了数据的可靠性。
    • 当leader宕机的时候,kafka会从ISR中选举一个follower成为新的leader接管服务,从而保证了高可用。

四、 如何保证消息不被重复消费 ? 如何保证消息的幂等性?

1. 怎么保证消息不被重复消费?

MQ是不保证消息只发一次的,MQ大多数情况是是只尽量保证不丢消息。所以一条消息有可能发送多次的,保证消息不重复消费是我们自己的事情,虽然在部分MQ中也有一定的机制尽量保证不重复消费:

  • Kafka

    • kafka中每个partition中的每个消息都有一个唯一的自增offset与之对应,这个offset可以定位是哪个partition中的哪条消息。

    • 消费者消费的时候也是按照顺序来的,消费者在消费了一条数据之后,会提交offset, 告诉kafka我消费到哪里了

    • 如果出现系统宕机下次重启的时候,kafka 会按照上次系统的最后消费记录,即offset将将消息给到对应的系统;但是如果系统刚消费完还没来得及把offset提交,kafka就宕机了,那么下次重启的时候还是会出现重复消费的,这个时候就需要我们系统中作出相应的保障。

    其实我们往往不用保证消息是不是重复消费,我们只要保证消费幂等即可

2. 怎么保证消息的幂等性
  1. 什么叫幂等?

幂等是一个数学概念,意思是多次执行和一次执行产生的效果是一样的,比如你经常因为卡顿点了多次的提交和确认,其实只算了一次,不会因为你多次提交就产生多个用户或者多份数据的。

  1. 如何保证幂等?
  • 我们可以将MQ中的数据比如说msgID缓存在系统中Set, 每次消费数据的时候,我们都使用Set进行判重操作,重复则不进行接下来的操作
  • 如果放在系统内存中如果系统宕机Set也会随之丢失,更保险的方案还是需要借助三方,比如我们将msgID放在Redis集群中Set中, redis集群本身支持高可用, set也能进行判重。 或者干脆使用数据库的unique列,插入数据的时候,如果该列数据存在,则insert失败。

五、 如何保证MQ数据不丢失?

我们来分析一下,MQ丢失数据的原因:

  • 生产者生产的数据因为网络原因没有发送到MQ丢失
  • 生产者生产的数据发送给MQ, MQ没来得及落盘宕机,导致数据丢失
  • 消费者刚消费数据,还没走业务逻辑,消费者挂了,MQ以为消费者消费了数据,数据丢失
1. RabbitMQ

RabbitMQ 一般是存放核心系统的一些数据的

  • rabbitmq 支持事务, 生产者发送消息的时候开启事务,同步机制,性能受影响

     channel txSelect()  // 开启事务
     try {
         sendMessage(msg);
     } catch (Exception e) {
         resendMessage(msg) 或者 rollBack()
     } finally {
         channel.txCommit
     }
    
  • confirm机制异步模式 ,这个机制下,需要生产者提供一个回调函数,生产者发送完消息就不管了, rabbitmq 如果成功接收并落盘会调用成功回调的函数,这样让生产者知道消息发送成功;如果mq消息接收失败,或者生产者长时间没有接收到成功的回调,那么生产者重新发送一次消息。 异步机制,性能比较高。

  • MQ 本身将数据持久化到磁盘才算接收到了消息

    • rabbitmq 中首先要开启的持久化功能,这样rabbitmq 才会进行持久化,但是只是持久化元数据
    • 发送消息的时候将 deliveryMode 设置成 2, 这样才能保证消息持久化
    • 但是就算开启了参数,如果数据没来得及持久化mq就挂了,还是可能丢少量数据,但是概率极低
  • 消费者端,不建议开启 autoAck

    如果开启则意味着消费者一旦消费,就会向mq发送消费成功标识,如果此时消费者没来的及完成业务逻辑以及落盘,消费者宕机,那么这个消息就算丢失了;

    消费端关闭 autoAck,只有当消费者的消息处理完成之后再发送 ack 告诉mq消费完毕即可。

2. Kafka
  • 消费端丢失数据

    当消费者开启了自动提交offset,消费者没来得及处理就宕机了,这种情况的解决方案和rabbitmq一样,关闭自动提交,只有当消费端消费并处理完了再提交offset即可

  • kafka 本身丢失数据的情况

    1. leader端的数据还没有同步到follower,leader宕机,触发重新选举,follower被选举成leader,此时follower中是没有刚刚未同步的数据的,这样就造成了数据丢失
    2. kafka的落盘也是基于os的,kafka先将数据写入os的cache,然后通过fsync将数据落盘,如果当数据还没有落盘的时候,os崩溃也会导致数据丢失
  • 解决方案:

    设置四个参数

    • 设置 replication.factor >= 2 用来设置topic的副本至少有两个,当然这个值可以根据broker的数量去调整,但是至少保留两个
    • 设置 min.insync.replicas > 1 这个参数保证最小同步副本数大于1 ,也就是说保证了一定至少有一个副本和leader数据一致
    • producer 设置 acks = -1, 保证写入所有的 replica 才算发送成功
    • producer 设置 retries = MAX 保证丢失数据就会一直重试发送,直到成功为止
  • 生产端丢失数据

    按照上面的设置,生产者不会丢数据,如果数据没应用到,生产者会无限次重新发送数据,其实生产端如果设置成同步消息,如果失败则重试也行,就像rabbitmq的事务机制,但是同步的效率太低,所以一般我们不用

六、 怎么保证消息队列中的数据的顺序性?

消息顺序异常的场景

  • rabbitmq 一个queue多个consumer

    解决这个问题只要提供多个queue,每个消费者只消费一个queue, 我们保证有顺序要求的消息发送到指定的queue中即可

  • Kafka

    • kafka 保证写到一个partition中的消息是有序的
    • producer 通过设置key保证需要有序性要求的数据进入同一个partition
    • kafka中一个消费者只能消费同一个topic中的一个partition的数据
    • 如果消费者中是多线程消费,那么要针对多线程,每个线程准备一个消息队列,利用不同的key将需要顺序的过程放到同一个队列中进行消费,说白了还是一种切割的思想,但是保持顺序性还是通过排队来进行的

七、 消息队列满了怎么办?消息队列中积压了很多消息怎么办?

出现这个问题,一般是consumer挂了, 或者consumer和producer存在过大的速度差,如果是后者的话,就要考虑增加consumer的性能及数量了,我们这里只分析第一种情况,这是个线上常见的问题,当然了我们处理线上生产故障的时候,运维首先要保证有一些高性能服务器可用,如果这个基本条件不满足,那就只能慢慢来了。

  1. 先修复一个consumer的问题 确保其恢复消费速度 然后停掉所有的异常 consumer

  2. 新建一个临时的比原来长度10倍或者20倍的queue

  3. 然后写一个临时的consumer ,这个consumer不做业务逻辑,不耗时,他的任务就是把当前mq中的数据快速转发到我们临时创建的queue中

  4. 接着临时征用高性能的机器,部署多个consumer去快速消费10倍的队列

  5. 其实消息队列设置过期时间也能保证不会大面积积压 ,但是可能会丢失消息;如果大面积丢失消息,可能就需要我们自己重新找回数据然后往mq里面发送

八. 如果让你设计一个消息队列框架,你会怎么做?

  • MQ 要支持可伸缩
    • 主要的设计思路就是分布式,类似于 kafka 的 topic partition 机制,将数据分流提高吞吐量
  • 考虑MQ的数据落盘
    • 落盘是为了保证数据不丢,落盘也要保证高可用,如果设计成主从要保证数据在主从间的延迟控制,从而避免数据的丢失
  • 考虑MQ的高可用
    • 不能因为一个节点宕机就整个服务不可用了
  • 能不能支持数据0丢失
  • 能不能支持消息的顺序性
  • 支持各种方式的消息发送比如说延迟消息啊之类的

九、Kafka为什么这么快?

kafka 的快也要分多个方面来说, 从数据发送, 到kafka自己的数据存储, 到数据消费kafka 都做了很多。

  1. 架构上, kafka 采用了分布式设计模式, 支持了水平扩展, 这种模式本身就带来了高吞吐的基础。集群化部署下的kafka集群,可以容纳高并发量的数据写入。 为producer端提供了很好的写能力。
  2. 基于partition的顺序IO给了kafka远超随机IO的磁盘性能,提供了kafka将数据落盘的基础; kafka的每个partition存放消息都是顺序的,每个topic都有一个offset来标识消费的位置,这种情况下,kafka可以将消息顺序写入磁盘,大大提高了写的性能。
  3. 充分利用了操作系统的缓存功能再次提升写性能,kafka中利用 mmap (Memory Mapped Files) 内存映射文件,使用这种方式呢,相当于kafka的写是先写到操作系统的缓存中,然后再由操作系统flush到磁盘上,所以性能非常高
  4. 读消息使用 sendfile 实现零拷贝,提升了性能
  5. 文件本身使用批量压缩,增加了压缩性能