副本读取:fetchMessages方法读取需要的消息,它逻辑如下:
def fetchMessages(timeout: Long, // 请求处理超时时间。
replicaId: Int, // 副本 ID。对于消费者而言,该参数值是 -1;对于 Follower 副本而言,该值就是 Follower 副本所在的 Broker ID。
fetchMinBytes: Int, // 够获取的最小字节数。
fetchMaxBytes: Int, // 够获取的最大字节数。
hardMaxBytesLimit: Boolean, // 对能否超过最大字节数做硬限制。
fetchInfos: Seq[(TopicPartition, PartitionData)], // 规定了读取分区的信息,比如要读取哪些分区、从这些分区的哪个位移值开始读、最多可以读多少字节,等等。
quota: ReplicaQuota, // 这是一个配额控制类,主要是为了判断是否需要在读取的过程中做限速控制。
responseCallback: Seq[(TopicPartition, FetchPartitionData)] => Unit, // Response 回调逻辑函数。当请求被处理完成后,调用该方法执行收尾逻辑。
isolationLevel: IsolationLevel,
clientMetadata: Option[ClientMetadata]): Unit = {
// 判断该读取请求是否来自于Follower副本或Consumer
val isFromFollower = Request.isValidBrokerId(replicaId)
val isFromConsumer = !(isFromFollower || replicaId == Request.FutureLocalReplicaId)
// 根据请求发送方判断可读取范围
// 如果请求来自于普通消费者,那么可以读到高水位值
// 如果请求来自于配置了READ_COMMITTED的消费者,那么可以读到Log Stable Offset值
// 如果请求来自于Follower副本,那么可以读到LEO值
val fetchIsolation = if (!isFromConsumer)
FetchLogEnd
else if (isolationLevel == IsolationLevel.READ_COMMITTED)
FetchTxnCommitted
else
FetchHighWatermark
// Restrict fetching to leader if request is from follower or from a client with older version (no ClientMetadata)
val fetchOnlyFromLeader = isFromFollower || (isFromConsumer && clientMetadata.isEmpty)
// 定义readFromLog方法读取底层日志中的消息
def readFromLog(): Seq[(TopicPartition, LogReadResult)] = {
val result = readFromLocalLog(
replicaId = replicaId,
fetchOnlyFromLeader = fetchOnlyFromLeader,
fetchIsolation = fetchIsolation,
fetchMaxBytes = fetchMaxBytes,
hardMaxBytesLimit = hardMaxBytesLimit,
readPartitionInfo = fetchInfos,
quota = quota,
clientMetadata = clientMetadata)
if (isFromFollower) updateFollowerFetchState(replicaId, result)
else result
}
val logReadResults = readFromLog()
// check if this fetch request can be satisfied right away
var bytesReadable: Long = 0
var errorReadingData = false
val logReadResultMap = new mutable.HashMap[TopicPartition, LogReadResult]
var anyPartitionsNeedHwUpdate = false
// 统计总共可读取的字节数
logReadResults.foreach { case (topicPartition, logReadResult) =>
if (logReadResult.error != Errors.NONE)
errorReadingData = true
bytesRe