kafka日志存储(一):FileMessageSet

FileMessageSet
FileMessageSet管理日志上一篇博客中介绍的日志文件。主要字段有:

class FileMessageSet private[kafka](
                                    //磁盘上对应的日志文件
                                    @volatile var file: File,
                                    //jdk的FileChannel类型,读写对应的日志文件
                                    private[log] val channel: FileChannel,
                                    //FileMessageSet除了表示一个完整的日志文件,还可以表示日志的分片,start和end表示分片的起始位置和结束位置。
                                    private[log] val start: Int,
                                    private[log] val end: Int,
                                    //表示当前文件是否是分片
                                    isSlice: Boolean) extends MessageSet with Logging {
    //FileMessageSet的大小,单位是字节。如果FileMessageSet是日志文件的分片,则表示分片的大小。如果是完整的日志,则记录整个文件的大小。
    //可能存在多个handle线程并发向同一个分区写入信息,所以用AtomicInteger
    private val _size =
    if(isSlice)
      new AtomicInteger(end - start) // don't check the file size if this is just a slice view
    else
      new AtomicInteger(math.min(channel.size.toInt, end) - start)
  
    /* 如果不是分片,则更新position到文件末尾 */
    if (!isSlice)
        channel.position(math.min(channel.size.toInt, end))
    
    //构造函数:
    def this(file: File, fileAlreadyExists: Boolean, initFileSize: Int, preallocate: Boolean) =
      this(file,
        //如果使用了preallocate进行预分配空间,则end被初始化为0
        channel = FileMessageSet.openChannel(file, mutable = true, fileAlreadyExists, initFileSize, preallocate),
        start = 0,
        end = ( if ( !fileAlreadyExists && preallocate ) 0 else Int.MaxValue),
        isSlice = false)
        
    def openChannel(file: File, mutable: Boolean, fileAlreadyExists: Boolean = false, initFileSize: Int = 0, preallocate: Boolean = false): FileChannel = {
        if (mutable) {//FileChannel是否可写
          if (fileAlreadyExists)
            //文件已经存在
            new RandomAccessFile(file, "rw").getChannel()
          else {
            //进行文件预分配
            if (preallocate) {
              val randomAccessFile = new RandomAccessFile(file, "rw")
              randomAccessFile.setLength(initFileSize)
              randomAccessFile.getChannel()
            }
            else
              new RandomAccessFile(file, "rw").getChannel()
          }
        }
        else
          new FileInputStream(file).getChannel()//创建只读的FileChannel
  }
}

FileMessageSet.append()实现了写日志文件的功能,参数是ByteBufferMessagingSet:

class FileMessageSet{
  def append(messages: ByteBufferMessageSet) {
    val written = messages.writeFullyTo(channel)
    _size.getAndAdd(written)
  }
}
  /** Write the messages in this set to the given channel */
object ByteBufferMessageSet {
  def writeFullyTo(channel: GatheringByteChannel): Int = {
    buffer.mark()
    var written = 0
    while (written < sizeInBytes)
      //ByteBufferMessageSet中的数据全部写入文件。
      written += channel.write(buffer)
    buffer.reset()
    written
  }
}

查找指定消息的功能在searchFor方法中实现。从指定的startingPosition开始逐条遍历FileMessageSet中的消息。

def searchFor(targetOffset: Long, startingPosition: Int): OffsetPosition = {
    //其实位置
    var position = startingPosition
    //用于读取offset+size的ByteBuffer(大小为12)
    val buffer = ByteBuffer.allocate(MessageSet.LogOverhead)
    //当前FileMessageSet的大小
    val size = sizeInBytes()
    //从position开始逐条遍历
    while(position + MessageSet.LogOverhead < size) {
      //重置指针,准备读入数据
      buffer.rewind()
      //读取offset+size
      channel.read(buffer, position)
      if(buffer.hasRemaining)
        throw new IllegalStateException("Failed to read complete buffer for targetOffset %d startPosition %d in %s"
                                        .format(targetOffset, startingPosition, file.getAbsolutePath))
      //重置指针,准备从byteBuffer中读出数据
      buffer.rewind()
      val offset = buffer.getLong()
      //判断消息的offset是否服务退出条件
      if(offset >= targetOffset)
        // 符合条件,把offset对应的position封装成OffsetPosition返回。
        return OffsetPosition(offset, position)
      //没找到则获取消息的size,移动position,准备读取下个消息。
      val messageSize = buffer.getInt()
      if(messageSize < Message.MinMessageOverhead)
        throw new IllegalStateException("Invalid message size: " + messageSize)
      position += MessageSet.LogOverhead + messageSize
    }
    null
}

FileMessageSet的truncateTo方法,负责把日志文件截断到targetSize


def truncateTo(targetSize: Int): Int = {
    val originalSize = sizeInBytes
    //检测targetSize的有效性
    if(targetSize > originalSize || targetSize < 0)
      throw new KafkaException("Attempt to truncate log segment to " + targetSize + " bytes failed, " +
                               " size of this log segment is " + originalSize + " bytes.")
    if (targetSize < channel.size.toInt) {
      //裁剪文件,重置size
      channel.truncate(targetSize)
      channel.position(targetSize)
      _size.set(targetSize)
    }
    //返回裁剪的字节数
    originalSize - targetSize
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值