高水位管理操作
源码定义:
@volatile private var highWatermarkMetadata: LogOffsetMetadata = LogOffsetMetadata(logStartOffset)
根据定义可以看出,1、highWatermarkMetadata是可变的,并且通过volatile 保证它线程之间的可见性。2、高水位值的初始值是 Log Start Offset 的值。3、类型是LogOffsetMetadata 。
LogOffsetMetadata 类
源码:
/*
* A log offset structure, including:
* 1. the message offset
* 2. the base message offset of the located segment
* 3. the physical position on the located segment
*/
case class LogOffsetMetadata(messageOffset: Long,
segmentBaseOffset: Long = Log.UnknownOffset,
relativePositionInSegment: Int = LogOffsetMetadata.UnknownFilePosition) { }
根据源码的定义可以看出,LogOffsetMetadata 有三个重要参数。
1、messageOffset,消息位移,我们所说的高水位的值就是指的这个值。
2、segmentBaseOffset:保存该位移值所在日志段的起始位移。日志段起始位移值辅助计算两条消息在物理磁盘文件中位置的差值,就是两条消息间隔字节数,当然计算的两条消息是在同一日志段上。
3、relativePositionInSegment:保存该位移值所在日志段的磁盘位置。
该类的方法比较简单,主要是用来判断两个位移是否在同一日志段,位移差值等,举例如下:
// check if this offset is already on an older segment compared with the given offset
def onOlderSegment(that: LogOffsetMetadata): Boolean = {
if (messageOffsetOnly)
throw new KafkaException(s"$this cannot compare its segment info with $that since it only has message offset info")
this.segmentBaseOffset < that.segmentBaseOffset
}
onOlderSegment()用来判断给定的位移是否在先前的日志段上。
更新高水位值元数据
这部分的源码可以直接看注释:
private def updateHighWatermarkMetadata(newHighWatermark: LogOffsetMetadata): Unit = {
if (newHighWatermark.messageOffset < 0) //高水位值不能为负数,否则抛异常
throw new IllegalArgumentException("High watermark offset should be non-negative")
lock synchronized { //添加监视器锁
highWatermarkMetadata = newHighWatermark //设置新的HighWatermark
producerStateManager.onHighWatermarkUpdated(newHighWatermark.messageOffset) // 处理事务状态管理器的高水位值更新逻辑
maybeIncrementFirstUnstableOffset() //处理FirstUnstable 事务
}
trace(s"Setting high watermark $newHighWatermark")
}
更新高水位值
有两种方法updateHighWatermark主要用在 Follower 副本从 Leader 副本获取到消息后更新高水位值。一旦拿到新的消息,就必须要更新高水位值。
// updateHighWatermark method
def updateHighWatermark(hw: Long): Long = {
// 新高水位值一定介于[Log Start Offset,Log End Offset]之间
val newHighWatermark = if (hw < logStartOffset)
logStartOffset
else if (hw > logEndOffset)
logEndOffset
else
hw
// 更新高水位元数据
updateHighWatermarkMetadata(LogOffsetMetadata(newHighWatermark))
newHighWatermark // 最后返回新高水位值
}
maybeIncrementHighWatermark()主要是用来更新 Leader 的高水位值,leader副本不一定会立即更新高水位的值所以是maybe。
def maybeIncrementHighWatermark(newHighWatermark: LogOffsetMetadata): Option[LogOffsetMetadata] = {
// 新高水位值不能越过Log End Offset
if (newHighWatermark.messageOffset > logEndOffset) throw new IllegalArgumentException(s"High watermark $newHighWatermark " +
s"update exceeds current " + s"log end offset $logEndOffsetMetadata")
lock.synchronized {
// 获取老的高水位值
val oldHighWatermark = fetchHighWatermarkMetadata
// 新高水位值要比老高水位值大以维持单调增加特性,否则就不做更新!
// 另外,如果新高水位值在新日志段上,也可执行更新高水位操作
if (oldHighWatermark.messageOffset < newHighWatermark.messageOffset ||
(oldHighWatermark.messageOffset == newHighWatermark.messageOffset && oldHighWatermark.onOlderSegment(newHighWatermark))) {
updateHighWatermarkMetadata(newHighWatermark)
Some(oldHighWatermark) // 返回老的高水位值
} else {
None
}
}
}
读取高水位元数据
实现源码如下:
private def fetchHighWatermarkMetadata: LogOffsetMetadata = {
checkIfMemoryMappedBufferClosed() // 读取时确保日志不能被关闭
val offsetMetadata = highWatermarkMetadata // 保存当前高水位值到本地变量,避免多线程访问干扰
if (offsetMetadata.messageOffsetOnly) { //没有获得到完整的高水位元数据
lock.synchronized {
val fullOffset = convertToOffsetMetadataOrThrow(highWatermark) // 通过读日志文件的方式把完整的高水位元数据信息解析出来
updateHighWatermarkMetadata(fullOffset) // 然后再更新一下高水位元数据
fullOffset
}
} else { // 直接返回即可
offsetMetadata
}
}
至此,高水位相关源码就解析完毕,你是否有疑问呢?欢迎留言互相探讨。