消息队列之Kafka|青训营笔记

161 阅读7分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记。下面对之前学习的消息队列之Kafka进行知识总结。

消息队列的必要性

  1. 系统崩溃,导致存储服务发生故障,如果拥有消息队列,消息也会顺利发送消息队列中,不会影响整个搜索流程。
  2. 如果服务处理能力有限,面对庞大的请求量,我们可以通过削峰,即先将请求送入消息队列中,然后每次只获取10个请求进行处理。
  3. 链路耗时长尾,我们可通过异步操作进行改善。
  1. 日志丢失,我们可以先将日志放入消息队列中,通过专门组件将日志写入到搜索引擎中如ES,通过Kibana这种展示平台,对日志进行分析。

消息队列

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

队列的发展历程: img

其中比较代表性的:

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

其中,我主要总结Kafka的相关知识和应用。

Kafka

使用场景:用于离线的消息处理当中

  1. 日志信息
  2. Metrics数据 程序在运行过程中对程序状态的采集 QPS 服务查询 写入的耗时等
  3. 用户行为 搜索点赞评论收藏

如何使用Kafka:

  1. 创建kafka集群
  2. 新增Topic 设置分区数量
  3. 编写生产者逻辑:引入kafka的SDK 实现上游的生产逻辑:把Hello world发送到Topic当中
  4. 编写消费者逻辑:把hello world通过pull从队列中拉取出来进行业务处理。

Kafka基本概念

image.png

Topic:逻辑队列,每一个不同的业务场景都可以作为一个Topic 所有数据都存储在Topic当中

  • Partition:Topic的一个分区,Topic有多个分区,不同分区之间消息可以并发处理,提高单个Topic的吞吐能力。
  • Offset:消息在partition内的相对位置信息,可以理解为唯一ID,在 partition内部严格递增。

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

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

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

ConsumerGroup:消费者组,不同组Consumer消费进度互不干涉

Replica

Partition中还有多个Replica,即副本。

这些副本会分布在集群中不同的机器上,以此来达到我们容灾的作用。

Leader:对外进行写入、读取的作用。(与外面生产者及消费者的接口)

Follower:从Leader把数据拉取下来,努力和Leader数据保持一致的状态。

ISR(In-Sync Replicas, 同步副本):将Follower与Leader进行配置

如果Follower的数据与Leader差距达到一定数值,则将Follower从ISR中踢出来。

配置:

  1. 通过Offset来进行判断(较早)
  2. 通过时间判断(如仅允许有10s的差距)

作用:如果Leader所在的副本出现了宕机,那么我们可以将ISR中选择一个副本让其成为Leader继续进行为生产者及消费者服务。

数据复制

img

Broker:Controller 负责对集群中的所有副本以及broker进行分配。

kafka架构

img

ZooKeeper:和Broker:Controller配合,负责存储集群信息,分区信息等等。

Kafka提高吞吐或者稳定性的功能

  • Producer:

    • 批量发送:可以通过producer-批量发送 减少IO次数,加强发送能力。
    • 数据压缩:通过压缩,减少消息大小,目前支持Snappy、Gzip、LZ4、ZSTD(计算性能、压缩率更好) 压缩算法
  • Broker:

    • 顺序写:采用顺序写的方式进行写入,以提高写入效率

    • 消息索引

      偏移量索引文件:Consumer通过发送FetchRequest请求消息数据,Broker 会将指定Offset 处的消息,在对应Partition中通过二分找到小于目标Offset的最大文件,按照时间窗口和消息大小窗口发送给Consumer

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

    • 零拷贝

      传统拷贝:数据需要从磁盘拷贝出来到内核空间,然后把数据发送到应用空间,然后把数据发送给Socket Buffer,然后再发送给NIC Buffer(网卡内存),然后再发送给消费者进程。

      零拷贝:数据需要从磁盘拷贝出来到内核空间,然后把数据直接发送给NIC Buffer(网卡内存),然后再发送给消费者进程。省去了三次数据拷贝过程。

  • Consumer: Rebalance

    • 手动分配(Consumer-Low Level):

      优点:速度快。

      1. 如果一个Consumer意外故障,那么对应的Partition数据流就会直接断掉。无法自动容灾。
      2. 执行业务过程中发现Consumer能力不够临时增加一个Consumer,就需要将其他Consumer停下来来将一些partition分配给新Consumer,这样也会导致数据流的中断。
    • 自动分配(Consumer-High Level)

      在Broker运行过程中,对于不同的consumer Group,寻取一个Broker来作为Coordinator(协调者),帮助consumer Group中consumer进行自动分配partition。

      Consumer Rebalance:

      img

      1. 一开始Consumer需要找到Consumer group的Coordinater,于是向Broker Cluster发出FindCoordinaterRequest,然后Cluster会根据Broker当前负载会分配一个Broker。
      2. Consumer向coordinater发出一个请求 joinGroupRequest,需要加入到这个group当中。
      3. coordinater收到请求后,会从当前Consumer当中选取一个作为Leader,作用是计算分配策略(原因:Kafka有自己一个默认分配方式,但是某些业务有自己的业务特性,希望某些Partition分配到特定的Consumer上,也给业务提供一个接口实现自己的分配方式)
      4. Consumer发送SyncGroupRequest 让Coordinater给同步整个集群的分配方案。(其中Leader Consumer会同步携带一个分配方案),然后Coordinater将分配方案发送给所有Consumer
      5. Consumer给Coordinater发送heartbeatRequest,如果某个Consumer一定时间内还没有发送heartbeatRequest,那么Coordinater认为该Consumer在Group已经死掉了,将其踢出。

Kafka一些问题和弊端

  • 重启场景下:

Leader回切:假如我们有100个Broker,如果需要对1-99个Broker都进行重启,那么如果不回切就会导致所有Leader都到了100的Broker上,导致100这个Broker需要请求所有的读写符,压力非常大。所以要保持负载均衡。

注意:不可以进行并发重启Broker,可能对于某个Partition他所有的Replica都包含在这些要重启的Broker上,导致该集群的这个Partition处于不可用的状态,影响整个Topic的可用性。

并且,在重启之后,相应follower只需要追赶一小段数据即可,但是替换、扩容、所有数据都为空,需要从0开始追赶。缩容情况类似。

最终导致时间成本巨大。

  • 负载不均衡:

img

最左边Broker负载过高,于是将Partition3迁走,目的是降低Broker所机器的IO负载,但是迁出Partition3又会引来复制操作,导致IO负载再次升高。(死循环)

总结:

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