插曲:Kafka的HW,LEO更新原理及运行流程总结

5,843 阅读9分钟

前言

虽然这是一个源码的预热篇同样这也是插曲篇的一个总结,我们从一开始的集群的各个角色,到集群的设计,到网络模型,到生产者,消费者都已经提了个遍。这一篇会把最后的一个内核提一下,然后对以往的概念三篇做一个总结,把流程梳理一下,增加记忆的同时,也让后面的源码变得更加轻松愉快,好的这是最后一次鸽了🤣。

以往链接

概念①:插曲:大白话带你认识Kafka

实践的:插曲:Kafka的集群部署实践及运维相关

概念②:插曲:Kafka的生产者原理及重要参数说明

概念③:插曲:Kafka的生产者案例和消费者原理解析

没啥的:插曲:Kafka源码预热篇--- Java NIO

一、补完上一篇Kafka中没提完的

Kafka的生产者案例和消费者原理解析中我们提到kafka的内核里还有个 LEO&HW 原理,现在补充回来。

1.1 LEO&HW更新原理

首先这里有两个Broker,也就是两台服务器,然后它们的分区中分别存储了两个p0的副本,一个是leader,一个是follower

此时生产者往leader partition发送数据,数据最终肯定是要写到磁盘上的。然后follower会从leader那里去同步数据,follower上的数据也会写到磁盘上

可是follower是先从leader那里去同步再写入磁盘的,所以它磁盘上面的数据肯定会比leader的那块少。

1.1.1 LEO是什么

LEO(last end offset)就是该副本底层日志文件上的数据的最大偏移量的下一个值,所以上图中leader那里的LEO就是5+1 = 6,follower的LEO是5。以此类推,当我知道了LEO为10,我就知道该日志文件已经保存了10条信息,位移范围为[0,9]

1.1.2 HW是什么

HW(highwater mark)就是水位,它一定会小于LEO的值。这个值规定了消费者仅能消费HW之前的数据。

1.1.3 流程分析

follower在和leader同步数据的时候,同步过来的数据会带上LEO的值,可是在实际情况中有可能p0的副本可能不仅仅只有2个。此时我画多几个follower(p0),它们也向leader partition同步数据,带上自己的LEO。leader partition就会记录这些follower同步过来的LEO,然后取最小的LEO值作为HW值

这个做法是保证了如果leader partition宕机,集群会从其它的follower partition里面选举出一个新的leader partition。这时候无论选举了哪一个节点作为leader,都能保证存在此刻待消费的数据,保证数据的安全性。

那么follower自身的HW的值如何确定,那就是follower获取数据时也带上leader partition的HW的值,然后和自身的LEO值取一个较小的值作为自身的HW值

现在你再回想一下之前提到的ISR,是不是就更加清楚了。follower如果超过10秒没有到leader这里同步数据,就会被踢出ISR。它的作用就是帮助我们在leader宕机时快速再选出一个leader,因为在ISR列表中的follower都是和leader同步率高的,就算丢失数据也不会丢失太多。

而且我们之前没提到什么情况下follower可以返回ISR中,现在解答,当follower的LEO值>=leader的HW值,就可以回到ISR

可是按照刚刚的流程确实无法避免丢失部分数据的情况,当然也是有办法来保证数据的完整的,咱们留到源码篇之后进行总结的时候再提。

1.1.4 觉得图中字比较多不好看清楚的看这个

二、Kafka的流程梳理

在大白话篇中带过大家画,现在再来一遍。

首先来两个Broker(这集群好歹要超过1个服务器才能叫集群吧),然后它们启动的时候会往zookeeper集群中注册,这时候这两台服务器会抢占一个名字叫controller的目录,谁抢到了,谁就是controller。比如现在第一台Broker抢到了。那它就是controller,它要监听zookeeper中各个目录的变化,管理整个集群的元数据

此时我们通过客户端来用命令来创建一个主题,这时候会有一个主题的分区方案写入到zookeeper的目录中,而在controller监听到这个目录写入了分区方案(其实就是一些元数据信息)之后,它也会更改自己的元数据信息。之后其他的Broker也会向controller来同步元数据。保证整个集群的Broker的元数据都是一致的

此时再比如我们现在通过元数据信息得知有一个分区p0,leader partition在第一台Broker,follower partition在第二台Broker。

此时生产者就该出来了,生产者需要往集群发送消息前,要先把每一条消息封装成ProducerRecord对象,这是生产者内部完成的。之后会经历一个序列化的过程。接下来它需要过去集群中拉取元数据(所以大家知道为啥在 插曲:Kafka的生产者原理及重要参数说明 的 1-⑤-1 生产者代码里面为啥要提供一个或多个broker的地址了吧),当时的代码片段如下

props.put("bootstrap.servers", "hadoop1:9092,hadoop2:9092,hadoop3:9092");

因为如果不提供服务器的地址,是没法获取到元数据信息的。此时生产者的消息是不知道该发送给哪个服务器的哪个分区的。

此时生产者不着急把消息发送出去,而是先放到一个缓冲区。把消息放进缓冲区之后,与此同时会有一个独立线程Sender去把消息分批次包装成一个个Batch。整好一个个batch之后,就开始发送给对应的主机上面。此时经过 大白话篇 中加餐时间所提到的Kafka的三层网络架构模型,写到os cache,再继续写到磁盘上面。

之后写磁盘的过程又要将 Kafka的生产者案例和消费者原理解析 中提到的日志二分查找,和刚刚才提完的ISR,LEO和HW。因为当leader写入完成时,follower又要过去同步数据了。

此时消费者组也进来,这个消费者组会有一个它们的group.id号,根据这个可以计算出哪一个broker作为它们的coodinator,确定了coordinator之后,所有的consumer都会发送一个join group请求注册。之后coordinator就会默认把第一个注册上来的consumer选择成为leader consumer把整个Topic的情况汇报给leader consumer。之后leader consumer就会根据负载均衡的思路制定消费方案,返回给coordinator,coordinator拿到方案之后再下发给所有的consumer,完成流程。

所以这样就把我们的插曲系列中提到的所有的知识点都连起来了,基本上囊括了所有必须知道的知识点。这么大一件事情全部都是分开一篇一篇一个个小的知识点循序渐进地阐述完整的。如果对Kafka感兴趣的朋友,真的推荐把之前的那几篇都可以读一下,相信对你一定有所帮助。

回到源码的那个事儿

源码篇中Java NIO的基础知识(看到没有,基础是重点字,不要求高级的)和scala是两个条件,不过如果是对scala没了解的朋友也不需要担心,它和Java十分类似。相信配合一定的解释的话,一定也可以看懂所有的套路。

三、简单说说环境

Kafka的版本是0.10.1版本,最新的应该是2.2.x了。核心流程都没太大变化,老版本相比新版本更加的稳定,老一点的版本代码结构会更清晰,因为像这种开源的项目,很多人都会去提交一些patch,但是提交patch的人员不一定是最优秀的那一群,会让新的版本代码看起来很混乱,学习起来就自然不太方便了

1.1 JDK1.7+(这个就不说了)

1.2 scala

Kafka一开始出来的时候其源码是用scala写的,但是后面producer端和consumer端的代码用java重写了,但是server端的源码还一直用的scala写的,所以我们分析Kafka的源码需要安装scala的环境。

我使用的是2.11.8版本,下载好配置环境变量即可(通过度娘可以完美完成,和Java的配置也很相像,这里不展开了)。

IDEA需要安装一个scala的插件,在Settings-plugins那里,直接搜索scala即可。

1.3 gradle

Kafka的源码没有采用maven去管理,而是用的gradle,大家就把这个想成是一个类似于maven的代码管理工具即可。安装它的方式跟安装maven一样。

finally

将会讲到的源码

KafkaProducer:
    (难的,这个行了其它都是行的,而且源码写的很优秀,估计要分好几篇慢慢来)
Server:
    (有了上面的Producer之后就不会很难了)
KafkaConsumer:
    (非常基础,不太需要讲,其实大数据的框架关于读数据的部分都不算太难)

如果要一个一个类地去说明,那肯定会非常乱套的,所以要借助场景去阐述巧了,这个场景甚至还不需要我来写。看见源码里面有个example包了吗?大部分的大数据框架都是开源的,为了推广,首先官方文档要写的详细,而且还得自己提供一些不错的示例包才方便。从现在开始,讲解基本依靠代码注释。

下一篇就从这里开始。大家共勉,一起努力