消息队列原理与实战-Kafka | 青训营笔记

81 阅读6分钟

四个案例

案例一:系统崩溃

此时会在相关环节卡住

解决方案:解耦(利用消息队列)

案例二:服务能力有限

解决方案:削峰

案例三:链路耗时长尾

解决方案:异步

案例四:日志存储

什么是消息队列?

消息队列(MQ),指保存消息的一个容器,本质是个队列。但这个队列呢,需要支持 高吞吐,高并发,并且高可用

  • 解耦
  • 削峰
  • 异步
  • 日志处理

消息队列前世今生

消息队列发展历程

业界消息队列对比

  • Kafka:分布式的、分区的、多副本的日志提交服务,在 高吞吐场景下 发挥较为出色
  • RocketMQ:低延迟、强一致、高性能、高可靠、万亿级容量和灵活的可扩展性,在一些 实时场景中 运用较广
  • Pulsar:是下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体、采用存算分离的架构设计
  • **BMQ:**和Pulsar架构类似,存算分离,初期定位是承接高吞吐的离线业务场景,逐步替换掉对应的Kafka集群

消息队列-Kafka

使用场景

  • 离线信息,如日志
  • Metrics 数据(程序运行时采集的数据)
  • 用户行为数据

如何使用 Kafka

  • 创建集群
  • 新增Topic
  • 编写生产者逻辑
  • 编写消费者逻辑

基本概念

  • Topic:逻辑队列,不同Topic可以建立不同的Topic
  • Cluster:物理集群,每个集群中可以建立多个不同的Topic
  • Producer:生产者,负责将业务消息发送到Topic中
  • Consumer:消费者,负责消费Topic中的消息
  • ConsumerGroup:消费者组,不同组 Consumer消费进度互不千涉

Offset

Offset:消息在 partition内的相对位置信息,可以理解为唯一ID,在 partition内部严格递增。

Replica

每个分片有多个Replica,Leader Replica将会从ISR中选出。

Leader 是主要负责读和写的,其它 Follower 不断将 Leader 中的消息同步,有点类似 Redis 集群

在 ISR 中,假如消息相差过远,Follower 是会被踢出的,上古版本使用 offset 判断,但是很明显这样做有极大缺陷,所以现在都通过写入时间判断

当 Leader 宕机则换其它 Follower,保证高可用

数据复制

controller 担当了集群大脑的职责,计算分区分配状态并告知集群

Kafka 架构

ZooKeeper:负责存储集群元信息,包括分区分配信息等

Cluster:Kafka 集群,包含多个 Broker

Producer:消息的生产者

Consumer:消费者,一般被分成若干个消费者组

Producer

批量发送

如果发送一条消息并确认之后在发送下一条,不能充分利用 Kafka 的高吞吐,所以考虑将消息打包进 Batch,最后批量发送

这样可以减少 IO 次数加强发送能力

但是由于消息量较大,可能会导致要传输的数据量大,耗尽带宽怎么办?

数据压缩

通过压缩,减少消息大小,目前支持 Snappy、Gzip、LZ4、ZSTD 压缩算法

ZSTD 在性能和压缩率上都比较好,推荐

Broker

数据的存储

Broker 将压缩后数据如何存储到本地磁盘?

Topic——>多个 Partition——>多个 Replica——>多个Log——>切分为LogSegment

LogSegment:

  • .log(日志文件)
  • .index(偏移量索引文件)
  • .timeindex(时间戳索引文件)
  • 其他文件

磁盘结构

移动磁头找到对应磁道,磁盘转动,找到对应扇区,最后写入。寻道成本比较高,因此顺序写可以减少寻道所带来的时间成本,提高写入效率。

如何找到消息

Consumer通过发送FetchRequest请求消息数据,Broker 会将指定Offset 处的消息,按照时间窗口和消息大小窗口发送给Consumer,寻找数据这个细节是如何做到的呢?

偏移量索引文件

采用二分法找到小于目标 Offset 的最大文件

然后二分找到小于目标 offset的最大索引位置(batch 中存有一组信息,所以索引是稀疏的)

时间戳索引文件

在文件和offset之间加入了一级时间戳索引

二分找到小于目标时间戳最大的索引位置,在通过寻找 offset的方式找到最终数据。

数据拷贝

Linux - 零拷贝技术

传统拷贝需要经由 Application Buffer 和 Socket Buffer 才能传给网卡发送

利用 零拷贝 可以跨过这两个环节,直接从 Read Buffer 发给网卡

Consumer

主要需要解决Partition在Consumer Group中的分配问题

Low Level

通过手动进行分配,哪一个 Consumer消费哪一个Partition完全由业务来决定。

缺点:

  • 不能自动容灾(Consumer宕机后,部分分区数据流断掉)
  • 加入新消费者时需要启停较复杂

优点:

提前写好,速度快

High Level

Coordinator(协调者),可以进行 Rebalance 操作:

感知 Consumer 状态,当出现异常时及时剔除,出现新消费者及时加入,并重新分配分区

Rebalance

Kafka Rebalance机制分析

  • 消费者此时手上没有相关信息,会向 Broker(找负载最小的)发送 FindCoordinatorRequest,确认 Coordinator 位置

  • 当多个 Consumer 达成一致时,发送 JoinGroupRequest 请求加入分组,此时协调者收到请求后会在 Consumer 中选取一个 Leader

  • 协调者返回响应,并在指定 Leader 的响应中通知 isLeader=true

  • 此时,Consumer 算加入了组内,但是对组内的分配策略还一无所知,因此还需要一次请求 SyncGroupRequest 来获取相关方案,这一次,Leader 发送的请求中会携带其分配方案

  • 之后,会定期发送心跳,确保 Consumer 处于活动状态,一旦超时无响应则判定为死亡

数据复制问题

Kafka 为了保证高可用,Follower 会从 Leader 上同步数据

假如由于业务需要,我们重启了 Leader

  • 关闭、重启:

    重启后,Leader 会转到一台 Follower 上

  • Leader切换,追赶数据:

    直到相差不大才会认为同步完成

  • 数据同步完成

  • Leader 回切

    这里有一个回切操作,也就是将 Leader 换回原来的节点,这是为了避免我们依次重启节点后所有 Leader 都归到最后一个节点造成过大压力

一台节点重启花费一些时间,所有节点依次重启整个周期将会非常长

而且不能够并发重启,如果在一个只有两分片(一Leader一Follower)的情况下可能造成该 Topic 的使用问题

替换、扩容、缩容

这三种操作都会涉及节点变更操作,不可避免会有数据的复制同步,也会带来时间成本问题

负载不均衡

当某一 Broker 上一个 Partiton 相当大之后,我们为了负载均衡,考虑将别的较小的 Partiton 移动至其它 Broker,但是这样的移动又会造成数据读写带来的负载问题,恶性循环了属于是

总结

有哪些帮助Kafka提高吞吐或者稳定性的功能:

  • Producer:批量发送、数据压缩
  • Broker:顺序写,消息索引,零拷贝
  • Consumer:Rebalance

问题:

  • 运维成本高
  • 对于负载不均衡的场景,解决方案复杂
  • 没有自己的缓存,完全依赖Page Cache
  • Controller 和 Coordinator和Broker在同一进程中,大量IO会造成其性能下降