Kafka为了提高吞吐量,Producer采用批量发送数据的模式。Producer可以通过配置设置整个batch缓冲区的大小以及每一个batch的大小:
buffer.memory= //默认32MB
batch.size= //默认16KB
当消息达到把batch大小,就会在buffer中申请一定大小的空间,封装成一个新的batch。然后Producer就会以batch为单位发送数据。
如果按照常规设计,我们只需要在需要申请空间的时候判断缓冲区可用空间是否足够,然后new出新的对象,然后发送完了之后GC自己会释放空间,我们只需要管理缓冲区可用空间大小即可。但是这样有一个缺点,当吞吐量很高的时候,程序需要频繁的new对象,GC释放空间,给程序带来非常大的负载。
而Kafka正是用来处理高吞吐量的,所以Kafka在这里涉及了内存池BufferPool来减少创建对象和GC带来的消耗。
设计思路
BufferPool中通过下面两个变量来维护空间:
long nonPooledAvailableMemory
Deque<ByteBuffer> free //可用的byteBuffer空间,每一个ByteBuffer大小就是上面配置的batch.size
整个缓冲区可用的空间 = nonPooledAvailableMemory + free * batch.size
在最开始整个缓冲区中free是空的,所有的内存空间都在nonPooledAvailableMemory中,每要创建一个batch(batch的大小正好是batch.size)就会从nonPooledAvailableMemory获取空间,用完释放空间时,空间不会回收到nonPooledAvailableMemory中,而是将ByteBuffer放到free中。那么当下一次需要创建batch的时候,如果free中有没有使用的ByteBuffer,就直接从free中获取。
而对于需要创建size大于batch.size的batch时,永远都是直接从nonPooledAvailableMemory获取空间,并且释放时放回nonPooledAvailableMemory中。如果nonPooledAvailableMemory不够时,会从free中释放一些ByteBuffer给nonPooledAvailableMemory。
为什么有一些batch size不是配置的size大小呢?
因为有一些消息体本身很大,大小超过batch.size,这些消息每一条会创建一个ProducerBatch。
Kafka Producer单条消息最大默认是1MB。
对于这些消息,Kafka Producer其实相当于是一条一条发送消息,并且在BufferPool中并没有很好的利用ByteBuffer,所以他们会影响Kafka Producer的吞吐量的。
所以在实际的生产环境中要根据消息的大小调整batch.size的大小
如果nonPooledAvailableMemory + free * batch.size的大小也不够创建batch时,程序就会等待别的正在使用的batch释放空间,这个block时间默认是1min。
代码
释放空间
public void deallocate(ByteBuffer buffer, int size) {
lock.lock();
try {
if (size == this.poolableSize && size == buffer.capacity()) {
//batch.size大小的资源直接放到free中
buffer.clear();
this.free.add(buffer);
} else {
//不是batch.size大小的资源放到nonPooledAvailableMemory中
this.nonPooledAvailableMemory += size;
}
Condition moreMem = this.waiters.peekFirst();
if (moreMem != null)
moreMem.signal();
} finally {
lock.unlock();
}
}
申请空间
public ByteBuffer allocate(int size, long maxTimeToBlockMs) throws InterruptedException {
... ...
try {
//如何申请的资源正好是BATCH.SIZE的大小,并且free中有没有使用的ByteBuffer,直接使用
if (size == poolableSize && !this.free.isEmpty())
return this.free.pollFirst();
int freeListSize = freeSize() * this.poolableSize;
//总的可以使用的内存大小 = this.nonPooledAvailableMemory + freeListSize
if (this.nonPooledAvailableMemory + freeListSize >= size) {
//nonPooledAvailableMemory内存不够用时,释放掉一些free中的byteBuffer,知道够size用
freeUp(size);
this.nonPooledAvailableMemory -= size;
} else {
//资源不够用的时候,等待资源
int accumulated = 0;
Condition moreMemory = this.lock.newCondition();
try {
long remainingTimeToBlockNs = TimeUnit.MILLISECONDS.toNanos(maxTimeToBlockMs);
this.waiters.addLast(moreMemory);
//循环直到内存够用
while (accumulated < size) {
long startWaitNs = time.nanoseconds();
long timeNs;
boolean waitingTimeElapsed;
try {
//等待有内存资源释放
waitingTimeElapsed = !moreMemory.await(remainingTimeToBlockNs, TimeUnit.NANOSECONDS);
} finally {
long endWaitNs = time.nanoseconds();
timeNs = Math.max(0L, endWaitNs - startWaitNs);
recordWaitTime(timeNs);
}
if (this.closed)
throw new KafkaException("Producer closed while allocating memory");
if (waitingTimeElapsed) {
//等待超时报错
this.metrics.sensor("buffer-exhausted-records").record();
throw new BufferExhaustedException("Failed to allocate memory within the configured max blocking time " + maxTimeToBlockMs + " ms.");
}
remainingTimeToBlockNs -= timeNs;
//因为上面其他batch释放了资源,所以在此尝试获取资源
if (accumulated == 0 && size == this.poolableSize && !this.free.isEmpty()) {
//如果是batch.size,并且释放的资源使得free部位空时,就从free中获取byteBuffer直接使用
buffer = this.free.pollFirst();
accumulated = size;
} else {
//nonPooledAvailableMemory内存不够用时,释放掉一些free中的byteBuffer给nonPooledAvailableMemory,知道够size用
freeUp(size - accumulated);
int got = (int) Math.min(size - accumulated, this.nonPooledAvailableMemory);
this.nonPooledAvailableMemory -= got;
accumulated += got;
}
}
accumulated = 0;
} finally {
//没有成功获取到资源,把获取的一部分资源交还给nonPooledAvailableMemory
this.nonPooledAvailableMemory += accumulated;
this.waiters.remove(moreMemory);
}
}
} finally {
try {
//有内存资源剩余的时候,通知的资源等候着
if (!(this.nonPooledAvailableMemory == 0 && this.free.isEmpty()) && !this.waiters.isEmpty())
this.waiters.peekFirst().signal();
} finally {
lock.unlock();
}
}
if (buffer == null)
return safeAllocateByteBuffer(size);
else
return buffer;
}
private void freeUp(int size) {
//nonPooledAvailableMemory内存不够用时,释放掉一些free中的byteBuffer,直到够size用
while (!this.free.isEmpty() && this.nonPooledAvailableMemory < size)
this.nonPooledAvailableMemory += this.free.pollLast().capacity();
}