0%

面试系列(一)消息队列

Linux


应用场景(使用场景,项目具体使用场景,MQ 类型)

  • 解耦 (一个系统调用多个系统,或者多个系统相互调用,并且不需要同步调用,使用 MQ 做解耦)
  • 异步 (一个系统调用完,有多个耗时动作,使用 MQ 异步调用加快运行效率)
  • 削峰 (一段时间内有大量请求涌入系统,并且同时写入数据库,导致系统崩溃)

缺点

  • 系统可用性减低 (MQ 一旦出问题,与之关联的系统都会出现问题)
  • 系统复杂度变高 (需要考虑数据幂等,消息顺序性,数据积压在 MQ 中,如何快速消费等)
  • 数据一致性问题 (一个请求有多个动作,其中一个动作失败,但是整个请求返回成功,导致一致性问题)

MQ 技术选型(Kafka,ActiveMQ,RabbitMQ,RocketMQ)

ActiveMQ RabbitMQ RocketMQ Kafka
单机吞吐量 万级 万级 十万级 十万级(吞吐量最高)
topic 数量对吞吐量的影响 topic 可以到几百,几千个的级别吞吐量会小幅降低 topic 从几十个到几百个的时候,吞吐量会大幅度下降
时效性 ms 微秒级(延时最低) ms 级 ms 级
可用性 高,基于主从实现 高,基于主从实现 非常高,分布式架构 非常高,分布式架构,一个数据多个副本
消息可靠性 有较少概率丢失数据 经过参数优化可做到不丢失数据 经过参数优化可做到不丢失数据
核心特点 功能极其完备 基于 erlang 开发,并发高,性能好,延时低 功能较为完备,分布式,拓展性好 功能较为简单,在大数据领域的实时计算以及日志采集被大规模使用
总结 成熟,功能强大。偶尔会出现丢失消息,社区不活跃 性能好,延时低,管理界面好用。但是吞吐量比较低,erlang 源码定制比较困难,比较难定制,集群动态拓展麻烦 接口简单易用,吞吐量高,使用 java 开发,容易定制,分布式架构,拓展性好 功能较少,但吞吐量高,易于拓展,适用于大数据

高可用

  • RabbitMQ(集群)

    1. 单机模式

    2. 普通集群模式

      多个 RabbitMQ 实例,元数据(配置信息)所有实例中都存在,实际消息数据只保存在主节点中。

      消费者要在从节点中消费数据,实际上是从节点向主节点拉取数据,再让消费者消费。

      缺点:

      • 集群内部会存在大量数据传输;

      • 可用性几乎没有保障(如果主节点数据丢失,导致整个集群无法消费)

    3. 镜像集群模式

      元数据和消息数据在所有节点都存在,生产者想一个节点生产数据,会同步到所有节点中。

      缺点:

      • 实际不是分布式,消息数据过于庞大,超过服务器容量,无法解决!
  • Kafka(分布式)

    1. 每一个 Kafka 在每台服务器上启动一个Broker进程。

    2. 在 Kafka 集群中创建一个Topic,指定其Partition的数量为 3,则可认为每个 Partition 承载该 Topic 的一部分数据,并且分布在 3 台服务器上,生产者向 Topic 生产数据会分布在 3 个 Partition 中,分布在不同的服务器上。

    3. 通过Replica 副本机制实现高可用。每个 Partition 都有各自的 Replica 副本,每个 Partition 可能有多个 Replica 副本,实际上 Partition 也是一个副本,分布在其他服务器上。

    4. Partition 下所有副本会选举一个 Leader 和多个 Follower,其中只有 Leader 能对外提供读写(生产者只能往 Leader 里面写数据,写数据到 Leader 后,Leader 会将数据同步到 Follower)

    5. 高可用体现在当 Leader 宕机后,会从 Follower 中选举出新 Leader。

幂等性(重复消费/消息的重复发送)

  • Kafka(为什么会出现消息重复消费)
    1. 生产者生产的消息都带有一个 offset,代表消息的顺序(进入 Kafka 的顺序)
    2. 消费者按照 offset 的顺序消费,定期会提交 offset(告诉 Kafka 该 offset 的数据已经消费了,基于 zk 实现)
    3. 因为 offset 是定期提交的,消费者重启会导致期间的 offset 不会被提交,Kafka 不会知道消息已经被消费,消费者重启完成后 Kafka 再次发送那条没有提交 offset 但是重复的消息。
  • 保证幂等的几个方法
    1. Set
    2. Redis
    3. 消息中带着全局唯一 ID
    4. 基于数据库唯一键

数据丢失问题

  • RabbitMQ

    1. 生产者生产消息由于网络传输问题或者 RabbitMQ 内部出错造成数据丢失
    2. 消息已经保存至 RabbitMQ 内存中,但消费者消费之气 MQ 挂掉,造成数据丢失
    3. 消费者已经收到消息,但是没处理就挂掉,RabbitMQ 以为已经处理完,造成数据丢失

    解决办法:

    • 生产者丢失数据
    1. 基于 RabbitMQ 提供的事务功能(同步机制,性能不好)

      1
      2
      3
      channel.txSelect // 开启事务
      channel.txCommit // 提交事务
      channel.txRollback // 回滚事务
    2. 设置 channel 为 confirm 模式(异步机制,回调)

      在 RabbitMQ 收到消息后,会回调生产者的一个接口,通知生产者收到消息。如果接受失败,也会回调接口通知生产者失败。

    • RabbitMQ 丢失数据
    1. 持久化磁盘(queue 设置持久化;消息的 deliveryMode 设置成 2,设置消息持久化)
    2. 出现消息在内存,没持久到磁盘的情况,也会小概率造成小部分消息丢失。
    • 消费者丢失数据(消费者打开 autoAck 机制)
    1. 关闭 autoAck,手动发送 ack
  • Kafka

    • 消费者丢失数据
    1. 关闭 Kafka 自动提交 offset,手动提交 offset
    • Kafka 丢失数据
    1. leader 没有将数据同步给 fellower 就挂掉,选出新 leader 后,数据就丢了
    2. 设置 replication.factor > 1 (每个 partition 至少有两个副本)
    3. 设置 min.insync.replicas > 1 (leader 至少要跟 1 个 fellower 一直联系到)
    4. 生产者设置 acks=all (每条数据必须写入所有 replica 才算成功)
    5. 生产者设置 retries=MAX (写入失败,无限重试)
    • 生产者丢失数据
    1. 设置 acks=all,retries=MAX 就不会丢数据

消息顺序性

  • 顺序出错的场景
  1. RabbitMQ (一个 queue,多个消费者)
  2. Kafka (一个 Topic 一个 Partition 一个消费者内部多线程消费消息)
  • 解决办法
  1. RabbitMQ:每个消费者使用一个 queue
  2. Kafka:每个线程一条内存队列,需要顺序的数据设置同一个 id,每条数据 id 都进行 hashcode,需要顺序的数据进入相同的队列

消费端问题

  • 消息队列延时以及过期失效
  1. 生产环境不能设置过期时间
  2. 手动重新导入 MQ,再消费
  • 消息队列满了,怎么办
  1. 消费到的消息直接写到临时队列接着消费
  2. 修改消费者,直接丢掉,快速消费消息,问题解决后重新导入 MQ,再消费
  • 几百万消息积压几个小时,怎么快速消费积压消息
  1. 尽快修复消费者的问题,确保恢复消费速度
  2. 新建一个 Topic,partition 是原来的 10 倍,临时建立原来 10 倍或 20 倍的 queue 数量
  3. 将原来的消费者修改为直接写入消息到新 Topic,再部署 10 倍数量的消费者消费新 Topic 里面的消息,消费完积压消息就能恢复原来的消费者正常消费消息。

如何设计一个消息队列

  • 分布式(增加吞吐量和容量)

    参考 Kafka,broker->topic->partition,通过调整个组件的数量,完成数据迁移,实现分布式。

  • 持久化

    参考 Kafka,顺序读写

  • 可用性

    参考 Kafka,多副本,leader->fellower 机制,leader 挂了重新选举

  • 数据可靠性,怎么做到数据零丢失