TransientStorePool暂存池
TransientStorePool
类比较简单,采用双端队列Deque
维护了一些列的预分配的ByteBuffer
,这些ByteBuffer
都是在堆外分配的直接内存,DefaultMessageStore
会持有TransientStorePool
对象实例,如果启动时配置了启用transientStorePoolEnable,那么在DefaultMessageStore
构造函数中会调用TransientStorePool.init
方法,预分配ByteBuffer
并放入队列中,如果启动时没有启用TransientStorePool
功能,则不会调用TransientStorePool.init
方法,那么从队列中获取ByteBuffer
会返回null。
TransientStorePool
主要域如下:
//TransientStorePool
//池中预分配ByteBuffer数量
private final int poolSize;
//每个ByteBuffer大小
private final int fileSize;
//采用双端队列维护预分配的ByteBuffer
private final Deque<ByteBuffer> availableBuffers;
TransientStorePool
主要的方法如下:
//TransientStorePool
//如源码注释,因为这里需要申请多个堆外ByteBuffer,所以是个
//十分heavy的初始化方法
/**
* It's a heavy init method.
*/
public void init() {
//申请poolSize个ByteBuffer
for (int i = 0; i < poolSize; i++) {
//申请直接内存空间
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(fileSize);
final long address = ((DirectBuffer) byteBuffer).address();
Pointer pointer = new Pointer(address);
//锁住内存,避免操作系统虚拟内存的换入换出
LibC.INSTANCE.mlock(pointer, new NativeLong(fileSize));
//将预分配的ByteBuffer方法队列中
availableBuffers.offer(byteBuffer);
}
}
//销毁内存池
public void destroy() {
//取消对内存的锁定
for (ByteBuffer byteBuffer : availableBuffers) {
final long address = ((DirectBuffer) byteBuffer).address();
Pointer pointer = new Pointer(address);
LibC.INSTANCE.munlock(pointer, new NativeLong(fileSize));
}
}
//使用完毕之后归还ByteBuffer
public void returnBuffer(ByteBuffer byteBuffer) {
//ByteBuffer各下标复位
byteBuffer.position(0);
byteBuffer.limit(fileSize);
//放入队头,等待下次重新被分配
this.availableBuffers.offerFirst(byteBuffer);
}
//从池中获取ByteBuffer
public ByteBuffer borrowBuffer() {
//非阻塞弹出队头元素,如果没有启用暂存池,则
//不会调用init方法,队列中就没有元素,这里返回null
//其次,如果队列中所有元素都被借用出去,队列也为空
//此时也会返回null
ByteBuffer buffer = availableBuffers.pollFirst();
//如果队列中剩余元素数量小于配置个数的0.4,则写日志提示
if (availableBuffers.size() < poolSize * 0.4) {
log.warn("TransientStorePool only remain {} sheets.", availableBuffers.size());
}
return buffer;
}
//剩下可借出的ByteBuffer数量
public int remainBufferNumbs() {
//如果启用了暂存池,则返回队列中元素个数
if (storeConfig.isTransientStorePoolEnable()) {
return availableBuffers.size();
}
//否则返会Integer.MAX_VALUE
return Integer.MAX_VALUE;
}
如果使用的暂存池TransientStorePool
,那么创建MappedFile
使用的构造函数为:
//MappedFile
public MappedFile(final String fileName, final int fileSize,
final TransientStorePool transientStorePool) throws IOException {
init(fileName, fileSize, transientStorePool);
}
public void init(final String fileName, final int fileSize,
final TransientStorePool transientStorePool) throws IOException {
//这个重载的init方法上面已经介绍过
init(fileName, fileSize);
//不同就是这里的writeBuffer会被赋值,后续写入操作会优先
//写入writeBuffer中
this.writeBuffer = transientStorePool.borrowBuffer();
//记录transientStorePool主要为了释放时归还借用的ByteBuffer
this.transientStorePool = transientStorePool;
}
这里还要注意一下,在预分配AllocateMappedFileService
服务中,会先尝试使用ServiceLoader
扩展点加载用户自定义的MappedFile
实现,此时构造函数使用的是两个参数的构造函数,但是会显示调用具有TransientStorePool
参数的init
方法进行初始化。
是否启用暂存池TransientStorePool
并不是单独配置transientStorePoolEnable
为true就可以了,我们可以看下MessageStoreConfig.isTransientStorePoolEnable
方法的实现:
//Enable transient commitLog store pool only if transientStorePoolEnable
//is true and the FlushDiskType is ASYNC_FLUSH
public boolean isTransientStorePoolEnable() {
return transientStorePoolEnable && FlushDiskType.ASYNC_FLUSH == getFlushDiskType()
&& BrokerRole.SLAVE != getBrokerRole();
}
只有主Broker、刷盘方式为异步刷盘且transientStorePoolEnable
为true才会启用暂存池TransientStorePool
暂存池维护了一系列的堆外内存,通过将消息写到堆外内存中来提高性能。如果启用,只会向writeBuffer写入数据。否则只会向mappedByteBuffer写入数据,不会同时写入,看下图