概述
用过netty,大家都知道在请求处理之前,会有一个缓冲区用于接受数据,不同场景对缓冲的大小都不太一样。
比如UDP协议的DatagramChannel,默认缓冲区大小只给了2048,而假如开发一个SyslogUdp的协议服务,大小其实就不止这么点。
因此,缓冲区怎么用,怎么设置就非常关键啦,很不小心就会踩坑,本文主要给大家讲解下netty4下接受缓冲区的原理及对源码进行解读…
结构分解
Netty的接收缓冲区,是由接口类RecvByteBufAllocator实现而来,该类有个子接口Handle,包含了三个方法:
interface Handle {
/**
* 创建一个合适大小的接收缓冲区(大到足够读取所有inbound数据,小到不会存在数据浪费)
*/
ByteBuf allocate(ByteBufAllocator alloc);
/**
* 猜测这个缓冲区的容量,之所有是猜测,是因为存在动态扩容的一种缓冲区
*/
int guess();
/**
* 记录上一次读操作实际读取的字节数,以便接收缓冲区能动态调整一个合适的容量
*
* @param actualReadBytes the actual number of read bytes in the previous read operation
*/
void record(int actualReadBytes);
}
了解完基类,应该可以知道设计者的意图,提供多种灵活方式来创建接收缓冲区,比如固定空间大小的,空间动态扩容缩的等…
这个有什么好处呢,对比起JDK原生的NIO类库使用的java.nio.ByteBuffer,实际是一个固定长度的byte数组,这也说明原生buffer无法动态扩容,相关代码如下:
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>
{
// These fields are declared here rather than in Heap-X-Buffer in order to
// reduce the number of virtual method invocations needed to access these
// values, which is especially costly when coding small buffers.
//
final byte[] hb; // Non-null only for heap buffers
final int offset;
boolean isReadOnly; // Valid only for heap buffers
讲好处前先踩踩原生ByteBuffer固定空间的坏处,例如开发人员一开始很难预测到每条消息报文的长度,或者消息堆积空间所需大小,当然你可以说干脆直接分配一个比较大的ByteBuffer,这通常没问题,不过对于海量推送、高并发等场景,这会给服务器带来沉重的内存负担,也算是一种资源浪费啊。
举个详细点例子:例如海量推送服务,单条消息若最大上限是16K,消息平均大小是6K,为了满足消息支持16K的处理,我们需要把buffer设置成16K,这样子的话,因是海量链路推送,那么假如并发链接数为100w,每个链路都有独立的ByteBuffer接收缓冲区,那将会额外损耗的总内存为:100,0000 * (16K-6K) = 9764M。是不是吓一跳,竟然快要消耗一个G的内存了,大内存不仅增加硬件成本,而且会导致长时间的FGC,对系统的维护和稳定保障带来非常大的冲击。
那讲完坏处,相比好处就显而易见啦,能灵活调整内存空间大小,是一件多么棒的事情。实际上RecvByteBufAllocator提供了两种实现,分别是:AdaptiveRecvByteBufAllocator和FixedRecvByteBufAllocator
我们先来看看简单的FixedRecvByteBufAllocator,代码逻辑如下:
public class FixedRecvByteBufAllocator implements RecvByteBufAllocator {
private static final class HandleImpl implements Handle {
private final int bufferSize;
HandleImpl(int bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public ByteBuf allocate(ByteBufAllocator alloc) {
return alloc.ioBuffer(bufferSize);
}
@Override
public int guess() {
return bufferSize;
}
@Override
public void record(int actualReadBytes) { }
}
private final Handle handle;
/**
* Creates a new predictor that always returns the same prediction of
* the specified buffer size.
*/
public FixedRecvByteBufAllocator(int bufferSize) {
if (bufferSize <= 0) {