摸鱼逼逼赖赖-Netty(一) Netty的零拷贝机制

363 阅读3分钟

Netty的ByteBuf

Netty实现了自己的ByteBuf,是对JDK的ByteBuffer的优化。

JDK的ByteBuffer的缺点:

  • 无法动态扩容。若数据大于ByteBuffer容量,会发生索引越界异常。
  • API使用复杂。需手工调用flip()和rewind()等方法。

Netty的ByteBuffer做了以下四个方面的增强:

  • API操作便捷性
  • 动态扩容
  • 多种ByteBuf实现
  • 高效的零拷贝机制

ByteBuf操作

ByteBuf有三个重要属性:capacity容量、readerIndex读取容量、writeIndex写入位置。同时提供了两个指针变量来支持顺序读和写操作,分别是readerIndex和写操作writerIndex。 缓冲区如下图: capacity表示是容量。readerIndex指针左边的是已读可丢弃的区域,表示已经读取过一遍,可以丢弃了。而readerIndex和writerIndex指针之间的是可读区域。writerIndex右边的则是尚未被写入的待写区域。

ByteBuf动态扩容

capacity默认值为256字节,其最大值为2GB。 在Netty调用写方法的时候,会通过AbstractByteBufAllocator.calculateNewCapacity(新capacity最小要求,capacity最大值)进行判断。

根据对新的capacity的最小值要求,其对应的计算方法有: 4M为一个固定的阈值:CALCULATE_THRESHOLD。

没超过4M

从64字节开始,每次增加一倍,直至计算出来的newCapacity满足新容量最小要求。

超过4M

新容量 = 新容量最小要求/4M* 4M + 4M

至于为什么这么扩容,别问我,我不知道。

多种ByteBuf实现

有8种具体的实现 在使用中,都是通过ByteBufAllocator分配器进行申请,同时,分配器具备有内存管理的功能。 在Netty里面常用PooledUnsafeDirectByteBuf,一切都是为了快,三秒真男人。 PooledByteBufAllocator.ioBuffer运作过程:

每个线程都有一个对应的PoolThreadCache,里面有一个Arena负责buf的分配管理。先拿一个ByteBuf对象。从可复用对象池RECYCLER里面拿一个ByteBuf,若没有则创建。拿到对象之后就要为之分配内存。每一个线程在对应的缓冲中维护了三大块内存缓存,如果要存放一个小于512byte的数据, 就回到对应的缓存块。

如果没有可用缓存,就直接向JVM申请新的缓存unpool,不会进入缓存池中。

零拷贝机制

Netty的零拷贝机制,是一种应用层的实现。与底层JVM和操作系统没有关系。

1、CompositeByteBuf,将多个ByteBuf合并为一个逻辑上的ByteBuf,从而避免各个ByteBufe之间的拷贝。Netty提供了一个虚拟的复合缓冲区。这个缓冲区内部依然是两个独立的buffer,但是在逻辑上已经是一体的。

CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
ByteBuf newBuffer = compositeByteBuf.addComponents(true,buffer1,buffer2);

2、wrapedBuffer()方法,将byte[]数据包装成ByteBuf对象。

对于数组array内的数据不会进行变动,而仅仅是在buffer里的memory里面引用array。

ByteBuf newBuffer = Unpooled.wrappedBuffer(new byte[{1,2,3}]);

3、slice()方法,将一个ByteBuf对象切分成多个ByteBuf对象。

比如已有一个buffer记录着"hello",我们要调取其中的"ll",拆分出来,新的buffer引用原来的原有的buffer的“ll”的引用。

ByteBuf buffer1 = Unpooled.wrappedBuffer("hello".getBytes());
ByteBuf newBuffer = buffer1.slice(1,2);