消息队列六个常见问题

261 阅读10分钟

1、如何做消息队列技术选型

消息队列主要有三个使用场景:解耦、异步、削峰。

  • 解耦:有些功能模块之间需要降低耦合度。比如A模块产生的数据,B模块需要调用,C模块也需要调用,A模块就要调用B模块和C模块的接口,把数据传给他们。过段时间D模块也需要获取A模块产生的数据,A模块就要修改代码调用D模块的接口。A模块就要改来改去。当然也可以设计一种注册回调机制,保证其他模块把自己的RestFull接口注册一下,A模块自动调用,但是A模块无法保证接口的调用成功。万一被调用的接口出现问题,数据传输失败,A模块就要设计一系列的消息机制保证消息到达。所以 这个就可以增加一个消息队列,来实现解耦。把数据放到消息队列中,A模块只需要保证消息已经放到消息队列即可。其他模块都从消息队里中获取数据即可。

  • 异步:异步是指有些场景服务器需要多个步骤完成任务,步骤A需要200ms,步骤B需要350ms,步骤C需要100ms,这样服务器响应至少要650ms。此时可以把消息放在MQ中,就直接返回消息给前台。后台异步的从MQ中取出消息进行处理。

  • 削峰:在某个时间段会有大量的请求进来。比如一些定时秒杀活动。此时如果不进行限制,可能会导致大量的数据进到数据库中,此时数据库来不及处理最终宕机。从而导致服务宕机。这种情况可以通过MQ来存储用户的请求。数据洪峰过来之后先把数据存在MQ中,此时MQ就像一个水库,把请求的洪峰蓄起来,下游再慢慢消费。从而避免洪峰情况下数据库层消息处理速度过慢情况

    目前常用的消息队列有activeMQ,RabbitMQ,RocketMQ,Kafka。时效性、可用性,消息可靠性,核心特点。

  • activeMQ 也是一个比较成熟的消息中间件,功能强大,消息并发量万级,偶尔有消息丢失情况,但是目前使用的公司越来越少,社区活跃度比较低,版本更新也比较慢。

  • RabbitMQ: 消息并发量也是万级,延时是微秒级,但是社区活跃度非常高,版本更新比较频繁。提供非常丰富的功能,有个关键点是提供了一个特别好用的管理界面。但是它是使用erlang语言开发的,性能极高,一般国内公司没有能力维护。不过这个也不用担心,因为社区活跃度高。在短时间内没有停滞的迹象

  • RocketMQ: 是阿里开源一个分布式的消息队列中间件,吞吐量可达到十万级,延时毫秒级,功能也是比较强大,并且经历过阿里的业务考验的。目前社区活跃度还行。风险就是如果阿里以后抛弃了这个项目,并且自己公司没有能力维护修复对应bug的话,那可能就比较危险。topic达到几百上千时性能会略有下降。可用性非常高

  • Kafka: 最大的有点就是吞吐量高,吞吐量可达十万级别,延时毫秒级,功能比较单一,提供比较核心的MQ功能。topic达到几百上千的时候,吞吐量会大幅下降。可用非常高。用于实时计算和大规模的日志采集被大规模使用。大数据处理的的标准。

    综上,如果公司的业务技术能力比较强,并且业务量比较大,推荐使用RocketMQ,否则建议使用RabbitMQ。如果是处理大数据场景的话,肯定考虑Kafka。 结合我们公司当前的业务情况,采用了RabbitMQ作为消息中间件。它功能比较丰富,并且简单易用。响应时间也比较极速,提供的管理界面所以采用它作为消息中间件。

使用MQ之后会带来的新的问题:

  • 1、系统复杂度变高:要保证消息的唯一性,不被重复消费;要保证消息的可靠性,数据不丢失;要保证消息的顺序性,不能乱序。
  • 2、降低了系统的可用性:依赖的外部系统越多,系统的可靠性就越低,万一MQ出现故障,会导致依赖MQ的系统都不可用。
  • 3、数据一致性问题:如果系统需要B、C、D三个系统都处理完成,才算成功,但是只有B、C系统成功,D系统失败,这就不好保证一致性。

2、如何保证消息队列的高可用

  • RabbitMQ:有两种集群模式

    • a、普通集群模式 主节点存放元数据和消息的queue,从节点只存放元数据。元数据是描述数据存放地址,长度等信息的数据结构。其他节点根据元数据找到主节点,然后从主节点上获取数据返回给服务消费者。因为数据只有一份在主节点上,所以并不是高可用模式,仅仅是提升了吞吐量。实现高可用可以采用镜像模式
    • b、镜像集群模式 与普通集群不同的是在一个节点上写数据的时候,这个数据会被同步到其他节点上,有请求时候可以直接返回,不需要从主节点上获取数据,并且主节点挂了,对其他节点不会产生影响。我们是采用这种模式。但是 这种模式也有缺点,因为RabbitMQ不是分布式的,在单节点上的容量满了可能会导致问题。
    • 开启镜像集群模式也比较简单,只需要在提供的管理界面上设置一个镜像集群策略,可指定数据是同步到所有节点,还是到指定数量的节点。
  • Kafka:是分布式的消息队列,可以部署在多台机器上。每台机器上有一个broker进程。每个topic被划分为多个partition,消息过来后会被均匀的分布在三个partition上。这时的队列还不是高可用的,在kafka0.8版本之后,提供了副本(replica)机制,可以设置每个partition的replica数量。这样每个partition就会有多个副本。partition会有一个领导者节点,只有领导者节点提供消息读写功能,flower节点从leader节点pull数据进行同步,成功后会返回一个ack给leader节点,所有flower节点都返回ack之后,leader节点才给生产者返回成功信息。消费者消费数据的时候相同,也是从leader节点获取数据,所有flower节点都返回ack的时候消费者才能读取到消息。如果leader节点挂了kafka会感知到,其他节点会再选举出来一个leader节点继续提供服务。这样就达到高可用。

3、如何解决消息队列数据重复问题

  • Kafka 消息重复的原因:kafka在记录消息时候每条消息都有一个对应的offset,customer在消费一条数据后,会记录当前消费消息的offset,然后上报给kafka,kafka的协调管理者zk会记录当前customer消费的offset。但是因为offset上报并不是实时的,如果消费了一条数据后,如果还没来得及上报就宕机,则下次再消费的时候就会出现重复消费情况。
  • 保证幂等性采用的方案:生产者发送消息时候给消息设置唯一编号,消费者消费这条数据后,把数据id存放在redis中,消费消息之前先从redis中验证一下消息的唯一编号是否存在,如果存在则忽略。如果不存在才进行处理。也可以使用唯一键处理。 这是常用的场景,方法也有很多,要结合自己的业务具体对待。

4、消息队列中数据丢失了怎么办

  • RabbitMQ消息丢失场景:

    • 1、生产者写入消息时候网络传输中丢失,或者在RabbitMQ内部处理的时候出现宕机,就是消息没有能够成功存入RabbitMQ。

      • 解决方案1:消息事务: channel.txSelect try{ 发送消息 }(ex){ channel.txRollBack() } channel.txCommit();
        问题:效率比较低
      • 解决方案2:channel开启confirm模式: 生产者发送消息,RabbitMQ确认发送成功会回调本地的接口通知生产者消息接收成功。如果接受失败会回调接收失败的方法,可以再次发送。 一般都采用confirm模式发送消息,该模式为异步模式,吞吐量比较高。
    • 2、RabbitMQ接收到消息暂存在内存中,还没完成持久化就崩溃了,导致内存中的数据丢失 消息要持久化到磁盘中,

      • 第一、queue要设置为持久化。
      • 第二、消息的liveryMode设置为2,就是将消息设置为持久化的。RabbitMQ会将该消息持久化到磁盘上。下次重启会自动恢复。RabbitMQ突然崩溃可能会存在尚未来得及持久化的数据丢失。这是很少量的一部分数据。
    • 3、消费者消费了消息,但是还没来得及处理自己就挂了,但是RabbitMQ以为消费者已经消费了。下次再消费消息时会从丢掉的下一条数据继续。 这种情况是因为消费者开启了autoAck的机制,就是自动通知RabbitMQ消费成功。此时可能还在业务的逻辑处理中。如果还没有处理完成消费端宕机,会出现消息处理。所以需要关闭autoAck模式,消费成功后手动发送ack给RabbitMQ。

  • Kafka消息丢失场景:

    • 1、消息到达partition leader节点,还没有同步到follower节点上leader节点就宕机了,重新选举的leader节点上没有这条消息,消费端就消费不到这条消息。 需要设置4个参数:

      • a、给topic设置replication.factor:数据必须大于1,要求每个partition至少有2个副本。
      • b、kafka服务器端设置min.insync.replicas参数:必须大于1,参数要求一个leader感知到有至少有一个follower节点跟自己保持联系,才能继续提供服务。保证leader节点宕机还有一个follower能够提供服务。
      • c、producer端设置acks=all,要求每条数据必须写入所有的replica后才认为是写成功。
      • d、producer设置retries=MAX,要求写入失败,就继续尝试。 设置这四个参数后,可以保证生产者消息不会丢失。

5、如何保证消息队列中消息的顺序处理

  • 1、RabbitMQ乱序的场景: 一个queue被多个consumer消费,每个consumer消费速度不一样,可能会出现乱序情况。 解决方案:每个消费者只消费一个queue中的数据,把需要保证顺序的消息放到一个queue中。
  • 2、kafka乱序场景: topic,一个partition被一个consumer中多个线程消费。

6、几千万消息在消息队列中积压几小时,如何快速处理?

  • 1、增加queue数量,写一个新的consumer把原来的数据从queue中取出来,再放入临时准备好的10被或者20被数量的queue中。增加对应数量的consumer进行消费。等消息处理完成后再恢复原来的部署架构,重新用原来的consumer机器消费消息。