Kafka源码剖析:内存池

1. Why BufferPool ?

Kafka Producer以ProducerBatch为单位发送数据,而ProducerBatch中的数据以ByteBuffer的形式进行存储。当发送端数据量极大时,ByteBuffer就会无限制地频繁申请,可能会引发OOM;另外,发送完数据后,ByteBuffer就会释放,会频繁的引发FullGC,影响Kafka性能。
为此,设计了Kafka Producer端的内存池,它具有以下功能:

  1. 限制可以申请的内存总量totalMemory,防止OOM,totalMemory可以通过KafkaProducer配置项指定;
  2. free池持有ProducerRecord的引用,减少FullGC的频率;

KafkaProducer发送流程:
在这里插入图片描述
KafkaProducer相关数据结构:
在这里插入图片描述

1.1 Why two kinds of pools ?

为什么需要free池?
new一个对象时,需要经历申请对象空间、设置引用(引用常量池中的常量,引用堆中的对象)等过程,代价较大。而如果能够直接从一个容器中取出已经实例化好的对象,则可以省去以上步骤,避免频繁的实例化。而要想将ByteBuffer都实例化好,则必然需要给ByteBuffer设定一个大小即poolableSize。

为什么需要availableMemory池?
由于free池中的ByteBuffer对象都是固定大小的,而KafkaProducer端发送的数据未必都能被ByteBuffer装下,因此遇到size > poolableSize的时候,我们需要通过availableMemory池来申请ByteBuffer对象。

2. What is a BufferPool ?

名词解释:
free:该池子中存储大小等于poolableSize的ByteBuffer;

availableMemory:内存池中,除了free池和已申请的ByteBuffer,剩余的字节大小。物理上,其处于JVM堆内存中,只是通过nonPooledAvailableMemory标记来约束其可以从堆内存申请的字节大小;

totalMemory:内存池可以申请的ByteBuffer字节总大小;

poolableSize:free池中ByteBuffer的固定大小;
在这里插入图片描述

3. How BufferPool run ?

步骤:

  1. 如果申请的ByteBuffer size超过totalMemory,抛异常;
  2. 如果申请的size符合预设的poolableSize,则从free池获取;
  3. 如果申请的size不符合预设的poolableSize,但是free池和availableMemory池的总大小可以满足
    1. 尝试从availableMemory池直接申请;
    2. 如果availableMemory池容量小于size,释放free池中的ByteBuffer,添加到availableMemory池;(nonPooledAvailableMemory + = poolableSize),然后再从availableMemory池直接申请;
  4. free池和availableMemory池的总大小不足矣满足size大小,则等待已申请的ByteBuffer释放,ProducerRecord在成功发送到broker后(参考Sender#handleProduceResponse),会进行ByteBuffer的释放。释放的字节大小会重新回到free池或availableMemory池,释放的字节大小通过accumulated计数器进行技术,当accumulated >= size,再进行申请;

流程图:
在这里插入图片描述
源码:


public class BufferPool {
   

    static final String WAIT_TIME_SENSOR_NAME = "bufferpool-wait-time";
	// 内存池最多可以申请的字节大小
    private final long totalMemory;
    // free池中单个ByteBuffer的大小
    private final int poolableSize;
    // free池用一个双端队列来持有,因此其中的ByteBuffer不会被GC
    private final Deque<ByteBuffer> free;
    // 用于等待已申请的ByteBuffer释放的条件锁
    private final Deque<Condition> waiters;
	// 用于标记AvailableMemory的大小
    private long nonPooledAvailableMemory;


    public ByteBuffer allocate(int size, long maxTimeToBlockMs) throws InterruptedException {
   
    	// 申请的size大于内存池总大小,抛异常,可以通过KafkaProducer进行重新设置
        if (size > this.totalMemory)
            throw new IllegalArgumentException("Attempt to allocate " + size
                                               + " bytes, but there is a hard limit of "
                                               + this.totalMemory
                                               + " on memory allocations.");

        ByteBuffer buffer = null;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值