消息队列基础要点

本文不对 API 进行讨论,主要针对 RabbitMQ、Kafka,需要对这两个 MQ 有一定认识

本文主要讨论消息队列的简单原理,仅针对面试要点,不会过分深入


使用消息队列的常见场景

  • 下单微服务调用库存服务改库存

  • 相关微服务调用日志微服务,进行统计、存储、通知

  • 部分 RPC 可以转为使用 MQ


  1. 解耦:某服务的关键数据发送数据到多个系统使用,且各系统不固定、不一定稳定

  2. 异步:某服务接受到一个请求,需要修改自己的库,同时需要在其他多个服务中写对应的多个库,且十分耗时,拖慢系统响应。一般要求用户响应低于200ms

  3. 削峰:短期内流量高峰


为什么使用(优缺点)

项目中有某个业务场景,该场景有某个技术挑战,如果不使用 MQ 将带来很多问题,但是在使用了 MQ 后,带来了很多好处。

  • 缺点

    • 引入外部依赖导致可用性下降,万一 MQ 挂了

    • 复杂性提高,如何保证一致性(多个系统均成功?)、顺序性、不丢不重


各消息队列对比

特性ActiveMQRabbitMQRocketMQKafka
单机吞吐量万级,吞吐量比 RocketMQ 和Kafka 低了ー个数量级万级,吞吐量比 RocketMQ 和Kafka 低了ー个数量级10万级,RocketMQ也是可以支撑高吞吐的一种MQ10万级,一般配合大数据系统使用,如实时运算、日志采集等
Topic 数量对吞吐量的影响--可达几百、几千级别,有小幅下降,同等机器下可以职称大量topic 在几十到几百个时,吞吐量大幅下降
时效性ms级微秒级(特点)ms级ms级以内
可用性高,主从架构高,主从架构非常高,分布式架构非常高,多副本,少数节点宕机不丢失数据,不影响可用性
消息可靠性有较低概率丢失数据-经过参数优化,可以实现0丢失经过参数优化,可以实现0丢失
功能支持MQ 领域的功能完备基于 erlang,并发性能好,延时低,MQ功能完备MQ 功能较为完善,分布式,可拓展性好功能较简单,支持简单的MQ功能,适合大数据使用
优劣总结成熟、强大,但可能丢数据。
社区、应用案例减少。有被淘汰的倾向
性能好、界面友好,社区相对活跃,互联网散户维护,一般不会凉。
但吞吐量受限。适合中小型公司,不关心源码,关注使用,依赖社区
API 简单,性能好,大规模吞吐,分布式拓展方便,支持大规模的topic,社区尚可,阿里开源。
适合大型互联网公司,万一社区凉了,有实力进行基础架构、自行维护
仅提供较少的核心功能,提供超高吞吐量、可用性、分布式可拓展性。
社区活跃度很高,世界级的大数据行业标准

如何保证高可用

RabbitMQ

三种模式

  • 单机模式

  • 普通集群模式(主备集群),备机只存储元数据,代理消费请求。造成集群内大量数据传输,可用性无保障

  • 镜像集群模式,从节点实际存储完整数据镜像,但不是分布式的,单机存不下所有消息时,可能遭遇瓶颈

Kafka

架构:每个节点是一个 broker,创建一个 topic 会创建多个 partition,每个 broker 存放多个 partition。partition 在集群中存有副本,多个副本中有一个 partition 会被选举为 leader,消费者消费时使用 leader,leader 向 follower 同步数据


避免重复消费(幂等性)

  • Offset:生产者数据进入MQ后,会给每条数据分配一个 offset 表示数据编号,消费者消费数据后,需要提交 offset (Zookeeper)告知已消费的位置。

  • 重复消费原因:消费者在消费后,提交 offset 前进程终止

  • 保证幂等性:使用 Redis 保存已处理的唯一数据 ID


可靠性传输、数据丢失

  • 使用 MQ 传递关键消息时,有可能不允许消息丢失

  • 丢消息原因

    • 生产者传输过程中丢失、MQ 内部出错

    • 消息等待消费的过程中 MQ 终止

    • 消费者处理消息过程中终止

  • 应对方法

    • 生产者丢消息:生产者发送消息时开启事务,但是生产者吞吐量下降

    • 生产者丢消息:生产者配置为 confirm 模式,要求 MQ 接受到消息后确认,使用回调、异步机制保证可靠性

    • RabbitMQ 丢数据:创建 Queue 时,设置为持久化,并把 deliveryMode 设置为2,将消息持久化。极端情况下(持久化期间终止)仍存在一定风险。

    • Kafka 丢数据:数据发到 leader 后宕机,新选举 leader 导致丢数据。 通过下述方法解决

      • topic 设置 replication.factor 大于1、服务端设置 min.insync.replicas 大于1(ISR 机制),确保一个 leader 至少有一个 follower 与自己保持联系

      • 生产者端设置 acks=all 要求每条数据必须写入所有 replication 后才认为成功

      • 生产者端设置 retries=很大的值,只要失败就一直重试

    • 消费者丢数据(Kafka 为 Offset):只在配置了 autoAck 的情况下发送。关闭自动确认,手动发送 ack


消息的顺序性

  • RabbitMQ 生产者:存在多个队列时,难以保证消息的顺序消费,需要生产者将需要顺序消费的消息发送到同一个队列中,确保出队的消息有序。

  • Kafka 生产者:为一系列需要顺序消费的消息分配同一个 key,按照 key 的 hash 进行分发,这些数据会据此发送到同一个 partition,partition 本身有序

  • 消费者:一个 Queue 对应一个 消费者,多个消费者线程时仍可能混乱,需要在消费者内存中使用 Queue 进行再分发。


延时、队列满、积压

  • 大量消息积压,消费者故障,即使消费者恢复也需要很长时间才能完成消费

    • 首先修复 consumer 问题,并将现有的 consumer 都停掉

    • 临时建立 10~20 倍于原架构的 queue 数量

    • 构建临时分发数据的 consumer,部署并对积压的数据进行转发,轮询写入新建立的 queue 中

    • 临时征用 10~20 倍于原架构的节点进行消费,每一倍的 consumer 消费一个 queue 的数据

    • 上述方案可以达到日常 10~20 倍的消费速度

  • RabbitMQ 消息积压,超时清除导致消息丢失

    • 线上环境不配置过期删除,如果配置了,只能手动进行补偿
  • 大量消息积压,MQ 磁盘耗尽,情况紧急,没时间建立临时消费者集群

    • 写一个消费者,迅速进行消费,不对消息进行任何处理。事后对丢失的消息进行手动补偿

如何实现 MQ

  • 支持扩容,分布式MQ

    • 参考 Kafka 的设计,broker - topic - partition

    • 每个 partition 放到一个节点,存放 topic 的一部分数据

    • 扩容时,为 topic 增加 partition

  • 持久化,写磁盘

    • 顺序写磁盘,Kafka 采用,比随机写磁盘快很多
  • 可用性保证

    • 参考 Kafka,多副本、Leader & Follower 集群、broker

    • leader 挂了由 controller 重新推选 leader 即可

  • 不丢失数据

    • 参考上文 Kafka 应对消息丢失的方案(全部 ISR 确认)