乐趣区

关于kafka:Kafka核心原理的秘密藏在这19张图里

导语 | 本文推选自腾讯云开发者社区 -【技思广益 · 腾讯技术人原创集】专栏。该专栏是腾讯云开发者社区为腾讯技术人与宽泛开发者打造的分享交换窗口。栏目邀约腾讯技术人分享原创的技术积淀,与宽泛开发者互启迪共成长。本文作者是腾讯后端开发工程师刘国强。

应用 kafka 能够对系统解耦、流量削峰、缓冲,能够实现零碎间的异步通信等。在流动追踪、消息传递、度量指标、日志记录和流式解决等场景中非常适合应用 kafka。这篇文章次要介绍下 kafka 中的基本概念。

kafka 的整体构造

下图展现了很多对于 kafka 的细节,临时先不必关注:

图中展现出了 kafka 的一些重要组件,接下来一一介绍一下。

(一)Broker

服务代理节点。其实就是一个 kafka 实例或服务节点,多个 broker 形成了 kafka cluster。

(二)Producer

生产者。也就是写入音讯的一方,将音讯写入 broker 中。

(三)Consumer

消费者。也就是读取音讯的一方,从 broker 中读取音讯。

(四)Consumer Group

生产组。一个或多个消费者形成一个生产组,不同的生产组能够订阅同一个主题的音讯且互不影响。

(五)ZooKeeper
kafka 应用 zookeeper 来治理集群的元数据,以及控制器的选举等操作。

(六)Topic

主题。每一个音讯都属于某个主题,kafka 通过主题来划分音讯,是一个逻辑上的分类。

(七)Partition

分区。同一个主题下的音讯还能够持续分成多个分区,一个分区只属于一个主

题。

(八)Replica

正本。一个分区能够有多个副原本进步容灾性。

(九)Leader and Follower

分区有了多个正本,那么就须要有同步形式。kafka 应用一主多从进行音讯同步,主正本提供读写的能力,而从正本不提供读写,仅仅作为主正本的备份。

(十)Offset

偏移。分区中的每一条音讯都有一个所在分区的偏移量,这个偏移量惟一标识了该音讯在以后这个分区的地位,并保障了在这个分区的程序性,不过不保障跨分区的程序性。

简略来说,作为音讯零碎的 kafka 实质上还是一个数据系统。既然是一个数据系统,那么就要解决两个基本问题:

  • 当咱们把数据交给 kafka 的时候,kafka 怎么存储;
  • 当咱们向 kafka 要回数据的时候,kafka 怎么返回。

音讯如何存储 (逻辑层面)

目前大多数数据系统将数据存储在磁盘的格局有追加日志型以及 B + 树型。而 kafka 采纳了追加日志的格局将数据存储在磁盘上,整体的构造如下图:

追加日志的格局能够带来写性能的晋升(毕竟只须要往日志文件前面追加就能够了),然而同时对读的反对不是很敌对。为了晋升读性能,kafka 须要额定的操作。

对于 kafka 的数据是如何存储的是一个比拟大的问题,这里先从逻辑层面开始。

(一)Topic+Partition 的两层构造

kafka 对音讯进行了两个层级的分类,别离是 topic 主题和 partition 分区。

将一个主题划分成多个分区的益处是不言而喻的。多个分区能够为 kafka 提供可伸缩性、程度扩大的能力,同时对分区进行冗余还能够进步数据可靠性。

不同的分区还能够部署在不同的 broker 上,加上冗余正本就进步了可靠性。

(二)Offset

对于追加日志格局,新来的数据只须要往文件开端追加即可。

对于有多个分区的主题来说,每一个音讯都有对应须要追加到的分区(分区器),这个音讯在所在的分区中都有一个惟一标识,就是 offset 偏移量:

这样的构造具备如下的特点:

  • 分区进步了写性能,和数据可靠性;
  • 音讯在分区内保障程序性,但跨分区不保障。

逻辑层面上晓得了 kafka 是如何存储音讯之后,再来看看作为使用者,如何写入以及读取数据。

如何写入数据

接下来从使用者的角度来看看,如何将数据写入 kafka。

(一)整体流程

生产者将音讯写入 kafka 的整体流程如下图:

在生产端次要有两个线程:main 和 sender,两者通过共享内存 RecordAccumulator 通信。

各步骤如下:

  • KafkaProducer 创立音讯;
  • 生产者拦截器在音讯发送之前做一些筹备工作,比方过滤不符合要求的音讯、批改音讯的内容等;
  • 序列化器将音讯转换成字节数组的模式;
  • 分区器计算该音讯的指标分区,而后数据会存储在 RecordAccumulator 中;
  • 发送线程获取数据进行发送;
  • 创立具体的申请;
  • 如果申请过多,会将局部申请缓存起来;
  • 将筹备好的申请进行发送;
  • 发送到 kafka 集群;
  • 接管响应;
  • 清理数据。

在音讯累加器 RecordAccumulator 中来进行缓存,缓存大小通过参数 buffer.memory 配置,默认 32MB。累加器依据分区来治理每一个音讯,其中音讯又被组织成 ProducerBatch 的模式(通过 batch.size 管制大小,默认 1MB),为了进步吞吐量升高网络申请次数,ProducerBatch 中可能蕴含一个或多个音讯。

当音讯不多时一个 Batch 可能没有填满,但不会期待太长时间,能够通过 linger.ms 管制等待时间,默认 0。增大这个值能够进步吞吐量,然而会减少提早。

当生产音讯的速度过快导致缓存满了的时候,持续发送音讯可能会有阻塞或异样,通过参数 max.block.ms 管制,默认 60 秒。

数据达到发送线程创立好申请之后,须要对其进行重新组合,依据须要发送到的 broker 节点分组,每个节点就是一个连贯,每个连贯能够缓存的申请数通过 max.in.flight.requests.per.connection 管制,默认 5。每个申请的大小通过 max.reqeust.size 管制,默认 1MB。

(二)发送形式

音讯的发送有三种形式:

  • 发后即忘(fire and forget):只管发送不论后果,性能最高,可靠性也最差;
  • 同步(sync):等集群确认音讯写入胜利再返回,可靠性最高,性能差很多;
  • 异步(async):指定一个 callback,kafka 返回响应后调用来实现异步发送的确认。

其中前两个是同步发送,后一个是异步发送。不过这里的异步发送没有提供 callback 的能力。

那么生产者发送音讯之后 kafka 怎么才算确认呢?这波及到 acks 参数:

  • acks = 1, 默认值 1,示意只有分区的 leader 正本胜利写入就算胜利;
  • acks=0,生产者不须要期待任何服务端的响应,可能会失落数据;
  • acks=- 1 或 acks=all,须要全副处于同步状态的正本确认写入胜利,可靠性最强,性能也差。

(三)生产者重要参数

如何读取音讯

(一)生产音讯

  • 生产模式

音讯的生产一般来说有两种模式:推模式和拉模式,而 kafka 中的生产是基于拉模式的。消费者通过一直地调用 poll 来获取音讯进行生产,基本模式如下(伪代码):

while(true) {records := consumer.Pull()

     for record := range records {// do something with record}

}
  • 位移提交

kafka 中的音讯都有一个 offset 惟一标识,对于消费者来说,每生产完一个音讯须要告诉 kafka,这样下次拉取音讯的时候才不会拉到已生产的数据(不思考反复生产的状况)。这个消费者已生产的音讯地位就是生产位移,比方:

假如 9527 以后拉取到音讯的最大偏移量且曾经生产完,那么这个消费者的生产位移就是 9527,而要提交的生产位移是 9528,示意下一条须要拉取的音讯的地位。

消费者一次可能拉取到多条音讯,那么就会有一个提交的形式问题。kafka 默认应用的是主动提交,即五秒主动将拉到的每个分区中最大的音讯位移(相干参数是 enable.auto.commit 和 auto.commit.interval.ms)。不过这可能导致反复生产以及数据失落的问题。

先看反复生产:

上一次提交的生产位移是 9527,阐明 9526 及之前的音讯都曾经被生产了;以后这次 pull 拉取到的音讯是 9527、0528 和 9529,因而,这次生产胜利后要提交的惟一就是 9530;消费者以后正在解决音讯 9528,如果此时消费者挂掉,如果此时还没有提交 9530,那么 9527 到 9529 之间的音讯都会被调配到下一个消费者,导致音讯 9527 反复解决。

上面看一下音讯失落。还是下面的图,如果消费者刚拉取到 9527 到 9529 这三个音讯,刚好主动提交了 9530,而此时消费者挂了,那么还没有解决就提交了,导致这三条音讯失落。

(二)分区调配策略

音讯在 kafka 的存储是分多个分区的,那么消费者音讯分区的音讯也就有一个分区调配策略。拿最开始的图来说就是上面 consumer group 这部分:

一共有三个分区,生产组 1 有四个生产组,所以有一个处于闲暇状态;生产组 2 有两个生产组,所以有一个生产组须要解决两个分区。

kafka 消费者的分区调配策略通过参数 partition.assigment.strategy 来配置,有如下几种:

  • Range:依照消费者的总数和分区总数进行整除运算来调配,不过是依照主题粒度的,所以可能会不平均。比方:
  • RoundRobin:将生产组内所有消费者及消费者订阅的所有主题的分区依照字典序排序,而后通过轮询形式这个将分区一次调配给每个消费者。比方:
  • Sticky:这个策略比较复杂,目标是分区的调配尽可能平均,以及调配要尽可能和上次保持一致。

(三)再平衡

消费者之间的协调是通过消费者协调器(ConsumerCoordinator)和组协调器(GroupCoordinator)来实现的。其中一项就是消费者的再平衡。

上面几种状况会导致消费者再平衡的产生:

  • 有新的消费者退出;
  • 有消费者下线;
  • 有消费者被动退出;
  • 生产组对应的组协调器节点发生变化;
  • 订阅的主题或分区产生数量变动。

再平衡会通过上面几个步骤:

  • FindCoordinator:消费者查找组协调器所在的机器,而后建设连贯;
  • JoinGroup:消费者向组协调器发动退出组的申请;
  • SyncGroup:组协调器将分区调配计划同步给所有的消费者;
  • Heartbeat:消费者进入失常状态,开始心跳。

如何存储音讯 (物理层面)

在后面介绍了逻辑层面 kafka 是如何存储数据的,接下来在物理层面持续。还是这张图:

(一)日志文件

kafka 应用日志追加的形式来存储数据,新来的数据只有往日志文件的开端追加即可,这样的形式进步了写的性能。

然而文件也不能始终追加吧,因而,kafka 中的 log 文件对应着多个日志分段 LogSegment。

采纳分段的形式不便对其进行清理。而 kafka 有两种日志清理策略:

  • 日志删除(Log Retention):依照肯定策略间接删除日志分段;
  • 日志压缩(Log Compaction):对每个音讯的 key 进行整合,只保留同一个 key 下最新的 value。
  • 日志删除

日志删除策略有过期工夫和日志大小。默认保留工夫是 7 天,默认大小是 1GB。

尽管默认保留工夫是 7 天,然而也有可能保留工夫更长。因为以后沉闷的日志分段是不会删除的,如果数据量很少,以后沉闷日志分段始终没能持续拆分,那么就不会删除。

kafka 会有一个工作周期性地执行,对满足删除条件的日志进行删除。

  • 日志压缩

日志压缩针对的是 key,具备雷同 key 的多个 value 值只保留最近的一个。

同时,日志压缩会产生小文件,为了防止小文件过多,kafka 在清理的时候还会对其进行合并:

(二)日志索引

日志追加进步了写的性能,然而对于读就不是很敌对了。为了进步读的性能,就须要升高一点写的性能,在读写之间做一点均衡。也就是在写的时候保护一个索引。

kafka 保护了两种索引:偏移量索引和工夫戳索引。

  • 偏移量索引

为了可能疾速定位给定音讯在日志文件中的地位,一个简略的方法就是保护一个映射,key 就是音讯的偏移量,value 就是在日志文件中的偏移量,这样只须要一次文件读取就能够找到对应的音讯了。

不过当音讯量微小的时候这个映射也会变很大,kafka 保护的是一个稠密索引(sparse index),即不是所有的音讯都有一个对应的地位,对于没有地位映射的音讯来说,一个二分查找就能够解决了。

下图就是偏移量索引的原理:

比方要找 offset 是 37 的音讯所在的地位,先看索引中没有对应的记录,就找不大于 37 的最大 offset 是 31,而后在日志中从 1050 开始按序查找 37 的音讯。

  • 工夫戳索引

工夫戳索引就是能够依据工夫戳找到对应的偏移量。工夫戳索引是一个二级索引,现依据工夫戳找到偏移量,而后就能够应用偏移量索引找到对应的音讯地位了。原理如下图:

(三)零拷贝

kafka 将数据存储在磁盘上,同时应用日志追加的形式来晋升性能。为了进一步晋升性能,kafka 应用了零拷贝的技术。

零拷贝简略来说就是在内核态间接将文件内容复制到网卡设施上,缩小了内核态与用户态之间的切换。

非零拷贝:

零拷贝:

kafka 的可靠性

kafka 通过多正本的形式实现程度扩大,进步容灾性以及可靠性等。这里看看 kafka 的多正本机制。

(一)一些概念

下图展现了正本同步的一些重要概念(单个分区视角):

AR: Assigned Replicas

所有的正本统称为 AR。

ISR: In-Sync Replicas

ISR 是 AR 的一个子集,即所有和主正本放弃同步的正本汇合

OSR: Out-of-Sync Replicas

OSR 也是 AR 的一个子集,所有和主正本未保持一致的正本汇合。所以 AR=ISR+OSR。

kafka 通过一些算法来断定从正本是否放弃同步,处于生效的正本也能够通过追上主副原本从新进入 ISR。

LEO: Log End Offset

LEO 是下一个音讯将要写入的 offset 偏移,在 LEO 之前的音讯都曾经写入日志了,每一个正本都有一个本人的 LEO。

HW: High Watermark

所有和主正本放弃同步的正本中,最小的那个 LEO 就是 HW,这个 offset 意味着在这之前的音讯都曾经被所有的 ISR 写入日志了,消费者能够拉取了,这时即便主正本生效其中一个 ISR 正本成为主正本音讯也不会失落。

(二)主正本 HW 与 LEO 的更新

LEO 和 HW 都是音讯的偏移量,其中 HW 是所有 ISR 中最小的那个 LEO。下图展现了音讯从生产者到主正本再同步到从正本的过程:

  • 生产者将音讯发送给 leader;
  • leader 追加音讯到日志中,并更新本人的偏移量信息,同时 leader 也保护着 follower 的信息(比方 LEO 等);
  • follower 向 leader 申请同步,同时携带本人的 LEO 等信息;
  • leader 读取日志,拉取保留的每个 follower 的信息(LEO);
  • leader 将数据返回给 follower,同时还有本人的 HW;
  • follower 拿到数据之后追加到本人的日志中,同时依据返回的 HW 更新本人的 HW,办法就是取本人的 LEO 和 HW 的最小值。

从下面这个过程能够看出,一次同步过程之后 leader 的 HW 并没有增长,只有再经验一次同步,follower 携带上一次更新的 LEO 给 leader 之后,leader 能力更新 HW,这个时候村能确认音讯的确是被所有的 ISR 正本写入胜利了。

leader 的 HW 很重要,因为这个值间接决定了消费者能够生产的数据。

(三)Leader Epoch

思考上面的场景,初始时 leader 以保留了两条音讯,此时 LEO=2,HW=1:

正在上传图片 …

在 sync 1 中 follower 拉取数据,追加之后还须要再申请 leader 一次(sync 2)能力更新 leader 和 follower 的 HW。

这样在更新 HW 中就会有一个间隙,当 sync 1 胜利之后 sync 2 之前 follower 挂掉了,那么重启之后的 HW 还是 1,follower 就会截断日志导致 m2 失落,而此时 leader 也挂掉的话这个 follower 就会成为 leader,m2 就彻底失落了(即便原来的 leader 重启之后也扭转不了)。

为了解决这个问题,kafka 引入了 leader epoch 的概念,其实这就是一个版本号,在 follower 同步申请中不仅仅传递本人的 LEO,还会带上以后的 LE,当 leader 变更一次,这个值就会增 1。

因为有了 LE 的信息,follower 在解体重启之后就不会轻易截断日志,而是会申请最新的信息,防止了上述情况下数据失落的问题。

这篇文章通过简略的语言、简略的图,简略地形容了 kafka 中的一些重要概念。其实 kafka 是一个简单的零碎,须要更多的学习能力深刻理解 kafka。

如果你是腾讯技术内容创作者,腾讯云开发者社区诚邀您退出【腾讯云原创分享打算】,支付礼品,助力职级降职。

退出移动版