一、日志
1、日志是如何加载日志段的?
![](https://img.haomeiwen.com/i6271376/2c77a60d5c1d4626.png)
![](https://img.haomeiwen.com/i6271376/e2b6ea707d581204.png)
2、一个日志段包括哪几个文件?
![](https://img.haomeiwen.com/i6271376/92003a202869255b.png)
3、Broker端提供定期删除日志的功能是如何实现?
时间轮 + 时间戳索引项
4、如何写入消息到日志段?写操作过程更新索引的时机是如何设定的?
![](https://img.haomeiwen.com/i6271376/ce68a39500de69ed.png)
5、如何读取日志段,获取消息?
![](https://img.haomeiwen.com/i6271376/9da3ed81bf1a0c03.png)
6、如何恢复日志段,重建索引?
![](https://img.haomeiwen.com/i6271376/883a4ef74c7f3205.png)
7、日志截断是什么?有什么影响?
日志截断就是删除日志中的部分消息。会导致LEO值发生变化,而要更新LEO对象。
8、Kafka什么时候需要更新Log Start Offset?
Log 对象初始化时要给 Log Start Offset 赋值,一 般是将第一个日志段的起始位移值赋值给它。
每个 Log 对象都会维护一 个 Log Start Offset 值。当首次构建高水位时,它会被赋值成 Log Start Offset 值
Kafka 什么时候需要更新 Log Start Offset 呢?我们一一来看 下。
- Log 对象初始化时:和 LEO 类似,Log 对象初始化时要给 Log Start Offset 赋值,一 般是将第一个日志段的起始位移值赋值给它。
- 日志截断时:同理,一旦日志中的部分消息被删除,可能会导致 Log Start Offset 发生 变化,因此有必要更新该值。
- Follower 副本同步时:一旦 Leader 副本的 Log 对象的 Log Start Offset 值发生变 化。为了维持和 Leader 副本的一致性,Follower 副本也需要尝试去更新该值。
- 删除日志段时:这个和日志截断是类似的。凡是涉及消息删除的操作都有可能导致 Log Start Offset 值的变化。
- 删除消息时:严格来说,这个更新时机有点本末倒置了。在 Kafka 中,删除消息就是通 过抬高 Log Start Offset 值来实现的,因此,删除消息时必须要更新该值。
9、Log对象的常见操作有哪些?
- 高水位管理操作
- 日志段管理
- 关键位移值管理
- 读写操作
二、索引
1、位移索引和时间索引有哪些异同特点?
![](https://img.haomeiwen.com/i6271376/615d92f0e85ad8fc.png)
2、为什么使用相对位移?为什么OffsetIndex、TimeIndex分别占8、12个字节?
在OffsetIndex位移索引中是override def entrySize = 8,8个字节。
相对位移是一个整型,占用4个字节,物理文件位置也是一个整型,同样占用4个字节,因此总共8个字节。
我们知道,Kafka中的消息位移值是一个长整型,应该占用8个字节才对,在保存OffsetIndex<Key , Value>对,Kafka做了一些优化,每个OffsetIndex对象在创建时,都已经保存了对应日志段对象的起始位移,因此保存与起始位移的差值就够了。
- 为了节省空间,一个索引项节省了4字节,想想那些日消息处理数万亿的公司。
- 因为内存资源是很宝贵的,索引项越短,内存中能存储的索引项就越多,索引项多了直接命中的概率就高了。
3、Kafka索引底层的实现原理是什么?
内存映射文件,即Java中的MappedByteBuffer
4、如何计算索引对象当前有多少个索引项?
entrySize 来表示不同索引项的大小.
计算索引对象中当前有多少个索引项,那么只需要执行下列计算即可:
protected var _entries: Int = mmap.position() / entrySize
计算索引文件最多能容纳多少个索引项,只要定义下面的变量就行了:
private[this] var _maxEntries: Int = mmap.limit() / entrySize
5、Kafka索引对二分查找算法做了什么优化?
原版的二分查找算法并没有考虑到缓 存的问题,因此很可能会导致一些不必要的缺页中断(Page Fault)
即每当索引文件占用 Page 数发生变化时,就会强行变更二分 查找的搜索路径,从而出现不在页缓存的冷数据必须要加载到页缓存的情形,而这种加载过 程是非常耗时的。
社区提出了改进版的二分查找策略,代码将所有索引项分成两个部分:热区(Warm Area)和冷区(Cold Area),然 后分别在这两个区域内执行二分查找算法
![](https://img.haomeiwen.com/i6271376/509cb0abdda2ae30.png)
这个改进版算法的最大好处在于,查询最热那部分数据所遍历的 Page 永远是固定的,因
此大概率在页缓存中,从而避免无意义的 Page Fault。
6、冷区和热区分割线为什么设定在8192?
现在处理器一般缓存页大小是4096,那么8192可以保证页数小于等3,用于二分查找的页面都能命中。
7、索引中的索引项是如何定义的?
位移索引
位移索引也就是所谓的 OffsetIndex。
每当 Consumer 需要从主题分区的某个位置开始读取消息时,Kafka 就会用到 OffsetIndex 直接定位物理文件位置,从而避免了因为从头读取消息而引入的昂贵的 I/O 操 作。
OffsetIndex保存的是 <Key, Value> 对,Key 就是消息的相对位移,Value 是保存该消息的日志段文件中该消息第一个字节的 物理文件位置。
时间戳索引
TimeIndex 保存的是 < 时间戳,相对位移值 > 对
8、如何向索引写入新的索引项?
位移索引
![](https://img.haomeiwen.com/i6271376/b0206c671e5ab139.png)
时间戳索引
和 OffsetIndex 类似,向 TimeIndex 写入索引项的主体逻辑,是向 mmap 分别写入时间 戳和相对位移值。只不过,除了校验位移值的单调增加性之外,TimeIndex 还会确保顺序 写入的时间戳也是单调增加的。
三、SocketServer
1、Kafka 如何应用 NIO 来实现网络通信的?
![](https://img.haomeiwen.com/i6271376/d64d0379a5265802.png)
2、Acceptor 线程和 Processor 线程的作用是什么?
Acceptor 线程
经典的 Reactor 模式有个 Dispatcher 的角色,接收外部请求并分发给下面的实际处理线
程。在 Kafka 中,这个 Dispatcher 就是 Acceptor 线程。
Acceptor 代码中,提供了 3 个与 Processor 相关的方法,分别是 addProcessors、startProcessors 和 removeProcessors。
![](https://img.haomeiwen.com/i6271376/c75ec66c11722c57.png)
Acceptor 类逻辑的重头戏其实是 run 方法,它是处 理 Reactor 模式中分发逻辑的主要实现方法。
![](https://img.haomeiwen.com/i6271376/c0ed8bd76fcb27d2.png)
Processor 线程
如果说 Acceptor 是做入站连接处理的,那么,Processor 代码则是真正创建连接以及分 发请求的地方。
![](https://img.haomeiwen.com/i6271376/f7dd03249cad0429.png)
3、为什么 Request 队列被设计成线程共享的,而 Response 队 列则是每个 Processor 线程专属的?
Request队列线程共享,这样不同线程的workload才不会发生倾斜,不然可能会发生一边的线程空闲,一边的线程队列满。
resquest共享这样才能实线程间的负载均衡,response专属的是因为对应的request已经分配给对应的线程处理已,为了避免线程上下文上下文切换理因也由这个线程处理响应,作为一个线程内部的变量更加合理。
4、Kafka如何对不同类型的请求进行优先级划分的?
Kafka 请求类型划分为两大类:数据类请求和控制类请求。
控制类请求的 数量应该远远小于数据类请求,因而不需要为它创建线程池和较深的请求队列。
优先级: 控制类请求 > 数据类请求
5、Kafka 请求处理全流程?(Broker(Clients)->Request->Acceptor->Processor->I/O 线程->KafkaRequestHandler->Processor->Response-Broker(Clients))
6、请求处理流程的哪些部分应用了经典 的“生产者 - 消费者”模式?
四、Controller
1、Controller的作用是什么?保存有哪些东西?有几种状态?
Controller的作用是什么?
一方面,它要为集群中的所有主题分区选举领导者副本;另一方面,它还承载着集群的全部元数据信息,并负责将这些元数据信息同步到其他 Broker 上。
- 主题管理(创建、删除、增加分区)
- 分区重分配
- Preferred 领导者选举
- 集群成员管理(新增 Broker、Broker 主动关闭、Broker 宕机)
- 数据服务
Controller 保存了什么数据?
![](https://img.haomeiwen.com/i6271376/22787ec7cf085b5d.png)
Controller 有几种状态?
2、Controller选举是怎么实现的?Controller选举的触发场景有几种?
Broker 在启动时,会尝试去 ZooKeeper 中创建 /controller 节点。Kafka 当前选举控制器的规则是:第一个成功创建 /controller 节点的 Broker 会被指定为控制器。
3、Controller 如何管理集群 Broker 成员和主题?
- 集群成员管理:Controller 负责对集群所有成员进行有效管理,包括自动发现新增 Broker、自动处理下线 Broker,以及及时响应 Broker 数据的变更。
- 主题管理:Controller 负责对集群上的所有主题进行高效管理,包括创建主题、变更主 题以及删除主题,等等。对于删除主题而言,实际的删除操作由底层的 TopicDeletionManager 完成。
五、Topic
1、Topic是怎么被删除的?
之前我以为成功执 行了 kafka-topics.sh --delete 命令后,主题就会被删除。
![](https://img.haomeiwen.com/i6271376/526ccc5f345190dd.png)
在主题删除过程中,Kafka 会调整集群中三个地方的数据:ZooKeeper、元数据缓存和 磁盘日志文件。删除主题时,ZooKeeper 上与该主题相关的所有 ZNode 节点必须被清 除;Controller 端元数据缓存中的相关项,也必须要被处理,并且要被同步到集群的其 他 Broker 上;而磁盘日志文件,更是要清理的首要目标。这三个地方必须要统一处理, 就好似我们常说的原子性操作一样。现在回想下开篇提到的那个“秘籍”,你就会发现 它缺少了非常重要的一环,那就是:无法清除 Controller 端的元数据缓存项。因此,你 要尽力避免使用这个“大招”。
六、Replica & Consume & Broker
1、Broker是怎么延时处理请求的?
通过分层时间轮,每个延迟请求需要根据自己的超时时间,来决定它要被保存于哪一层时间轮 上。Kafka 不断向前推动各个层级的时间轮的 时钟,按照时间轮的滴答时长,陆续接触到 Bucket 下的各个延迟任务,从而实现了对请求 的延迟处理。
2、Follower拉取Leader消息是如何实现的?
![](https://img.haomeiwen.com/i6271376/143a6fd036a716b9.png)
3、为什么 AbstractFetcherThread 线程总要不断尝试去做截断呢?读取消息之前为什么要做截断?
这是因为,分区的 Leader 可能会随时发生变化。每当有新 Leader 产生时,Follower 副本 就必须主动执行截断操作,将自己的本地日志裁剪成与 Leader 一模一样的消息序列,甚 至,Leader 副本本身也需要执行截断操作,将 LEO 调整到分区高水位处。
4、副本管理器是如何读写副本的?
写副本
![](https://img.haomeiwen.com/i6271376/9503b78ca3cd3075.png)
读副本
![](https://img.haomeiwen.com/i6271376/18aeb6258c6d0ecc.png)
5、Broker是怎么异步更新元数据缓存的?
![](https://img.haomeiwen.com/i6271376/4b4bc4d1ed77963d.png)
6、消费者组都有哪些元数据?
![](https://img.haomeiwen.com/i6271376/29c265533361a0b5.png)
网友评论