一、日志
1、日志是如何加载日志段的?
2、一个日志段包括哪几个文件?
3、Broker端提供定期删除日志的功能是如何实现?
时间轮 + 时间戳索引项
4、如何写入消息到日志段?写操作过程更新索引的时机是如何设定的?
5、如何读取日志段,获取消息?
6、如何恢复日志段,重建索引?
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、位移索引和时间索引有哪些异同特点?
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),然 后分别在这两个区域内执行二分查找算法
这个改进版算法的最大好处在于,查询最热那部分数据所遍历的 Page 永远是固定的,因
此大概率在页缓存中,从而避免无意义的 Page Fault。
6、冷区和热区分割线为什么设定在8192?
现在处理器一般缓存页大小是4096,那么8192可以保证页数小于等3,用于二分查找的页面都能命中。
7、索引中的索引项是如何定义的?
位移索引
位移索引也就是所谓的 OffsetIndex。
每当 Consumer 需要从主题分区的某个位置开始读取消息时,Kafka 就会用到 OffsetIndex 直接定位物理文件位置,从而避免了因为从头读取消息而引入的昂贵的 I/O 操 作。
OffsetIndex保存的是 <Key, Value> 对,Key 就是消息的相对位移,Value 是保存该消息的日志段文件中该消息第一个字节的 物理文件位置。
时间戳索引
TimeIndex 保存的是 < 时间戳,相对位移值 > 对
8、如何向索引写入新的索引项?
位移索引
时间戳索引
和 OffsetIndex 类似,向 TimeIndex 写入索引项的主体逻辑,是向 mmap 分别写入时间 戳和相对位移值。只不过,除了校验位移值的单调增加性之外,TimeIndex 还会确保顺序 写入的时间戳也是单调增加的。
三、SocketServer
1、Kafka 如何应用 NIO 来实现网络通信的?
2、Acceptor 线程和 Processor 线程的作用是什么?
Acceptor 线程
经典的 Reactor 模式有个 Dispatcher 的角色,接收外部请求并分发给下面的实际处理线
程。在 Kafka 中,这个 Dispatcher 就是 Acceptor 线程。
Acceptor 代码中,提供了 3 个与 Processor 相关的方法,分别是 addProcessors、startProcessors 和 removeProcessors。
Acceptor 类逻辑的重头戏其实是 run 方法,它是处 理 Reactor 模式中分发逻辑的主要实现方法。
Processor 线程
如果说 Acceptor 是做入站连接处理的,那么,Processor 代码则是真正创建连接以及分 发请求的地方。
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 保存了什么数据?
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 命令后,主题就会被删除。
在主题删除过程中,Kafka 会调整集群中三个地方的数据:ZooKeeper、元数据缓存和 磁盘日志文件。删除主题时,ZooKeeper 上与该主题相关的所有 ZNode 节点必须被清 除;Controller 端元数据缓存中的相关项,也必须要被处理,并且要被同步到集群的其 他 Broker 上;而磁盘日志文件,更是要清理的首要目标。这三个地方必须要统一处理, 就好似我们常说的原子性操作一样。现在回想下开篇提到的那个“秘籍”,你就会发现 它缺少了非常重要的一环,那就是:无法清除 Controller 端的元数据缓存项。因此,你 要尽力避免使用这个“大招”。
六、Replica & Consume & Broker
1、Broker是怎么延时处理请求的?
通过分层时间轮,每个延迟请求需要根据自己的超时时间,来决定它要被保存于哪一层时间轮 上。Kafka 不断向前推动各个层级的时间轮的 时钟,按照时间轮的滴答时长,陆续接触到 Bucket 下的各个延迟任务,从而实现了对请求 的延迟处理。
2、Follower拉取Leader消息是如何实现的?
3、为什么 AbstractFetcherThread 线程总要不断尝试去做截断呢?读取消息之前为什么要做截断?
这是因为,分区的 Leader 可能会随时发生变化。每当有新 Leader 产生时,Follower 副本 就必须主动执行截断操作,将自己的本地日志裁剪成与 Leader 一模一样的消息序列,甚 至,Leader 副本本身也需要执行截断操作,将 LEO 调整到分区高水位处。