知识点-消息队列

1.为什么使用消息队列? 消息队列有什么优点和缺点? Kafka、ActiveMQ、 RabbitMQ、 RocketMQ 都有什么优点和缺点?

核心的有 3 个: 解耦、 异步、 削峰

 

 

通过一个 MQ, Pub/Sub 发布订阅消息这么一个模型, A 系统就跟其它系统彻底解耦了。

 

 

缺点有以下几个:
  系统可用性降低:系统引入的外部依赖越多, 越容易挂掉。
  系统复杂度提高:硬生生加个 MQ 进来, 你怎么保证消息没有重复消费? 怎么处理消息丢失的情况? 怎么保证消息传递的顺序性?
  一致性问题:A 系统处理完了直接返回成功了, 人都以为你这个请求就成功了; 但是问题是, 要是 BCD 三个系统那里,BD 两个系统写库成功了, 结果 C 系统写库失败了,这数据就不一致了。
如何选型
  中小型公司, 技术实力较为一般, 技术挑战不是特别高, 用 RabbitMQ 是不错的选择; 大型公司, 基础架构研发实力较强, 用 RocketMQ 是很好的选择。
  如果是大数据领域的实时计算、 日志采集等场景, 用 Kafka 是业内标准的, 绝对没问题, 社区活跃度很高, 绝对不会黄, 何况几乎是全世界这个领域的事实性规范。

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

  RabbitMQ

  有三种模式: 单机模式、 普通集群模式、 镜像集群模式
  普通集群模式(无高可用性):多台机器上启动多个 RabbitMQ 实例, 每个机器启动一个。 你创建的 queue,只会放在一个 RabbitMQ 实例上, 但是每个实例都同步 queue 的元数据。消费的时候, 实际上如果连接到了另外一个实例, 那么那个实例会从 queue 所在实例上拉取数据过来。

 

 

  这种方式没做到所谓的分布式, 就是个普通集群。 因为这导致你要么消费者每次随机连接一个实例然后拉取数据, 要么固定连接那个 queue 所在实例消费数据, 前者有数据拉取的开销,后者导致单实例性能瓶颈。而且如果那个放 queue 的实例宕机了, 会导致接下来其他实例就无法从那个实例拉取, 如果你开启了消息持久化, 让 RabbitMQ 落地存储消息的话, 消息不一定会丢, 得等这个实例恢复了, 然后才可以继续从这个 queue 拉取数据。这方案主要是提高吞吐量的, 就是说让集群中多个节点来服务某个 queue 的读写操作。
 
  镜像集群模式(高可用性):创建的 queue, 无论元数据还是 queue 里的消息都会存在于多个实例上。

 

 

  如何开启这个镜像集群模式呢? 其实很简单, RabbitMQ 有很好的管理控制台, 就是在后台新增一个策略, 这个策略是镜像集群模式的策略, 指定的时候是可以要求数据同步到所有节点的, 也可以要求同步到指定数量的节点, 再次创建 queue 的时候, 应用这个策略, 就会自动将数据同步到其他的节点上去了。
  缺点:性能开销也太大;不是分布式的, 没有扩展性可言。
 

Kafka 的高可用性

  Kafka 一个最基本的架构认识: 由多个 broker 组成, 每个 broker 是一个节点; 你创建一个 topic, 这个 topic 可以划分为多个 partition, 每个 partition 可以存在于不同的 broker 上, 每个partition 就放一部分数据分布式消息队列, 就是说一个 topic 的数据, 是分散放在多个机器上的, 每个机器就放一部分数据。
 
  Kafka 0.8 以后提供了 HA 机制, 就是 replica(复制品) 副本机制。 每个 partition 的数据都会同步到其它机器上, 形成自己的多个 replica 副本。 所有 replica 会选举一个 leader 出来, 那么生产和消费都跟这个 leader 打交道, 然后其他 replica 就是 follower。 写的时候, leader 会负责把数据同步到所有 follower 上去, 读的时候就直接读 leader 上的数据即可。 只能读写 leader? 很简单, 要是你可以随意读写每个 follower, 那么就要 care 数据一致性的问题, 系统复杂度太高, 很容易出问题。Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上, 这样才可以提高容错性。

 

 

 

  写数据的时候, 生产者就写 leader, 然后 leader 将数据落地写本地磁盘, 接着其他 follower 自己主动从 leader 来 pull 数据。 一旦所有 follower 同步好数据了, 就会发送 ack 给 leader, leader 收到所有 follower 的 ack 之后, 就会返回写成功的消息给生产者。
  消费数据的时候, 只会从 leader 去读, 但是只有当一个消息已经被所有 follower 都同步成功返回 ack 的时候, 这个消息才会被消费者读到。
 

3.如何保证消息不被重复消费? 或者说, 如何保证消息消费的幂等性?

  重复消费举例:Kafka 实际上有个 offset 的概念, 就是每个消息写进去, 都有一个 offset, 代表消息的序号, 然后consumer 消费了数据之后, 每隔一段时间(定时定期) , 会把自己消费过的消息的 offset 提交一下, 表示“我已经消费过了, 下次我要是重启啥的, 你就让我继续从上次消费到的 offset 来继续消费吧”。当 直接 kill 进程, 再重启。 这会导致 consumer 有些消息处理了, 但是没来得及提交 offset,重启之后, 少数消息会再次消费一次。
  保证幂等性:
    1.根据主键查一下, 如果这数据都有了, 你就别插入了, update 一下好吧。
    2.写 Redis, 那没问题了, 反正每次都是 set, 天然幂等性。
    3.全局唯一的 id, 类似订单 id。
    4.基于数据库的唯一键。

4.如何保证消息的可靠性传输? 或者说, 如何处理消息丢失的问题?

RabbitMQ

 

 

 

生产者弄丢了数据:选择用 RabbitMQ 提供的事务功能

 
  问题: RabbitMQ 事务机制(同步) 一搞, 基本上吞吐量会下来, 因为太耗性能
  解决:开启 confirm 模式, 在生产者那里设置开启 confirm 模式之后, 你每次写的消息都会分配一个唯一的 id, 然后如果写入了RabbitMQ 中, RabbitMQ会给你回传一个 ack 消息, 告诉你说这个消息 ok 了。 如果 RabbitMQ 没能处理这个消息, 会回调你的一个 nack 接口, 告诉你这个消息接收失败, 你可以重试。 而且你可以结合这个机制自己在内存里维护每个消息 id 的状态, 如果超过一定时间还没接收到这个消息的回调, 那么你可以重发。
  Tips:事务机制和 confirm 机制最大的不同: 事务机制是同步的,confirm 机制是异步的。
RabbitMQ 弄丢了数据:开启 RabbitMQ 的持久化。
 步骤:
  1.创建 queue 的时候将其设置为持久化
  2.第二个是发送消息的时候将消息的 deliveryMode 设置为 2
消费端弄丢了数据:ack 机制
  关闭 RabbitMQ 的自动 ack, 可以通过一个 api 来调用就行, 然后每次代码里确保处理完的时候, 再 ack 一把。

 
Kafka
  Kafka 弄丢了数据:Kafka 某个 broker 宕机, 然后重新选举 partition 的 leader。此时其他的 follower 刚好还有些数据没有同步, 结果此时 leader 挂了, 然后选举某个follower 成 leader 之后, 不就少了一些数据?
  设置如下 4 个参数:
    1.给 topic 设置 replication.factor 参数: 这个值必须大于 1, 要求每个 partition 必须有至少 2 个副本。
    2.在 Kafka 服务端设置 min.insync.replicas 参数:要求一个 leader至少感知到有至少一个 follower 还跟自己保持联系
    3.在 producer 端设置 acks=all: 这个是要求每条数据, 必须是写入所有 replica 之后, 才能认为是写成功了。
    4.在 producer 端设置 retries=MAX, 这个是要求一旦写入失败, 就无限重试, 卡在这里了。
  生产者弄丢数据:在 producer 端设置了 acks=all, 一定不会丢
  消费端弄丢了数据:Kafka 会自动提交 offset, 那么只要关闭自动提交 offset,在处理完之后自己手动提交 offset, 就可以保证数据不会丢。 但是此时确实还是可能会有重复消费, 比如你刚处理完, 还没提交 offset, 结果自己挂了, 此时肯定会重复消费一次, 自己保证幂等性就好了。

5.如何保证消息的顺序性?

  如:数据库的增改删顺序;RabbitMQ的多Consumer;kafka的多个线程来并发处理消息
  解决:RabbitMQ拆分多个 queue, 每个 queue 一个 consumer或一个queue 但是对应一个 consumer, 然后这个 consumer 内部用内存队列做排队, 然后分发给底层不同的worker 来处理。
kafka
  • 一个 topic, 一个 partition, 一个 consumer, 内部单线程消费, 单线程吞吐量太低, 一般不会用这个。
  • 2.写 N 个内存 queue, 具有相同 key 的数据都到同一个内存 queue; 然后对于 N 个线程, 每个线程分别消费一个内存 queue 即可, 这样就能保证顺序性。

 

 

 

6.如何解决消息队列的延时以及过期失效问题? 消息队列满了以后该怎么处理? 有几百万消息持续积压几小时, 说说怎么解决?

  假设一个场景:消费端出故障了, 然后大量消息在 mq 里积压,怎么处理?
  • 临时紧急扩容:新建一个 topic, partition 是原来的 10 倍,然后写一个临时的分发数据的 consumer 程序进行消费
  mq 中的消息过期失效了?RabbtiMQ 是可以设置过期时间的, 也就是 TTL。批量重导
  消息队列满?临时写程序, 接入数据来消费, 消费一个丢弃一个
 

7.如果让你写一个消息队列, 该如何进行架构设计? 说一下你的思路。

  • mq 支持可伸缩性, 就是需要的时候快速扩容, 就可以增加吞吐量和容量?设计个分布式的系统, 参照一下 kafka 的设计理念, broker -> topic -> partition, 每个 partition 放一个机器, 就存一部分数据。 如果现在资源不够了, 给 topic 增加 partition, 然后做数据迁移,增加机器, 提供更高的吞吐量了。
  • mq 的数据持久化,使用顺序写, 这样就没有磁盘随机读写的寻址开销, 磁盘顺序读写的性能是很高的, 这就是 kafka 的思路。
  • mq高可用:多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。
  • 数据 0 丢失:kafka 数据零丢失方案,acks=all

posted @ 2020-11-23 11:23  叮叮007  阅读(164)  评论(0编辑  收藏  举报