Author:赵志乾
Date:2018-10-21
Declaration:All Right Reserved!!!
BufferPool.java
1、文件位置:
该文件在源码中的位置:kafka-2.0.0-src/clients/src/main/java/org/apache/kafka/clients/producer/internals/BufferPool.java,在IDEA导入的工程中,位于项目main3下。
2、缓冲池
池化的目的是降低创建和销毁时间,提升执行效率,即将原来的创建和销毁时间降为从池中获取和归还入池的时间。在kafka生产者客户端中,缓冲池中的内存可以分为3部分:正在使用中的内存、空闲的池化页列表、空闲的非池化页内存。其中赤化页列表中的各页大小在缓冲池创建时进行配置,而且缓冲池3个部分之间会不断的转化。
缓冲池各部分间的转化:
· 空闲池化页转为正在使用中的内存:当有线程需要分配一块儿缓冲空间时,如果要分配的缓冲空间大小恰巧等于池化页大小且空闲池化页列表非空,则取出一页进行分配,此时空闲池化页转化为正在使用中的内存;
·空闲池化页转化为空闲的非池化页内存:当有线程需要分配一块儿缓冲空间时,如果要分配的缓冲空间大小不等池化页大小且“空闲非池化页内存<需分配缓冲空间<=空闲非池化页+空闲池化页”,则将部分空闲池化页归并到空闲非池化页内存,使得空闲非池化页内存不小于要分配的缓冲空间;
·空闲非池化页空间转化为正在使用中的内存:当有线程需要分配一块儿缓冲空间时,如果要分配的缓冲空间大小不等于池化页大小且小于空闲非池化页大小时,从空闲非池化页中分配缓冲空间,从而转化为正在使用中的内存;
·正在使用中的内存转化为空闲池化页:如果缓冲区要退还给缓冲池,且缓冲区大小等于池化页大小,则将其退还至空闲池化页列表;
·正在使用中的内存转化为空闲非池化页:如果缓冲区要退还给缓冲池,且不满足退还至空闲池化页列表的条件,则将其退还至空闲非池化页空间;
3、缓冲池各部分转化示意图
4、代码逻辑见注释
package org.apache.kafka.clients.producer.internals;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.kafka.common.MetricName;
import org.apache.kafka.common.errors.TimeoutException;
import org.apache.kafka.common.metrics.Metrics;
import org.apache.kafka.common.metrics.Sensor;
import org.apache.kafka.common.metrics.stats.Meter;
import org.apache.kafka.common.utils.Time;
/*该类的实例用于维护字节缓冲池,其一些属性字段需要由生产者依据实际场景需求进行指定。*/
public class BufferPool {
static final String WAIT_TIME_SENSOR_NAME = "bufferpool-wait-time";
//缓冲池总内存
private final long totalMemory;
//池化页大小
private final int poolableSize;
//重入锁
private final ReentrantLock lock;
//空闲的池化页队列
private final Deque<ByteBuffer> free;
//排队的线程
private final Deque<Condition> waiters;
//非池化页可用内存
private long nonPooledAvailableMemory;
private final Metrics metrics;
private final Time time;
//等待时间
private final Sensor waitTime;
/*创建一个新的缓冲池,所需参数包括:可分配的最大内存、池化字节缓冲大小(free列表中一项的大
小)、实例的一些度量、时间实例、度量的逻辑组名称*/
public BufferPool(long memory, int poolableSize, Metrics metrics, Time time, String metricGrpName) {
this.poolableSize = poolableSize;
this.lock = new ReentrantLock();
this.free = new ArrayDeque<>();
this.waiters = new ArrayDeque<>();
this.totalMemory = memory;
this.nonPooledAvailableMemory = memory;
this.metrics = metrics;
this.time = time;
this.waitTime = this.metrics.sensor(WAIT_TIME_SENSOR_NAME);
MetricName rateMetricName = metrics.metricName("bufferpool-wait-ratio",
metricGrpName,
"The fraction of time an appender waits for space allocation.");
MetricName totalMetricName = metrics.metricName("bufferpool-wait-time-total",
metricGrpName,
"The total time an appender waits for space allocation.");
this.waitTime.add(new Meter(TimeUnit.NANOSECONDS, rateMetricName, totalMetricName));
}
/*分配给定大小的缓冲。如果缓冲池被配置未阻塞模式,其没有足够内存可供分配时,该方法会被阻塞。
所需参数:要分配的缓冲大小(单位为字节)、阻塞最长时间(单位为毫秒)。*/
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();
try {
// 如果要分配的缓冲大小等于池化项大小,且空闲池化列表中有可用缓存
if (size == poolableSize && !this.free.isEmpty())
则直接使用空闲列表张的缓冲
return this.free.pollFirst();
//如果当前可用内存不小于要分配的缓冲大小
int freeListSize = freeSize() * this.poolableSize;
if (this.nonPooledAvailableMemory + freeListSize >= 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);
this.waitTime.record(timeNs, time.milliseconds());
}
if (waitingTimeElapsed) {
throw new TimeoutException("Failed to allocate memory within the configured max blocking time " + maxTimeToBlockMs + " ms.");
}
remainingTimeToBlockNs -= timeNs;
//如果没有迭代累计、待分配缓冲空间大小等于池化页大小且空闲池化页列表不为空
if (accumulated == 0 && size == this.poolableSize && !this.free.isEmpty()) {
//则从空闲池化页列表进行分配
buffer = this.free.pollFirst();
accumulated = size;
} else {
//否则从非池化页空间分配当前迭代能够分配的空间大小
freeUp(size - accumulated);
int got = (int) Math.min(size - accumulated, this.nonPooledAvailableMemory);
//进行本次迭代分配空间的扣除
this.nonPooledAvailableMemory -= got;
//更新累计分配空间值
accumulated += got;
}
}
accumulated = 0;
} finally {
//循环过程中失败,则退还累计分配的空间
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 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 ByteBuffer allocateByteBuffer(int size) {
return ByteBuffer.allocate(size);
}
/*通过将空闲的池化空间归入未池化可用空间,来尽可能满足请求分配的缓冲大小*/
private void freeUp(int size) {
//如果要分配的缓冲大小大于未池化可用内存,且池化空闲列表有可用内存
while (!this.free.isEmpty() && this.nonPooledAvailableMemory < size)
//则循序将池化空间归于未池化空间
this.nonPooledAvailableMemory += this.free.pollLast().capacity();
}
//缓冲空间退还至缓冲池
public void deallocate(ByteBuffer buffer, int size) {
//加锁
lock.lock();
try {
//如果要退还的缓冲空间大小等于池化页大小,且字节缓冲空间容量等于池化页大小
if (size == this.poolableSize && size == buffer.capacity()) {
//则清除缓冲空间内容
buffer.clear();
//将缓冲空间加入空闲池化页列表
this.free.add(buffer);
} else {
//否则将其归还至非池化页内存
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());
}
//获取可用内存=未池化的可用内存+池化的可用内存
public long availableMemory() {
lock.lock();
try {
return this.nonPooledAvailableMemory + freeSize() * (long) this.poolableSize;
} finally {
lock.unlock();
}
}
// 用于测试
protected int freeSize() {
return this.free.size();
}
//获取未被使用的内存
public long unallocatedMemory() {
lock.lock();
try {
return this.nonPooledAvailableMemory;
} finally {
lock.unlock();
}
}
//获取因获取内存而被阻塞等待的线程数
public int queued() {
lock.lock();
try {
return this.waiters.size();
} finally {
lock.unlock();
}
}
//池化的空闲缓冲列表单项大小
public int poolableSize() {
return this.poolableSize;
}
//返回缓冲池管理的总内存
public long totalMemory() {
return this.totalMemory;
}
// 用于测试
Deque<Condition> waiters() {
return this.waiters;
}
}