1、入口
Kafka Server 处理 Client 发送来的请求的入口在
文件夹: core/src/main/scala/kafka/server
类:kafka.server.KafkaApis
方法: handle
处理offset请求的函数: handleOffsetRequest
2、处理逻辑
处理逻辑主要分为四步
- 获取partition
- 从partition中获取offset
- high water mark 处理(这一段的资料太少了)
- 异常处理
由于request中包含查询多个partition的offset的请求。所以最终会返回一个map,保存有每个partition对应的offset
这里主要介绍从某一个partition中获取offset的逻辑,代码位置
- kafka.log.Log#getOffsetsBefore(timestamp, maxNumOffsets)
从一个partition中获取offset
1、建立offset与timestamp的对应关系,并保存到数据中
//每个Partition由多个segment file组成。获取当前partition中的segment列表 val segsArray = segments.view
// 创建数组 var offsetTimeArray: Array[(Long, Long)] =null if(segsArray.last.size >0) offsetTimeArray =newArray[(Long, Long)](segsArray.length +1) else offsetTimeArray =newArray[(Long, Long)](segsArray.length)
// 将 offset 与 timestamp 的对应关系添加到数组中 for(i <-0until segsArray.length) // 数据中的每个元素是一个二元组,(segment file 的起始 offset,segment file的最近修改时间) offsetTimeArray(i) = (segsArray(i).start, segsArray(i).messageSet.file.lastModified) if(segsArray.last.size >0) // 如果最近一个 segment file 不为空,将(最近的 offset, 当前之间)也添加到该数组中 offsetTimeArray(segsArray.length) = (logEndOffset, time.milliseconds) |
通过这段逻辑,获的一个数据 offsetTimeArray,每个元素是一个二元组,二元组内容是(offset, timestamp)
2、找到最近的最后一个满足 timestamp < target_timestamp 的 index。
var startIndex = -1 timestamp match { // 需要查找的 timestamp 是 -1 或者 -2时,特殊处理 caseOffsetRequest.LatestTime => // OffsetRequest.LatestTime = -1 startIndex = offsetTimeArray.length -1 caseOffsetRequest.EarliestTime => // OffsetRequest.EarliestTime = -2 startIndex =0 case_ => var isFound =false debug("Offset time array = "+ offsetTimeArray.foreach(o =>"%d, %d".format(o._1, o._2))) startIndex = offsetTimeArray.length -1 // 从最后一个元素反向找 while(startIndex >=0&& !isFound) { // 找到满足条件或者 if(offsetTimeArray(startIndex)._2 <= timestamp) // offsetTimeArray 的每个元素是二元组,第二个位置是 timestamp isFound =true else startIndex -=1 } } |
通过这段逻辑,实际找到的是 “最近修改时间早于目标timestamp的最近修改的segment file的起始offset”
但是获取offset的逻辑并没有结束,后续仍有处理
3、找到满足该条件的offset数组
实际上这个函数的功能是找到一组offset,而不是一个offset。第二个参数 maxNumOffsets 指定最多找几个满足条件的 offset。
获取一组offset的逻辑
// 返回的数据的长度 = min(maxNumOffsets, startIndex + 1),startIndex是逻辑2中找到的index val retSize = maxNumOffsets.min(startIndex +1) val ret = newArray[Long](retSize)
// 逐个将满足条件的offset添加到返回的数据中 for(j <-0until retSize) { ret(j) = offsetTimeArray(startIndex)._1 startIndex -=1 }
// 降序排序返回。offset 越大数据越新。 // ensure that the returned seq is in descending order of offsets ret.toSeq.sortBy(- _) |
最终返回这个数组
3、注意事项
- 实际找到的offset并不是从目标timestamp开始的第一个offset。需要注意
- 当 timestamp 小于最老的数据文件的最近修改时间时,返回值是一个空数组。可能会导致使用时的问题。
- 调整segment file文件拆分策略的配置时,需要注意可能会造成的影响。