一 BufferPool
上一篇已经整理了ProducerBatch,本文继续看BufferPool。ByteBuffer的创建和释放时比较消耗资源的,池化的目的是降低创建和销毁时间,提升执行效率,即将原来的创建和销毁时间降为从池中获取和归还入池的时间。为了实现内存的高效利用,Kafka客户端使用BufferPool来实现ByteBuffer的复用。在package org.apache.kafka.clients.producer.internals。
/**
* A pool of ByteBuffers kept under a given memory limit. This class is fairly specific to the needs of the producer. In
* particular it has the following properties:
* <ol>
* <li>There is a special "poolable size" and buffers of this size are kept in a free list and recycled
* <li>It is fair. That is all memory is given to the longest waiting thread until it has sufficient memory. This
* prevents starvation or deadlock when a thread asks for a large chunk of memory and needs to block until multiple
* buffers are deallocated.
* </ol>用来管理bytebuffer
*/
public class BufferPool {
static final String WAIT_TIME_SENSOR_NAME = "bufferpool-wait-time";
//整个pool的大小
private final long totalMemory;
//bufferpool只针对特定大小poolableSize的bytebuffer进行管理
private final int poolableSize;
//加锁控制并发,保证线程安全
private final ReentrantLock lock;
//缓存了指定了大小的ByteBuffer对象
private final Deque<ByteBuffer> free;
//记录了因为申请不到足够空间而阻塞的线程(实际记录的是阻塞线程对应的condition对象)
private final Deque<Condition> waiters;
/** Total available memory is the sum of nonPooledAvailableMemory and the number of byte buffers in free * poolableSize. */
//总共可用内存=nonPooledAvailableMemory+free*poolableSize
private long nonPooledAvailableMemory;
private final Metrics metrics;
private final Time time;
private final Sensor waitTime;
每个BufferPool对象只针对特定大小(由poolableSize字段指定)的ByteBuffer进行管理,对于其他大小的ByteBuffer并不会缓存进BufferPool。一般情况下,我们会调整MemoryRecords的大小(RecordAccumulator.batchSize字段指定),使每个MemoryRecords可以缓存多条消息。但是当一条消息的字节数大于MemoryRecords时,就不会复用BufferPool中缓存的ByteBuffer,而是额外分配ByteBuffer,在它被使用完后也不会放到BufferPool进行管理,而是让GC回收。如果经常出现这种情况就要考虑调整batchSize的配置了。
这里2.1版本基本上跟老版本一致,区别就是为了更加直观的理解,nonPooledAvailableMemory 就是空闲的非池化内存。总共可用内存=nonPooledAvailableMemory+free*poolableSize。可以分为:正在使用中的内存、空闲的池化页列表free、空闲的非池化页内存nonPooledAvailableMemory 。
BufferPool.allocate()方法负责从缓冲池中申请ByteBuffer,当缓冲池中空间不足的时候,会阻塞调用线程。deallocate()释放内存。下面看一下源码。
/**
* Allocate a buffer of the given size. This method blocks if there is not enough memory and the buffer pool
* is configured with blocking mode.
*
* @param size The buffer size to allocate in bytes
* @param maxTimeToBlockMs The maximum time in milliseconds to block for buffer memory to be available
* @return The buffer
* @throws InterruptedException If the thread is interrupted while blocked
* @throws IllegalArgumentException if size is larger than the total memory controlled by the pool (and hence we would block
* forever)
* 从缓冲池申请给定大小的ByteBuffer,。如果缓冲池被配置未阻塞模式,其没有足够内存可供分配时,该方法会被阻塞。
*/
public ByteBuffer allocate(int size, long maxTimeToBlockMs) throws InterruptedException {
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;
this.lock.lock();//加锁(因为有多线程并发分配和回收ByteBuffer)
try {
// check if we have a free buffer of the right size pooled
// 如果请求的size是poolableSize,且free中有空闲的ByteBuffer
if (size == poolableSize && !this.free.isEmpty())
return this.free.pollFirst();//直接返回合适的ByteBuffer
// now check if the request is immediately satisfiable with the
// memory on hand or if we need to block
//(请求的大小不是poolableSize,会尽可能让未池化内存满足分配条件,当未池化内存不够用时,会尽量把free的归并到未池化内存
//计算free队列的空间
int freeListSize = freeSize() * this.poolableSize;
if (this.nonPooledAvailableMemory + freeListSize >= size) {//满足
// we have enough unallocated or pooled memory to immediately
// satisfy the request, but need to allocate the buffer
//为了让nonPooledAvailableMemory >申请的size.freeUP()会从free队列中不断释放ByteBuffer,只到满足这次申请
freeUp(size);
this.nonPooledAvailableMemory -= size;//nonPooledAvailableMemory扣除要分配的内存大小
} else {
// we are out of memory and will have to block
//没有足够空间,只能阻塞
int accumulated = 0;
Condition moreMemory = this.lock.newCondition();
try {
long remainingTimeToBlockNs = TimeUnit.MILLISECONDS.toNanos(maxTimeToBlockMs);
this.waiters.addLast(moreMemory);//把Condition添加到waiters中
// loop over and over until we have a buffer or have reserved
// enough memory to allocate one 循环等待至有足够空间进行分配
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 (waitingTimeElapsed) {//超时报错
throw new TimeoutException("Failed to allocate memory within the configured max blocking time " + maxTimeToBlockMs + " ms.");
}
remainingTimeToBlockNs -= timeNs;//剩余时间
// check if we can satisfy this request from the free list,
// otherwise allocate memory(如果没有迭代累计、检查请求的是poolableSize的大小,且free有空闲的bytebuffer)
if (accumulated == 0 && size == this.poolableSize && !this.free.isEmpty()) {
// just grab a buffer from the free list 则从空闲池化页列表进行分配
buffer = this.free.pollFirst();
accumulated = size;
} else {
// we'll need to allocate memory, but we may only get
// part of what we need on this iteration
//先分配一部分空间,并继续等待空闲空间
freeUp(size - accumulated);
int got = (int) Math.min(size - accumulated, this.nonPooledAvailableMemory);
//进行本次迭代分配空间的扣除
this.nonPooledAvailableMemory -= got;
accumulated += got;//更新累计分配空间值
}
}
// Don't reclaim memory on throwable since nothing was thrown
accumulated = 0;
} finally {
// When this loop was not able to successfully terminate don't loose available memory
//循环过程中失败,则退还累计分配的空间
this.nonPooledAvailableMemory += accumulated;
this.waiters.remove(moreMemory);//已分配空间,移除等待condition
}
}
} finally {
// signal any additional waiters if there is more memory left
// over for them 要是还有空闲空间,就唤醒下一个线程
try {
if (!(this.nonPooledAvailableMemory == 0 && this.free.isEmpty()) && !this.waiters.isEmpty())
this.waiters.peekFirst().signal();
} finally {
// Another finally... otherwise find bugs complains
lock.unlock();//解锁
}
}
//如果缓冲分配的不是池化页
if (buffer == null)
//则进行非池化页空间的实际分配
return safeAllocateByteBuffer(size);
else
return buffer;
}
// Protected for testing
protected void recordWaitTime(long timeNs) {
this.waitTime.record(timeNs, time.milliseconds());
}
/**
* Allocate a buffer. If buffer allocation fails (e.g. because of OOM) then return the size count back to
* available memory and signal the next waiter if it exists.
* *分配缓冲。如果分配失败,则将退还分配的数量,并通知下一个排队线程。
*/
private ByteBuffer safeAllocateByteBuffer(int size) {
boolean error = true;//默认分配失败
try {//分配缓冲
ByteBuffer buffer = allocateByteBuffer(size);
error = false; //成功分配,刷新标志位
return buffer;
} finally {
//分配失败
if (error) {
//加锁
this.lock.lock();
try {
//退还分配的缓冲
this.nonPooledAvailableMemory += size;
//如果存在其他排队线程
if (!this.waiters.isEmpty())
//则通知下一个排队线程进行缓冲分配
this.waiters.peekFirst().signal();
} finally {//释放锁
this.lock.unlock();
}
}
}
}
// Protected for testing.
protected ByteBuffer allocateByteBuffer(int size) {
return ByteBuffer.allocate(size);
}
/**
* Attempt to ensure we have at least the requested number of bytes of memory for allocation by deallocating pooled
* buffers (if needed)
*/
private void freeUp(int size) {
//池化空闲列表有可用内存且于未池化可用内存《分配的内存大小
while (!this.free.isEmpty() && this.nonPooledAvailableMemory < size)
//则顺序将池化空间归于未池化空间
this.nonPooledAvailableMemory += this.free.pollLast().capacity();
}
/**
* Return buffers to the pool. If they are of the poolable size add them to the free list, otherwise just mark the
* memory as free.
*
* @param buffer The buffer to return
* @param size The size of the buffer to mark as deallocated, note that this may be smaller than buffer.capacity
* since the buffer may re-allocate itself during in-place compression
*/
public void deallocate(ByteBuffer buffer, int size) {
//加锁
lock.lock();
try {
//要释放的ByteBuffer的大小是poolableSize ,且字节缓冲空间容量等于池化页大小
if (size == this.poolableSize && size == buffer.capacity()) {
//则清除缓冲空间内容
buffer.clear();
//将缓冲空间加入空闲池化页列表
this.free.add(buffer);
} else {//否则将其归还至非池化页内存,修改下nonPooledAvailableMemory值
this.nonPooledAvailableMemory += size;
}
//唤醒一个因为空间不足而阻塞的线程
Condition moreMem = this.waiters.peekFirst();
if (moreMem != null)
moreMem.signal();
} finally { //释放锁
lock.unlock();
}
}
public void deallocate(ByteBuffer buffer) {
deallocate(buffer, buffer.capacity());
}
/**
* the total free memory both unallocated and in the free list
* 获取可用内存=未池化的可用内存+池化的可用内存
*/
public long availableMemory() {
lock.lock();
try {
return this.nonPooledAvailableMemory + freeSize() * (long) this.poolableSize;
} finally {
lock.unlock();
}
}