1. Log的读写、删除流程---日志管理器(log manager)负责创建日志、获取日志、清理日志。所有的日志读写操作都交给具体的日志实例来完成。

KafkaServer启动的时候,初始化三个类:

  • LogManager
  • SocketServer
  • the request handlers - KafkaRequestHandlers

按照顺序组件有:

  1. KafkaScheduler
  2. LogManager
  3. SocketServer
  4. ReplicaManager
  5. KafkaController
  6. GroupCoordinator
  7. KafkaApis
  8. KafkaRequestHandlerPool
  9. KafkaHealthcheck

Tips:

  1. KafkaServer启动的时候,用到了数据结构CountDownLatch shutdownLatch: 用作以后KafkaServer stop的时候,等待别的都关闭完成。
// After calling shutdown(), use this API to wait until the shutdown is complete.
def awaitShutdown(): Unit = shutdownLatch.await()

1.2 LogManager startup

  1. 遍历所有的日志目录,如果有.kafka_cleanshutdown就说明是干净的shutdown,跳过当前log的recovery
  2. 读取recovery-point-offset-checkpoint文件,返回(topic, partition)->offset 映射。其中文件的第一行是版本号,第二行是记录条数,后面是映射内容
  3. 计算出log目录和对应的recovery point,并用来初始化Log对象。
  4. 清理工作,清理掉.kafka_cleanshutdown
  5. 启动三个定时任务,kafka-log-retention, kafka-log-flusher, kafka-recovery-point-checkpoint
    • retention是删除时间过久,体积过大的log文件。
    • flusher是根据时间间隔刷盘,调用FileChannel.force。
    • recovery-point-checkpoint是把内存里的(topic, partition)->offset checkpoint结构刷盘。
  6. 启动LogCleaner

1.2.1 一些删除的策略

  1. 删除掉.deleted和.cleaned后缀的文件
  2. 碰到.swap说明在swap中途server挂掉,这里的swap过程仅仅是一个rename。这种情况对于index的swap直接删除后面从log文件重建,如果是log的swap就先删index再通过rename的方式swap回log
  3. 遇到.index索引文件,如果没有对应的.log文件直接删除
  4. 通过.log文件生成LogSegment对象,如果没有索引文件就运行LogSegment.recover重建索引
  5. 重建索引的过程是以message为单位遍历LogSegment,遍历过程中如果累加的大小超过了index interval就在索引里面记录一下当前消息偏移量(会转化成相对偏移)和.log文件里面的字节为单位的位置。这个过程中会顺便把log文件和index文件末尾的可能因为crash产生的多余的字节给清理掉。
  6. 开始根据recovery point做恢复,这里碰到cleanShutdown文件就会跳过恢复
  7. 遍历从recovery point到末尾的LogSegment,调用LogSegment.recover,如果过程中发现多余的字符说明LogSegment非法,就把非法的一直到末尾的全部删掉。
  8. 最后对所有的index做一遍sanityCheck

1.2.2 Log.append

  1. 参数是MessageSet,一次append一批消息
  2. 分析校验,拿到是否递增,压缩codec,以及校验大小,crc,干掉多余的字节
  3. 后面的所有过程都加上Log级别的锁,如果需要设置偏移量,就遍历每条消息,设置上偏移量
  4. 再做一次消息大小检查,因为前面可能重新压缩过消息
  5. 如果当前LogSegment大小加上要append的大小超过上限就滚动到新的segment
  6. 用当前Log的logEndOffset生成新的log file和index file,之前的末尾index文件做一下裁剪
  7. 用新的log file和index file生成新的LogSegment并加入Log里面的列表,并且把recoveryPoint到之前的logEndOffset的segment加入刷盘队列,刷完了以后recoveryPoint增长到logEndOffset
  8. 如果增长的字节量达到了需要做索引的长度就在index里面append一个entry,然后FileMessageSet.append
  9. 如果append以后没刷盘的字节过多就刷一下盘

1.2.3 Log.read

  1. 参数有一个startOffset,首先定位到offset仅仅比这个低的entry,如果定位不到就直接异常
  2. 调用LogSegment.read,首先要找到log文件里面对应startOffset的第一个合法的position
  3. 查找过程先通过index查找offset对应的position,查找的过程是标准二分查找,因为用了mmap所以都是在内存里面找。index因为需要经常的随机读和append操作,所以做了mmap
  4. 调用FileMessageSet.searchFor,在log文件里找大于offset的合法message的position
  5. 用同样的方法计算出end offset对应position,在算出需要读取的字节数
  6. 通过这些信息,返回一个FileMessageSet,这里还没有实际读取,后面会提取出byte message返回

1.2.4 LogCleaner

  1. 如果对一个消息存在key相同但是offset更高的消息,那么就清理掉这个offset低的消息
  2. 先找最脏的Log,找的方法是先读取cleaner-offset-checkpoint里面存了每个log的最后清理offset,然后每个log拿最后清理的offset到active offset所有segments的size和log size算一个比率,然后取比率最大的
  3. 开始清理Log,先遍历所有脏的LogSegment,构建一个内存中的OffsetMap(index),key是消息key,value是消息offset,这个map后面用来判断消息是否是重复的
  4. 把所有的LogSegment分个组,每个组的字节大小总量尽量接近于某个指定值。尽量把一个组压缩成一个新的Segment,这样做让新的Segment体积尽量平均
  5. 拿出每个组的第一个segment以它的名字创建log和index对应的.cleaned文件
  6. 对于组里的每个LogSegment,过滤掉重复的和空的message,其余的全部写入那个新的目标LogSegment
  7. 新的LogSegment刷盘,然后把旧的segment list全部删除,最后把.cleaned名字改成.log文件

2.网络协议

看看请求的网络协议里面都存了写什么。

2.1 request and response

  1. 首先解析2字节的requestId
  2. 根据requestId选择kafka.api包里面对应的request类来处理,序列化反序列化协议 包含在对应的request类里面
  3. 有一个correlationId做request和response的关联
  4. 拿ProducerRequest做例子
+----------------------------+
| requestId 2 |
+----------------------------+
| versionId 2 |
+----------------------------+
| correlationId 4 |
+----------------------------+
| clientId |
+----------------------------+
| requiredAcks 2 |
+----------------------------+
| ackTimeoutMs 4 |
+----------------------------+
| topicCount 4 |
+----------------------------+
| +-------------------------+|
| | topic ||
| +-------------------------+|
| | partitionCount 4 ||
| +-------------------------+|
| | +----------------------+||
| | | partition 4 |||
| | +----------------------+||
| | | messageSetSize 4 |||
| | +----------------------+||
| | | messageSetContent |||
| | | |||
| | +----------------------+||
| +-------------------------+|
+----------------------------+

message的内容

+--------------+
|CRC 4 |
+--------------+
|Magic 1 |
+--------------+
|attributes 1 |
+--------------+
|key 4 |
+--------------+
|size 4 |
+--------------+
|content |
+--------------+

第一次创建topic的最后会启动highWaterMark的checkPoint线程,这个线程定期刷所有replica的checkPoint到磁盘。落地在磁盘的文件名叫做:replication-offset-checkpoint

3.partition reassign 扩容的机制

PartitionsReassignedListener监听/admin/reassign_partitions,由KafkaController控制---KafkaController.onPartitionReassignment 。扩容时,需要停止Producer和Consumer。

4.高可用

4.1 high water mark

  1. high water mark通常是一个partition所有replica的endOffset的最小值,也是同步提交模式情况下集群commit的位置
  2. 读的时候客户端最多只能读到highWatermark的位置
  3. 一个follower挂掉重启的时候首先扔掉它自己highWatermark之后的数据,然后开始追赶leader
  4. leader挂掉的会重新选,新leader把自己的endOffset当做新的highWatermark,然后让其它的replica开始追赶

4.2 in sync replica

  1. Partition会启动一个超时线程,调用Partition.maybeShrinkIsr,检查所有isr的end offset和最后更新时间,差距过大的就踢掉
  2. 处理follower的fetch request的时候可能会触发Partition.updateLeaderHWAndMaybeExpandIsr,重新检查这个replica的各种条件,可能的话就加回到isr

4.3 partition leader election

  1. PartitionLeaderSelector.OfflinePartitionLeaderSelector是用得最多的leader selector,选leader的逻辑是从controller context里面拿出live isr和live replica,优先选择live isr的第一个,如果没有就选择live replica的第一个,否则就抛异常
  2. 更新zk path /brokers/topics/huying_test1/partitions/0/state
  3. 给这个partition的每个replica broker发一个请求LeaderAndIsrRequest,其中leader和follower的指令稍有区别。然后给所有活着的broker发一个请求UpdateMetadataRequest。这里的请求都是异步带callback(多数情况是null)的
  4. 发送请求如果失败会重试,终止重试的流程是zk那边发起,导致当前发送线程被关闭
  5. 接收到请求的broker开始ReplicaManager.makeLeaders, makeFollowers相关的流程
  6. 处理UpdateMetadataRequest请求比较简单,更新一下cache数据就ok了
  7. 触发点,KafkaController.onBrokerStartup, KafkaController.onBrokerFailure分别对应broker启动的时候和broker挂掉的时候,KafkaController.onPreferredReplicaElection,使用了工具:kafka-preferred-replica-election.sh

4.4 controller leader election

  1. controller是kafka全局的leader,controller负责监听zookeeper然后分析状态变更发送命令到其它的broker
  2. broker启动的时候开始监听/controller节点,这里注意一个zk的bug(session失效可能过了一段时间临时节点才会删除,如果碰到node exist exception就返回可能会都选不上) LeaderChangeListener可能会触发KafkaController.onControllerFailover或者KafkaController.onControllerResignation
  3. onControllerFailover首先会读取zk上的epoch,然后+1乐观锁方式更新zk,epoch用来标识正确的leader,当新leader产生旧leader没挂掉的时候可以作废掉旧leader的命令,broker里面关注epoch的manager保存一份epoch,接到比自己大的epoch就设定成新的
  4. 监听几个zk path,/admin/reassign_partitions,/admin/preferred_replica_election,/brokers/topics,/admin/delete_topics,/brokers/ids,/brokers/topics/topicXXX
  5. 初始化controllerContext相关的内存结构和一些manager,并给其它的broker发一个metadata更新的命令
  6. 如果配了自动平衡partition leader,就启动一个检查并自动平衡partition leader的scheduler,调用checkAndTriggerPartitionRebalance,不平衡率高的时候触发onPreferredReplicaElection

    7.onControllerResignation执行相反的过程,取消注册之前的几个zk listener并且关闭几个manager

5.broker启动和关闭

  1. broker启动的时候注册SessionExpireListener,这里调用的是zkClient.subscribeStateChanges(sessionExpireListener)而不是监听zk临时节点,感觉碰到机器假死会少了删临时节点的手段。然后注册临时节点/brokers/ids/xxx,这个临时节点会被controller的BrokerChangeListener监听
  2. KafkaController.onBrokerStartup 首先给新启动的broker发送metadata
  3. 给新broker上的所有replica触发OnlineReplica的状态变更,会触发broker上面的replicaManager.becomeLeaderOrFollower,具体的逻辑和前面创建topic里面描述的相同
  4. 触发partitionStateMachine的状态变更,可能会导致leader重新选举,同时也会触发broker上面的replicaManager.becomeLeaderOrFollower
  5. 如果在新的broker上有partition reassignment,就调用onPartitionReassignment
  6. broker关闭的逻辑类似,也是触发partitionStateMachine和replicaStateMachine的状态变化,稍有不同的是那些leader在这台broker上的partition要先触发一个Offline再触发一个Online

6.高性能

  1. 磁盘顺序写速度很快,pagecache的存在加速读写
  2. 攒消息成message set,网络上传大包,更大块的磁盘操作,内存连续性更好
  3. 协议固定,消息字节在broker,consumer,producer之间传输不需要修改
  4. 字节从pagecache到socket在linux可以通过sendfile优化

2 Kafka Broker的更多相关文章

  1. kafka broker 进入 conflicted ephemeral node 死循环

    转载请注明原创地址 http://www.cnblogs.com/dongxiao-yang/p/5621303.html 最近发现kafka一台服务器producer客户端写入时一直报错,查看该br ...

  2. 关于Kafka broker IO的讨论

    Apache Kafka是大量使用磁盘和页缓存(page cache)的,特别是对page cache的应用被视为是Kafka实现高吞吐量的重要因素之一.实际场景中用户调整page cache的手段并 ...

  3. Kafka broker配置介绍 (四)

    这部分内容对了解系统和提高软件性能都有很大的帮助,kafka官网上也给出了比较详细的配置详单,但是我们还是直接从代码来看broker到底有哪些配置需要我们去了解的,配置都有英文注释,所以每一部分是干什 ...

  4. Kafka 单节点多Kafka Broker集群

    Kafka 单节点多Kafka Broker集群 接前一篇文章,今天搭建一下单节点多Kafka Broker集群环境. 配置与启动服务 由于是在一个节点上启动多个 Kafka Broker实例,所以我 ...

  5. Kafka 单节点单Kafka Broker集群

    下载与安装 从 http://www.apache.org/dist/kafka/ 下载最新版本的 kafka,这里使用的是 kafka_2.12-0.10.2.1.tgz $ tar zxvf ka ...

  6. spark streaming 接收kafka消息之三 -- kafka broker 如何处理 fetch 请求

    首先看一下 KafkaServer 这个类的声明: Represents the lifecycle of a single Kafka broker. Handles all functionali ...

  7. kafka broker Leader -1引起spark Streaming不能消费的故障解决方法

    一.问题描述:Kafka生产集群中有一台机器cdh-003由于物理故障原因挂掉了,并且系统起不来了,使得线上的spark Streaming实时任务不能正常消费,重启实时任务都不行.查看kafka t ...

  8. Kafka Broker | 命令行选项和过程

    1.目标 在这个Apache Kafka教程中,我们将学习Kafka Broker.Kafka Broker管理主题中的消息存储.如果Apache Kafka有多个代理,那就是我们所说的Kafka集群 ...

  9. Spark Streaming + Kafka整合(Kafka broker版本0.8.2.1+)

    这篇博客是基于Spark Streaming整合Kafka-0.8.2.1官方文档. 本文主要讲解了Spark Streaming如何从Kafka接收数据.Spark Streaming从Kafka接 ...

  10. Structured Streaming + Kafka Integration Guide 结构化流+Kafka集成指南 (Kafka broker version 0.10.0 or higher)

    用于Kafka 0.10的结构化流集成从Kafka读取数据并将数据写入到Kafka. 1. Linking 对于使用SBT/Maven项目定义的Scala/Java应用程序,用以下工件artifact ...

随机推荐

  1. Sprint8

    进展:添加事件主界面实现之后,实现事件添加部分代码的编写,进行设置事件提醒,选择时间.

  2. 《Spring1之第三次站立会议》

    <第三次站立会议> 昨天:我对自己找到的代码进行了相关的了解后,把它们在编译环境中进行了编译以及接着对代码进行逐步深入了解: 今天:我把小组成员找到的写关于登录界面的代码加到了我的项目工程 ...

  3. window 窗口编辑

    package com.chuangkohua; import java.awt.FileDialog; import java.awt.FlowLayout; import java.awt.Fra ...

  4. Beta后续感想/吐槽

    感想 磨人的软工实践终于结束了 艰难的度过了一学期,还是写点什么纪念一下吧. 大一大二的时候就听说软工实践是魔鬼般的锻炼,于是当年不知天高地厚的我是很期待的,终于,我大三了. 后来,我长大了. alp ...

  5. 消息队列第二篇:MessageQueue实战(课程订单)

    上一篇:消息队列介绍 本篇一开始就上代码,主要演练MessageQueue的实际应用.用户提交订单(消息发送),系统将订单发送到订单队列(Order Queue)中:订单管理系统(消息接收)端,监听消 ...

  6. lr关联-保存数组并调用(转)

    LOADRUNNER中的一个关联技巧     众所周知,在LoadRunner中,关联是一个很重要的动作,大多数的脚本在录制完成后并不能直接回放,需要通过一定的关联才能成功回放.关联的技巧有很多,这里 ...

  7. python 安装pymssql

    error: command 'gcc' failed with exit status 1 ---------------------------------------- Command &quo ...

  8. Navicat for MySQL和Navicat Premium之间的区别

    首先两款软件都可以用来管理数据库链接MySQL和MariaDB 相对于新手或者前端工程师使用Navicat for MySQL就够了,功能相对于Navicat Premium比较少Navicat fo ...

  9. HDU2883_kebab

    很好的题目. 有不多于200个任务,每个任务要在si到ei这个时间段内完成,每个任务的任务量是ti*ni,只有一台机器,且其单位时间内可完成的任务量为m. 现在问你,能否使所有的任务全部在规定的时间段 ...

  10. JAVAScript对象及初始面向对象

              javaScript对象及初始面向对象 1:内置对象 例:Date String Array 类等... 2:自定义对象 方法1:var newObj=new Object(); ...