kafka日志存储(二):ByteBufferMessageSet

使用ByteBuffer保存消息数据,提供以下三个功能。
1. 把Message集合按照指定的压缩类型进行压缩,构建ByteBufferMessageSet对象
2. 提供迭代器。
3. 提供消息验证和offset分配功能。

private def create(offsetAssigner: OffsetAssigner,
                 compressionCodec: CompressionCodec,
                 wrapperMessageTimestamp: Option[Long],
                 timestampType: TimestampType,
                 messages: Message*): ByteBuffer = {
    // 集合为空,则返回空的buffer对象
    if (messages.isEmpty)
      MessageSet.Empty.buffer
    // 不需要对message集合进行压缩
    else if (compressionCodec == NoCompressionCodec) {
      val buffer = ByteBuffer.allocate(MessageSet.messageSetSize(messages))
      //为每个消息分配offset,写入buffer
      for (message <- messages) writeMessage(buffer, message, offsetAssigner.nextAbsoluteOffset())
      buffer.rewind()
      buffer
    } else {
        //需要对消息进行压缩
        val magicAndTimestamp = wrapperMessageTimestamp match {
        case Some(ts) => MagicAndTimestamp(messages.head.magic, ts)
        case None => MessageSet.magicAndLargestTimestamp(messages)
      }
      var offset = -1L
      val messageWriter = new MessageWriter(math.min(math.max(MessageSet.messageSetSize(messages) / 2, 1024), 1 << 16))
      messageWriter.write(codec = compressionCodec, timestamp = magicAndTimestamp.timestamp, timestampType = timestampType, magicValue = magicAndTimestamp.magic) { 
        outputStream => // 创建压缩类型的输出流
        val output = new DataOutputStream(CompressionFactory(compressionCodec, magicAndTimestamp.magic, outputStream))
        try {
          for (message <- messages) {
              //遍历写入内层压缩信息。
            offset = offsetAssigner.nextAbsoluteOffset()
            if (message.magic != magicAndTimestamp.magic)
              throw new IllegalArgumentException("Messages in the message set must have same magic value")
            // Use inner offset if magic value is greater than 0
            if (magicAndTimestamp.magic > Message.MagicValue_V0)
              output.writeLong(offsetAssigner.toInnerOffset(offset))
            else
              output.writeLong(offset)
            output.writeInt(message.size)
            output.write(message.buffer.array, message.buffer.arrayOffset, message.buffer.limit)
          }
        } finally {
          output.close()
        }
      }
      val buffer = ByteBuffer.allocate(messageWriter.size + MessageSet.LogOverhead)
      //按照消息格式写入整个外层信息,注意,外层信息的offset是最后一条内层信息。
      writeMessage(buffer, messageWriter, offset)
      buffer.rewind()
      buffer
    }
}

ByteBufferMessageSet.validateMessagesAndAssignOffsets()方法实现了验证消息并分配offset的功能

private[kafka] def validateMessagesAndAssignOffsets(offsetCounter: LongRef,//分配offset的起始值
                                                      now: Long,
                                                      sourceCodec: CompressionCodec,
                                                      targetCodec: CompressionCodec,
                                                      compactedTopic: Boolean = false,
                                                      messageFormatVersion: Byte = Message.CurrentMagicValue,
                                                      messageTimestampType: TimestampType,
                                                      messageTimestampDiffMaxMs: Long): (ByteBufferMessageSet, Boolean) = {
    if (sourceCodec == NoCompressionCodec && targetCodec == NoCompressionCodec) {
      // 检测所有的magic是否和指定magic一致
      if (!isMagicValueInAllWrapperMessages(messageFormatVersion)) {
        // 不一致需要统一,需要创建新的ByteBufferMessageSet,长度可能会发送改变还进行offset的分配,验证并且更新offset
        (convertNonCompressedMessages(offsetCounter, compactedTopic, now, messageTimestampType, messageTimestampDiffMaxMs,
          messageFormatVersion), true)
      } else {
        // 非压缩消息且magic统一。
        (validateNonCompressedMessagesAndAssignOffsetInPlace(offsetCounter, now, compactedTopic, messageTimestampType,
          messageTimestampDiffMaxMs), false)
      }
    } else {
      // Deal with compressed messages
      // We cannot do in place assignment in one of the following situations:
      // 1. Source and target compression codec are different
      // 2. When magic value to use is 0 because offsets need to be overwritten
      // 3. When magic value to use is above 0, but some fields of inner messages need to be overwritten.
      // 4. Message format conversion is needed.

      // 检查1:消息压缩的类型和此Brokers指定的压缩类型不一致,需要新压缩。/
      
      var inPlaceAssignment = sourceCodec == targetCodec && 
          //检查1:magic为零
          messageFormatVersion > Message.MagicValue_V0

      var maxTimestamp = Message.NoTimestamp
      val expectedInnerOffset = new LongRef(0)
      val validatedMessages = new mutable.ArrayBuffer[Message]
      //遍历内层压缩信息,此步骤会解压
      this.internalIterator(isShallow = false).foreach { messageAndOffset =>
        val message = messageAndOffset.message
        validateMessageKey(message, compactedTopic)

        if (message.magic > Message.MagicValue_V0 && messageFormatVersion > Message.MagicValue_V0) {
          // 检查3,检查时间戳
          validateTimestamp(message, now, messageTimestampType, messageTimestampDiffMaxMs)
          // 检查内部offset是否正常
          if (messageAndOffset.offset != expectedInnerOffset.getAndIncrement())
            inPlaceAssignment = false
          maxTimestamp = math.max(maxTimestamp, message.timestamp)
        }

        if (sourceCodec != NoCompressionCodec && message.compressionCodec != NoCompressionCodec)
          throw new InvalidMessageException("Compressed outer message should not have an inner message with a " +
            s"compression attribute set: $message")

        // 检查4:消息格式需要转换
        if (message.magic != messageFormatVersion)
          inPlaceAssignment = false

        validatedMessages += message.toFormatVersion(messageFormatVersion)
      }
        //不满足条件,不能复用当前ByteBufferMessageSet的场景
      if (!inPlaceAssignment) {
        val wrapperMessageTimestamp = {
          if (messageFormatVersion == Message.MagicValue_V0)
            Some(Message.NoTimestamp)
          else if (messageFormatVersion > Message.MagicValue_V0 && messageTimestampType == TimestampType.CREATE_TIME)
            Some(maxTimestamp)
          else // Log append time
            Some(now)
        }

        // 创建新的并压缩
        (new ByteBufferMessageSet(compressionCodec = targetCodec,
                                  offsetCounter = offsetCounter,
                                  wrapperMessageTimestamp = wrapperMessageTimestamp,
                                  timestampType = messageTimestampType,
                                  messages = validatedMessages: _*), true)
      } else {
        // 复用.ByteBufferMessageSet。不需要重新压缩
        buffer.putLong(0, offsetCounter.addAndGet(validatedMessages.size) - 1)
        // validate the messages
        validatedMessages.foreach(_.ensureValid())

        var crcUpdateNeeded = true
        val timestampOffset = MessageSet.LogOverhead + Message.TimestampOffset
        val attributeOffset = MessageSet.LogOverhead + Message.AttributesOffset
        val timestamp = buffer.getLong(timestampOffset)
        val attributes = buffer.get(attributeOffset)
        if (messageTimestampType == TimestampType.CREATE_TIME && timestamp == maxTimestamp)
          // We don't need to recompute crc if the timestamp is not updated.
          crcUpdateNeeded = false
        else if (messageTimestampType == TimestampType.LOG_APPEND_TIME) {
          // 更新外层消息的offset,为内部最后一条压缩消息的offset
          buffer.putLong(timestampOffset, now)
          buffer.put(attributeOffset, messageTimestampType.updateAttributes(attributes))
        }

        if (crcUpdateNeeded) {
          // 更新外层信息的时间戳,和crc32
          buffer.position(MessageSet.LogOverhead)
          val wrapperMessage = new Message(buffer.slice())
          Utils.writeUnsignedInt(buffer, MessageSet.LogOverhead + Message.CrcOffset, wrapperMessage.computeChecksum)
        }
        buffer.rewind()
        (this, false)
      }
    }
  }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值