06-Kafka服务端

Kafka服务端

  • 本文内容关于Kafka服务端的核心设计和运行机制,包括协议设计、时间轮、延迟操作和控制器等。

Kafka系列文章是基于:深入理解Kafka:核心设计与实践原理一书,结合自己的部分实践和总结。

一、协议设计

  • Kafka自定义了基于TCP的二进制协议,只要遵守这些协议格式就可以和Kafka Broker进行通信。

  • Kafka2.0.0版本总共定义了43种协议类型,每种类型都有对应的请求和响应。请求包括请求头和请求体,其中请求头的格式是固定的,响应也是一样,响应头格式是固定的,但是请求体和响应体格式会有不一样。

  • Kafka的协议部分,通常不会涉及,但是了解其特定的协议对我们理解其运作原理有很大的帮助。具体可以阅读参考文章[1]的6.1章节以[2]

二、时间轮

2.1 引入

  • Kafka基于时间轮的概念自定义实现了一个用于延时功能的定时器,在JDK中的Timer或者DelayQueue都是O(nlogN)的复杂度,而基于时间轮的插入删除操作时间复杂度为O(1)。在很多高性能框架中都有时间轮的身影,比如Netty、Zookeeper、Akka等。

2.2 数据结构

  • 时间轮(TimeWheel)是一个基于数组实现的环形队列,其每一个元素存储的是定时任务列表(TimerTaskList),该列表是环形双向链表结构,每一个节点保存一个定时任务项(TimerTaskEntry)。时间轮的每一个数组元素代表当前时间轮的基本时间跨度(tickMs),数组长为wheelSize,因此总时间跨度interval是wheelSize*tickMs,另外有一个表盘指针(currentTime)指示当前时间轮所处的时间,指针将时间轮分为到期(含指针所指任务列表)和未到期2部分。

在这里插入图片描述

  • 一个时间轮能够表示的时间跨度是有限的,其限制interval=wheelSize*tickMs,如果有延时任务其延时值大于上限值如何处理?Kafka的时间轮采用类类似于是在的多层的思想,比如第一层wheelSize为20,tickMs为1ms,那么第一层的时间轮只能表示1-20ms的延迟任务,对应的第二层的时间轮,tickMs为第一层的interval,wheelSize不变,因此第二层的interval=20m乘以20=400ms,同理第三层为400ms乘以20为8000ms,上一层的最小单位tickMs为下一层的总interval。这和时钟是一个思想,秒钟的interval是60S为分组的一个单位,分钟的interval是时钟的一个单位。(不完全一致但非常类似),由此通过多层时间轮就可以表示延迟时长非常大的任务。

2.3 原理详解

  • 如果一个任务的延迟时间是10ms,那么则会放在第一个时间轮的第10个时间格对应的列表中,如果延迟比较大,就会放在高层的时间轮的对应位置。
  • 当时间流逝,currentTime指针会往前移动,等到10ms后就会指向时间轮的第10个时间格,对应的延迟任务会被执行。而对于高层时间轮的任务,会进行降级,比如一个延时任务是450ms,那么最初他会放在第三层的第一个格子(20+400+30),假设过去了40ms,那么410ms后需要执行这个任务,他就会降级放在第二层的第20个格子,(410=20+20*19+10),一直到最后会降级到第一个时间轮然后被执行。
PS:对降级而言,不是每一次都必须要检查,因为下一层的跨度(interval)是上一层的一个时间格所代表的的时间(tickMs),因此下一层轮转完毕一周之后上一层最多回退一格,因此下一
层轮转一周再去检查上一层的任务是否需要降级即可(而且需要降级的都是在上一层的第一个时间格内)
  • 细节
//在Kafka的时间轮实现上有些细节
PS:
1.时间轮创建时使用的当前系统时间不是System.currentTimeMillis(),而是System.nanoTime()/1000000来得到毫秒值,后者更加精确。
2.TimerTaskList是一个保存延迟任务的双向环形链表,其中引入了哑元节点来便于操作。(不含值的节点)
  • 时间轮部分更多原理,可以阅读参考文章[1]的6.2章节,以及参考文章[3]和[4]

三、延时操作

  • Kafka中有很多细节是由延时操作来实现的,很典型的比如生产者写入,如果ack参数设置为all,那么leader需要等待follower写入消息之后才能给生产者客户端写入成功的响应。假设第1ms leader副本写入成功,第3ms follower副本才写入成功,那么Kafka服务端需要在第3ms才能给客户端写入成功的响应,这是有一个延时的,而Kafka对这样的响应就是采用延时操作实现的。延时操作能够等待指定的条件被满足或者超时之后执行特定操作,在这里被满足的条件就是消息写入follower副本,超时是配置项request.timeout.ms决定。延时操作能够感知到条件被满足,这在Kafka中是通过外部事件触发来实现的。
  • 延时操作是由延时操作管理器来负责管理。同时会对延时操作分配一个定时器来做超时管理,定时器就是基于前面的时间轮实现的。

四、控制器

  • Kafka集群中会有一个broker被选举被控制器,控制器复杂集群中的许多管理工作,包括leader副本故障时为该分区选举新的leader副本、ISR集合变化时通知broker更新元数据或者负责topic的分区重分配等。

4.1 控制器选举和异常恢复

4.1.1 /controller
  • Kafka的控制器选举依赖zookeeper,选举成功的节点会在zookeeper中创建/controller临时节点,内容参考如下:
[zk: localhost:2181(CONNECTED) 2] get  /controller
{"version":1,"brokerid":1,"timestamp":"1563182273536"}
cZxid = 0x2ad82
ctime = Mon Jul 15 09:17:53 UTC 2019
mZxid = 0x2ad82
mtime = Mon Jul 15 09:17:53 UTC 2019
pZxid = 0x2ad82
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x16bf4ec714f0000
dataLength = 54
numChildren = 0

PS:version为1,brokerid为kafka在集群的唯一id,时间戳是节点被选为控制器的时间
Kafka集群的节点会去尝试读取zk的/controller节点信息,如果id值不为-1表示已有控制器,那么节点就会放弃竞选。如果/controller不存在或者节点中数据异常,那么节点就会尝试去竞选控制器。
只有创建临时节点成功的broker才会被选举为控制器,非控制器节点会在内存中保存控制器节点的brokerid。
4.1.2 /controller_epoch
  • zk中还会有一个持久节点/controller_epoch,他会保存控制器发生变更的次数,表示当前控制器是第几代控制器,初始值为1。
[zk: localhost:2181(CONNECTED) 4] get /controller_epoch
17
cZxid = 0x1e
ctime = Wed Jun 19 06:25:54 UTC 2019
mZxid = 0x2ad83
mtime = Mon Jul 15 09:17:53 UTC 2019
pZxid = 0x1e
cversion = 0
dataVersion = 16
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 2
numChildren = 0

PS:和控制器节点交互的请求会携带controller_epoch字段的值,如果该值小于内存中的controller_epoch则认为是向过期的控制器发送的请求,该请求会被人为是无效的。如果大于内存中的controller_epoch则认为是有新的节点当选为控制器了,由此可以通过该字段的值来保证控制器的唯一性。
4.1.3 控制器职责
  • 控制器的职责比非控制器要更多,包括下面
1.处理分区重分配;控制器会往zk的/admin/reassign_partitions节点注册监听器PartitionReassignmentHandler来处理分区重分配动作;
2.处理ISR集合;控制器会往zk的/isr_change_notification节点注册监听器IsrChangeNitoficationHandler来处理ISR集合变更动作;
3.处理主题变化;控制器会往zk的/brokers/topics节点注册监听器TopicChangeHandler来处理主题的增减变化;
4.处理主题删除;控制器会往zk的/admin/delete_topics节点注册监听器TopicDeletionHandler来处理主题的删除;
5.处理节点变化;控制器会往zk的/brokers/ids节点注册监听器BrokerChangeHandler来处理broker增减变化;
6.处理分区分配变化;控制器会往zk的/brokers/topics/topic节点注册监听器PartitionModificationsHandler来监听主题中分区分配变化;

4.2 关闭

  • 方式1:kafka-server-stop.sh关闭;不过该脚本有时候不能奏效,因为其内部是为了查找kafka进程id然后kill -s TERM pid,不过有时候因为ps命令的字符限制获取pid会失败。
  • 方式2:kill -s TERM pid 或者 kill -15 pid。
PS:正常关闭流程,kafka会持久化消息到磁盘另外会转移该broker上可能运行的分区leader副本,减少该分区的不可用时间。

4.3 分区leader选举

  • 分区leader选举由控制器负责完成,当创建分区或者分区上线的时候,都需要执行leader选举动作。
  • 选举场景和规则
场景策略leader规则
创建分区或者分区上线OfflinePartitionLeaderElectionStrategyAR集合中第一个存活且在ISR的副本
分区重分配ReassignPartitionLeaderElectionStrategy充分配的AR集合中第一个存活且在ISR的副本
优先副本选举PreferredReplicaPartitionLeaderElectionStrategyAR中第一个副本(第一个副本就是优先副本)
某broker被关闭ControlledShutdownPartitionLeaderElectionStrategyAR集合中第一个存活且在ISR的副本,且不在关闭的broker上
PS: 注意第一种场景如果ISR为空,那么就要看配置unclean.leader.election.enable是否为true(默认false),为true的话表示允许从非ISR副
本选举leader,从AR中找到第一个存活的副本即为leader。

五、参数

5.1 broker.id

  • 在一个集群中该id是唯一的,默认是-1,大于0才能启动成功,建议在server.properties文件中配置。如果没有配置会生成一个唯一id,关于该配置的作用和生成细节,下面一一说明。

  • 作用

broker.id默认为-1,该配置大于0才能启动成功,kafka启动后会在zk的/brokers/ids路径下创建一个与当前broker的id为名称的虚节点,kafka的健康检查依赖该节点,kafka
下线对应的虚节点也会删除。节点之间通过检查对应id是否存在来判断对应节点的健康状态。因此该id在集群中是必须唯一的。
  • meta.properties文件
在log.dirs配置中可以配置一个或者多个日志目录,每个目录下有一个meta.properties,包括broker.id和一个version配置,文件形式如下:

    #
    #Tue Oct 16 11:16:50 GMT 2018
    version=0
    broker.id=1
  • meta.properties与broker.id的关联如下:
如果多个日志目录下面的meta.properties中的broker.id不一致,或者和server.properties中不一致,则都会抛出InconsistentBrokerIdException异常
如果config/server.properties配置文件中并未配置broker.id的值,那么就以meta.properties文件中的broker.id为准
如果没有meta.properties文件,那么在获取到合适的broker.id值之后会创建一个新的meta.properties文件并将broker.id的值存入其中。
  • broker.id自动生成规则
broker.id自动生成规则:如果config/server.properties配置文件中并未配置broker.id,并且根目录中也没有任何meta.properties文件(比如服务第一次启动时),那么应该作何处
理呢?Kafka中还提供了另外两个参数broker.id.generation.enable和reserved.broker.max.id来配合生成新的broker.id。自动生成的broker.id是有一个基准值的,即自动生成
的broker.id必须超过这个基准值,这个基准值通过reserved.broker.max.id参数配置,默认值为1000,也就是说默认情况下自动生成的broker.id从1001开始。自动生成broker.id的原
理是先往zk的/brokers/seqid节点中写入一个空字符串,然后获取返回的Stat中的version的值,然后将version的值和reserved.broker.max.id参数配置的值相加可得。之所以是先往节
点中写入数据再获取Stat信息,这样可以确保返回的version值大于0,进而就可以确保生成的broker.id值大于reserved.broker.max.id参数配置的值,符合非自动生成的broker.id的
值在[0,reserved.broker.max.id]区间的设定。通过这个方法,每个kafka节点的broker.id就可以做到不重复的自增。不过为了简化配置,如果节点不多最好显示加上broker.id配置。
是否开启自动生成broker.id的功能,默认情况下为true,即开启此功能。启用时会检查为reserved.broker.max.id配置的值。
自动生成broker.id的基准值,自动生成的id均超过该值,默认1000即id从1001开始。

5.2 bootstrap.server

  • 用于发现Kafka集群元数据信息的服务地址。通过该配置可以获取整个Kafka集群的元数据而不仅仅只是连接该设备。

六、参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值