消息队列?你应该从这几个方面入手

174 阅读5分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

大家好!我是炭治,今天来说说消息队列常见的问题,对于新入门的同学,可以从这几个方面来入手研究。

1. MQ

1.1 MQ 应用场景、优缺点

应用场景:

  • 解耦
  • 异步
  • 削峰

缺点:

  • 系统可用性降低,外部依赖增多
  • 系统复杂性提高,重复消费、丢消息、消息顺序消费...
  • 一致性问题

1.2 技术选型

  • ActiveMQ

    • 吞吐量:万级
    • 时效性:ms
    • 可用性:高,基于主从架构
    • 消息可靠性:较低概率丢数据
  • RabbitMQ

    • 吞吐量:万级
    • 时效性:微秒
    • 可用性:高,基于主从架构
    • 消息可靠性:
  • RocketMQ

    • 吞吐量:10万级,topic 可达几百、几千,吞吐量只有较小幅度下降
    • 时效性:ms
    • 可用性:非常高,分布式架构
    • 消息可靠性:经过参数优化配置,可做到 0 丢失
  • Kafka

    • 吞吐量:10万级,topic 从几十到百千,吞吐量会大幅下降
    • 时效性:ms
    • 可用性:非常高,kafka 本身是分布式的,一个数据多个副本
    • 消息可靠性:经过参数优化配置,可做到 0 丢失
    • 优点:提供较少的核心功能和超高的吞吐量,ms级的延迟,极高的可用性和可靠性,分布式儿可 任意扩展
    • 缺点:有可能消息重复消费

1.3 如何保证高可用

  1. RabbitMQ

    模式:单机、普通集群、镜像集群

  • 普通集群模式

    多机多实例,每个机器启动一个。创建的 queue 只会放在一个实例上,每个实例都同步 queue 的元数据(queue 所在实例信息等)。

    消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据。

    没做到所谓的分布式,就是普通集群,主要提高吞吐量,让集群中多个节点来服务某个 queue 读写, 没有所谓的高可用。

  • 镜像集群模式

    创建的 queue 无论元数据,还是 queue 里的消息都会存在多个实例上,写消息的时候,会自动同步 到多个实例 queue 里

    坏处:1. 新能开销大,消息同步所有机器,网络带宽压力和消耗很重; 2. 没有扩展性可言,所有机器 数据同步,增加实例也没法解决某个 queue 的消息负载问题。

  1. Kafka

    基本架构:多个 broker 组成,每个 broker 是一个节点,创建一个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 存放一部 分数据。

    天然分布式消息队列,一个 topic 的数据是分散在多个机器上的,每个机器存放一部分数据。

  • 提供 HA 机制

    就是 replica 副本机制,每个 partition 的数据都会同步到其他机器上,形成多个 replica 副本,然后所有的 replica 会选举一个 leader 出来,其他的 replica 是 follower,写的 时候,leader 负责数据同步;读的时候,直接从 leader 读取。

    消费的时候,只会从 leader 去读,但是只有一个消息被所有 follower 都同步成功返回 ack 的时候,消息才会被消费者读到。

1.4 如何保证消息可靠性传输

丢数据场景:1. MQ 自己弄丢的;2. 消费的时候弄丢了。

  1. RabbitMQ

    • 生产者丢数据

      发数据的时候数据丢失(网络等原因),可以选用 RabbitMQ 的事务功能(channel.txSelect), 发送消息,如果没有被 MQ 接收到,生产者会收到异常报错,回滚事务(channel.txRollback), 重试,成功则提交事务(channel.txCommit)。事务机制是同步的,会降低吞吐量,比较耗性能。

      一般开启 confirm 模式,在生产者设置开启,每次写消息就会分配一个唯一id,写 MQ 成功后 回传 ack 消息,失败则回调 nack 接口。异步

    • MQ 丢消息

      开启 RabbitMQ 的持久化:创建 queue 设置持久化,发消息时将 deliveryMode 设置为 2,将消息持久化

      持久化 + confirm, 持久化完成后在通知 ack

    • 消费者丢数据

      使用 RabbitMQ 提供的 ack 机制,关闭自动 ack,通过 api 自己调用 ack

  2. Kafka

    • 消费者丢数据

      唯一可能导致丢消息的情况:消费到消息,消费者端自动提交 offset,让 Kafka 以为消费了, 但是此时系统挂了,消息就丢了。

      解决:关闭 offset 改为自动提交,会有消息重复消费,需要保证幂等(刚处理完挂了,还没提交 offset,重启 后又消费一次),

    • MQ 丢消息

      Kafka 某个 broker 宕机,从新选举 partition 的 leader 时,刚好有些 follower 的 数据没有同步,导致丢消息。

      解决: topic 设置 raplication.factor 参数,值 > 1,要求每个 partition 至少 2 个副本;

      服务端设置 min.insync.replicas 参数,值 > 1,要求一个 leader 至少感知到 1 个 follower;

      producer 设置 ack=all,保证每条数据都写入 replica;

      producer 设置 retries=MAX,失败无限重试。

    • 生产者丢数据

      按上面设置 ack=all,一定不会丢消息

1.5 如何保证消息顺序消费

顺序错乱场景:

  1. RabbitMQ: 一个 queue,多个 consumer
  2. Kafka:一个 topic,一个 partition, 一个 consumer,内部多线程处理

如何保证顺序:

  1. RabbitMQ:

    拆分多个 queue,每个 queue 一个 consumer; 或者一个 queue 对应一个 consumer,然后 consumer 内部使用内存队列做排队,分发到底层不同的 worker 处理。

  2. Kafka: 一个 topic,一个 partition,一个 consumer,内部使用单线程消费,些 N 个内存队列,然后 N 个线程 分别消费一个内存队列即可

1.6 生产消息大量积压

修改原来 consumer 逻辑,不处理消息,只是将原来的消息转发到新的临时的多个 queue/partition,增加消费者节点, 相当于是临时扩大 queue 和 consumer 的资源,等积压消息消费完成后,再恢复原来架构。

1.7 MQ 架构设计

  • 可伸缩性
  • 持久化
  • 可用性
  • 数据 0 丢失