Kafka 消息队列学习 | 青训营笔记

117 阅读6分钟

消息队列

业界消息队列的对比

Kafka :分布式的、分区的、多副本的日志提交服务,在 高吞吐场景 下发挥出色

RocketMQ: 低延迟,强一致,高性能,高可靠,万亿级容量和灵活的扩展性,在 实时场景 运用广泛

Pulsar: 下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体,采用 存算分离 的架构设计

BMQ: 和 pulsar 相似,存算分离。初期定位是承接高吞吐的离线业务场景,逐步替换掉 Kafka 集群。

消息队列 - Kafka

Kafka 使用场景:

  1. 一般用于离线消息处理中,比如日志信息,
  2. Metrics数据:在程序运行中,对程序的状态进行采集,程序qps,运行耗时等等
  3. 搜索服务,直播服务,订单服务,支付服务,满足一些用户行为(搜索,点赞,评论,收藏)

如何使用 Kafka:

graph LR
A[创建集群] --> B[新增topic] --> C[编写生产者逻辑] --> D[编写消费者逻辑]

Kafka 基本概念

Topic : 逻辑队列,不同 topic 可以建立不同的 Partition,所有数据都存储到 topic

Cluster : 物理集群,每个集群中可以建立多个不同的 Topic

Producer : 生产者,负责将业务消息发送到 Topic

Consumer : 消费者,负责消费 Topic 中的消息

ConsumerGroup : 消费组,不同消费组的 Consumer 进度不一样。

partition : topic 的分区,一个 topic 有多个分区,不同分区的消息可以并发处理,提高单个 topic 的吞吐能力。

Topic

一个 Topic 里可以建立不同的 Partitonpartition 内存储不同的消息。

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

image-20230604112321540.png

Partition

每个分区有多个 Replica , 多个 Replica 分布在集群不同的机器上,以此达到容灾的作用,且副本还有不同的角色 (Leader, Fllower),其中Leader Replica 将会从 ISR (红色框内的In-Sync Replicas)中选出

image-20230604130450338.png

消息队列的读写都是从 Leader 开始,然后 Follower 会从 Leader 中拉取消息出来,努力和 Leader 保持一致。 当有的 Follower 和 Leader 之间消息差距过大时,就会被踢出 ISR 。比如上图中的 Replica3

在过往的 kafka 中,差距是根据 Offset 的差距判断了,现在的版本中是根据时间差距来判断的。

如果 Leader 发生宕机,那么可以立刻从 ISR 中选举出新的 Leader 继续服务。这保证了高可用。

Kafka 架构

image-20230219163455038.png

  • Zookeeper:复杂存储集群元信息

Kafa 为什么支持高吞吐?

graph LR
A[Producer] --生产--> B[Broker] --消费--> C[Consumer] 

Producer - 批量发送 + 数据压缩

将多个 Producer 发送的消息,用一个 Batch 存储起来,然后定期发送给节点(Broker),这就是批量发送。批量发送可以减少 I\O 次数,从而加强发送能力。如果遇到消息太大,就会出现网络带宽不够用的情况,这个时候,Kafka通过压缩,减少消息大小。目前支持 Snappy/Gzip/LZ4/ZSTD 压缩算法。默认选择 Snappy 或者 ZSTD

Broker - 数据的存储

image-20230219164320368.png

Broker 里存储了 partition 的副本,而副本以日志形式写入到磁盘上。将日志切分成有序的日志段。每个日志段有四个文件,包含真正的日志文件和偏移量索引文件(offset和真实数据位置的映射)、时间戳索引文件、其他文件。

Broker - 磁盘结构

在机械盘中,写入数据的时候,最耗时的过程是磁头的寻道时间,而Kafka 中采用顺序写的方式写入,提高写入效率,对每条消息都是 末位添加 中途不改变磁头。

如何找到消息?

Consumer 通过发送 FetchRequest 请求消息数据,Broker 会将制定 Offset 处的消息,按照时间窗口和消息大小窗口发送给 Consumer。

因为日志文件的命名是以日志段切分后的第一条数据的Offset命名的。所以对于一条消息,如果我们知道了他的Offset,就可以通过二分来找到他所在的具体位置。

时间戳索引文件相当于在 offset 上加了一层二级索引,首先根据时间戳寻找到文件的 offset,然后再根据 offset 寻找文件。

Broker - 零拷贝

image-20230219165633826.png 传统数据拷贝中分为以下几步:

  1. 从磁盘中将数据读到内核空间中,然后将数据拷贝到应用空间的 ApplicationBuffer
  2. ApplicationBuffer 将数据传送到内核空间的 Socket
  3. 再从 Socket 发送到网卡。

而 kafka 采用了send File技术, 可以从磁盘读到内核空间后,内核空间直接将信息发送到网卡,不经过应用空间,减少了三次拷贝过程,增加了性能。在写的时候,Kafka利用操作系统的 Page 将磁盘文件映射到内存空间,则用户对内存的修改,会在适当的时间被操作系统同步到硬盘上。这个过程叫做 Memory Mapped File 。使用这种方式可以获取很大的I/O提升,省去了用户空间到内核空间复制的开销。Kafka 提供了同步写入和异步写入两种方式。

Consumer

通过手动进行分配,对每一个 Consumer 消费哪一个 Parition 完全由业务决定,但是缺点很明显,当其中一个服务挂掉后,会导致数据中断,也就是不能处理容灾。并且拓展性不好。

kafa 提供了自动分配,他会在不同的消费者集群中选取一台 Broker 作为 Coordinator(协调者) ,其作用就是帮助消费者集群中的消费者自动选取 partition ,这个过程也叫做 rebalanced。这样处理方式下,一旦由服务宕机,则 Coordinator 会自动剔除宕机服务,并且重新分配 Consumer。当有新的服务加入集群的时候,也会对服务进行重新分配。达到一个均衡的结果。

重启操作

kafka的重启节点的操作分为以下步骤:

  1. 关闭并重启一个节点,如果该节点是 Leader 节点。那么集群此时没有 Leader
  2. 于是重新选举一个 Leader ,需要注意的是,此时外部的写入操作是一直没有停止的,则当原来的节点重启后,会处于一个数据落后的装态,
  3. 重启的节点需要从 Leader 那里同步数据,直到 Offset判断为数据同步。
  4. 然后再回切 Leader 将原来的节点重新设置为 Leader

同样的,kafka 的运维中,替换、扩容、缩容都是需要经过以上流程,区别在于同步的数据的多少。这个过程中,消耗的时间成本会比较高。

负载不均衡

如果某一个 Partition 数据比较多,因为一个 Partition 的数据都是写入到一个 boker 中,会导致一个 broker 的数据特别多,为了减少负载,需要将集群中的 Partiton 迁移,而数据迁移会导致 IO 升高,所以需要在两台集群中做平衡。

Kafka问题总结:

  1. 运维成本高
  2. 对于负载不均衡的场景,需要设计复杂的策略来在多台机器中平衡
  3. 没有自己的缓存,完全依赖于 Page Cache, 降低了可拓展性
  4. Controller 和 Coordinator 和 Broker 在同一个进程中,大量的 io e会造成其性能下降。