图解 Kafka 源码实现机制之客户端缓存架构

本文深入解析Kafka客户端缓存机制,重点介绍ProducerBatch的内部构造,如BufferStream、ByteBuffer的关系及扩容、压缩功能。此外,详细探讨了客户端缓存池BufferPool的设计,包括其内存管理策略以及CopyOnWriteMap在读写分离场景的应用,以保证高并发下的性能。
摘要由CSDN通过智能技术生成
ecords = 0;
    private float actualCompressionRatio = 1;
    private long maxTimestamp = RecordBatch.NO_TIMESTAMP;
    private long offsetOfMaxTimestamp = -1;
    private Long lastOffset = null;
    private Long firstTimestamp = null;
    private MemoryRecords builtRecords;

从该类属性字段来看比较多,这里只讲2个关于字节流的字段。

  1. CLOSED_STREAM :当关闭某个 ByteBuffer 也会把它对应的写操作输出流设置为 CLOSED_STREAM, 目的就是防止再向该 ByteBuffer 写数据 ,否则就抛异常。
  2. bufferStream :首先 MemoryRecordsBuilder 依赖 ByteBuffer 来完成消息存储。它会将 ByteBuffer 封装成 ByteBufferOutputStream 并实现了 Java NIO 的 OutputStream,这样就可以按照流的方式写数据了。同时 ByteBufferOutputStream 提供了 自动扩容 ByteBuffer 能力 。

来看看它的初始化构造方法。

public MemoryRecordsBuilder(ByteBuffer buffer,...) {  
    this(new ByteBufferOutputStream(buffer), ...);
}
public MemoryRecordsBuilder(
    ByteBufferOutputStream bufferStream,
    ...
    int writeLimit) {
        ....    
        this.initialPosition = bufferStream.position();      
        this.batchHeaderSizeInBytes = AbstractRecords.recordBatchHeaderSizeInBytes(magic, compressionType);    
        bufferStream.position(initialPosition + batchHeaderSizeInBytes);
        this.bufferStream = bufferStream;     
        this.appendStream = new DataOutputStream(compressionType.wrapForOutput(this.bufferStream, magic));
    }
}

从构造函数可以看出,除了基本字段的赋值之外,会做以下3件事情:

  1. 根据消息版本、压缩类型来 计算批次 Batch 头的大小长度 。
  2. 通过 调整 bufferStream 的 position ,使其跳过 Batch 头部位置,就可以直接写入消息了。
  3. 对 bufferStream 增加压缩功能 。

看到这里,挺有意思的,不知读者是否意识到这里涉及到 「ByteBuffer」、「bufferStream」 、「appendStream」。

三者的关系是通过「装饰器模式」实现的,即 bufferStream 对 ByteBuffer 装饰实现扩容功能,而 appendStream 又对 bufferStream 装饰实现压缩功能。

来看看它的核心方法。

(1)appendWithOffset()

public Long append(long timestamp, ByteBuffer key, ByteBuffer value, Header[] headers) {
   return appendWithOffset(nextSequentialOffset(), timestamp, key, value, headers);
}
private long nextSequentialOffset() { 
  return lastOffset == null ? baseOffset : lastOffset + 1;
}
private Long appendWithOffset(
  long offset,
  boolean isControlRecord, 
  long timestamp, 
  ByteBuffer key,
  ByteBuffer value, 
  Header[] headers) {
    try {    
        if (isControlRecord != isControlBatch)
            throw new ...;     
        if (lastOffset != null && offset <= lastOffset)
            throw new ...;   
        if (timestamp < 0 && timestamp != RecordBatch.NO_TIMESTAMP)
           throw new ...;    
        if (magic < RecordBatch.MAGIC_VALUE_V2 && headers != null && headers.length > 0)
           throw new ...;    
        if (firstTimestamp == null)
           firstTimestamp = timestamp;   
        if (magic > RecordBatch.MAGIC_VALUE_V1)         {
            appendDefaultRecord(offset, timestamp, key, value, headers);
            return null;
        } else {       
            return appendLegacyRecord(offset, timestamp, key, value, magic);
        }
    } catch (IOException e) {
        
    }
}

该方法主要用来根据偏移量追加写消息,会根据消息版本来写对应消息,但需要明确的是 ProducerBatch 对标 V2 版本。

来看看 V2 版本消息写入逻辑。

private void appendDefaultRecord(
  long offset, 
  long timestamp, 
  ByteBuffer key, 
  ByteBuffer value,
  Header[] headers) throws IOException {   
    ensureOpenForRecordAppend(); 
    int offsetDelta = (int) (offset - baseOffset);
    long timestampDelta = timestamp - firstTimestamp;
    int sizeInBytes = DefaultRecord.writeTo(appendStream, offsetDelta, timestampDelta, key, value, headers);
    recordWritten(offset, timestamp, sizeInBytes);
}
private void ensureOpenForRecordAppend() {
    if (appendStream == CLOSED_STREAM)
        throw new ...;
}
private void recordWritten(long offset, long timestamp, int size) {
  .... 
  numRecords += 1; 
  uncompressedRecordsSizeInBytes += size; 
  lastOffset = offset;
  if (magic > RecordBatch.MAGIC_VALUE_V0 && timestamp > maxTimestamp) {    
      maxTimestamp = timestamp;   
      offsetOfMaxTimestamp = offset;
  }
}

该方法主要用来写入 V2 版本消息的,主要做以下5件事情:

  1. 检查是否可写 :判断 appendStream 状态是否为 CLOSED_STREAM,如果不是就可写,否则抛异常。
  2. 计算本次要写入多少偏移量。
  3. 计算本次写入和第一次写的时间差。
  4. 按照 V2 版本格式 写入 appendStream 流 中,并返回压缩前的消息大小。
  5. 成功后 更新 RecordBatch 的元信息 。

(2)hasRoomFor()

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值