为了防止Log文件过大,把log切分成很多个日志文件,每个日志文件对应一个LogSegment,在LogSegment中封装一个FileMessageSet和OffsetIndex对象。
class LogSegment(val log: FileMessageSet,//操作对应日志文件FileMessageSet对象
val index: OffsetIndex,//操作索引文件的OffsetIndex对象
val baseOffset: Long,//第一条消息的offset值
val indexIntervalBytes: Int,//索引项之间间隔的最下字节数
val rollJitterMs: Long,
time: Time) extends Logging {
//自从上次添加索引项之和,在日志文件中累计加入的Message集合的字节数,判断下次索引项添加的时机。
private var bytesSinceLastIndexEntry = 0
//LogSegment的创建时间,当调用truncateTo方法吧整个日志文件清空的时候,此字段会重置为当前时间
var created = time.milliseconds
}
LogSegment.append()实现了追加消息的功能。第一个参数是第一条消息的offset,如果是压缩的消息,则是内层消息的第一个offset。
def append(offset: Long, messages: ByteBufferMessageSet) {
if (messages.sizeInBytes > 0) {
trace("Inserting %d bytes at offset %d at position %d".format(messages.sizeInBytes, offset, log.sizeInBytes()))
//是否满足添加索引项目的条件
if(bytesSinceLastIndexEntry > indexIntervalBytes) {
//添加索引
index.append(offset, log.sizeInBytes())
this.bytesSinceLastIndexEntry = 0
}
// 写日志文件,更新bytesSinceLastIndexEntry
log.append(messages)
this.bytesSinceLastIndexEntry += messages.sizeInBytes
}
}
read方法读取消息,有四个参数
// startOffset指定读取的起始消息的offset
// maxOffset 读取结束的offset,可以为空
// maxSize 指定读取的最大字节数
// maxPosition 指定读取的最大物理地址
def read(startOffset: Long, maxOffset: Option[Long], maxSize: Int, maxPosition: Long = size): FetchDataInfo = {
if(maxSize < 0)
throw new IllegalArgumentException("Invalid max size for log read (%d)".format(maxSize))
// 日志文件的长度
val logSize = log.sizeInBytes // this may change, need to save a consistent copy
// 转换为物理地址,通过offsetIndex.lookup和FileMessageSet.searchFor,查找方法已在之前的博客中介绍
val startPosition = translateOffset(startOffset)
// if the start position is already off the end of the log, return null
if(startPosition == null)
return null
val offsetMetadata = new LogOffsetMetadata(startOffset, this.baseOffset, startPosition.position)
// if the size is zero, still return a log segment but with zero size
if(maxSize == 0)
return FetchDataInfo(offsetMetadata, MessageSet.Empty)
// 计算读取的字节数
val length = maxOffset match {
case None =>
// maxOffset为空,由maxPosition、maxSize决定读取的长度
min((maxPosition - startPosition.position).toInt, maxSize)
case Some(offset) =>
// maxOffset换成物理地址
if(offset < startOffset)
return FetchDataInfo(offsetMetadata, MessageSet.Empty)
val mapping = translateOffset(offset, startPosition.position)
val endPosition =
if(mapping == null)
logSize // the max offset is off the end of the log, use the end of the file
else
mapping.position
min(min(maxPosition, endPosition) - startPosition.position, maxSize).toInt
}
//返回消息
FetchDataInfo(offsetMetadata, log.read(startPosition.position, length))
}
LogSegment的recover方法根据日志文件重新索引文件。
def recover(maxMessageSize: Int): Int = {
// 清空索引文件,只是移动position指针,后续的写入会覆盖原有的内容。
index.truncate()
//修改索引文件的大小
index.resize(index.maxIndexSize)
//记录了已经通过验证的字节数
var validBytes = 0
//最后一个索引项对应的物理地址
var lastIndexEntry = 0
//FileMessageSet迭代器
val iter = log.iterator(maxMessageSize)
try {
while(iter.hasNext) {
val entry = iter.next
entry.message.ensureValid()
//符合添加索引的条件
if(validBytes - lastIndexEntry > indexIntervalBytes) {
// we need to decompress the message, if required, to get the offset of the first uncompressed message
val startOffset =
entry.message.compressionCodec match {
case NoCompressionCodec =>
entry.offset
case _ =>
//对消息进行解压缩,获取第一个消息的offset
ByteBufferMessageSet.deepIterator(entry).next().offset
}
//添加索引项
index.append(startOffset, validBytes)
lastIndexEntry = validBytes
}
//累加validBytes
validBytes += MessageSet.entrySize(entry.message)
}
} catch {
case e: CorruptRecordException =>
logger.warn("Found invalid messages in log segment %s at byte offset %d: %s.".format(log.file.getAbsolutePath, validBytes, e.getMessage))
}
//对日志文件进行截断,抛弃后面验证失败的message
val truncated = log.sizeInBytes - validBytes
log.truncateTo(validBytes)
index.trimToValidSize()
truncated
}