Netty入门(一):ByteBuf

       网络数据的基本单位总是字节。Java NIO 提供了 ByteBuffer 作为它的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐。Netty 的 ByteBuffer 替代品是 ByteBuf,一个强大的实现,既解决了 JDK API 的局限性,又为网络应用程序的开发者提供了更好的 API

Netty系列索引:

1.Netty入门(一):ByteBuf

2.Netty入门(二):Channel

ByteBuf优势

  • 它可以被用户自定义的缓冲区类型扩展
  • 通过内置的复合缓冲区类型实现了透明的零拷贝
  • 容量可以按需增长
  • 在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法
  • 读和写使用了不同的索引
  • 支持方法的链式调用
  • 支持引用计数
  • 支持池化

ByteBuf实现原理

如图ByteBuf通维护了两个不同的索引:一个用于读取,一个用于写入。

当你从 ByteBuf 读取时,它的 readerIndex 将会被递增已经被读取的字节数。同样地,当你写入 ByteBuf 时,它的writerIndex 也会被递增

当调用readBytes时,readIndex会相应移动length位,如果readIndex移动后大于writeIndex则会抛异常。

当调用writeBytes时,writeIndex会相应移动length位,且通过ensureWritable方法实现自动扩容

其他常用API

getBytes 获取可读字节数组
setBytes 写入字节
discardReadBytes 废弃已读字节
mark 标记index
reset 将index重置到之前标记的位置(配合mark使用)
isReadable 如果至少有一个字节可供读取,则返回 true
isWritable 如果至少有一个字节可被写入,则返回 true
readableBytes 返回可被读取的字节数
writableBytes 返回可被写入的字节数
capacity 返回 ByteBuf 可容纳的字节数。在此之后,它会尝试再次扩展直到达到 maxCapacity()
maxCapacity 返回 ByteBuf 可以容纳的最大字节数
hasArray 如果 ByteBuf 由一个字节数组支撑,则返回 true
array 如果 ByteBuf 由一个字节数组支撑则返回该数组;否则,它将抛出一个UnsupportedOperationException 异常

ByteBuf缓冲分类

1、Heap buffer(堆缓冲区):

就是将数据存在JVM堆空间中,在没有被池化的情况可以快速分配和释放。

优点:由于数据是存储在JVM堆中,因此可以快速的创建与快速的释放,并且它提供了直接访问内部字节数组的方法。

缺点:每次读写数据时,都需要先将数据复制到直接缓冲区中再进行网路传输。

2、Direct buffer(直接缓冲区):

直接缓冲区,在堆外直接分配内存空间,直接缓冲区并不会占用堆的容量空间,因为它是由操作系统在本地内存进行的数据分配。

优点:在使用Socket进行数据传递时,性能非常好,因为数据直接位于操作系统的本地内存中,所以不需要从JVM将数据复制到直接缓冲区中 。

缺点:因为Direct Buffer是直接在操作系统内存中的,所以内存空间的分配与释放要比堆空间更加复杂,而且速度要慢一些。

注意:

如果你的数据包含在一个在堆上的分配的缓冲区中,那么事实上,在通过套接字发送他之前,jvm将会在内部把你的缓冲区复制到一个直接缓冲区中;这样分配释放就比较浪费资源;

建议:

直接缓冲区并不支持通过字节数组的方式来访问数据。对于后端业务的消息编解码来说,推荐使用HeapByteBuf;对于I/O通信线程在读写缓冲区时,推荐使用DirectByteBuf;

3、Composite Buffer 复合缓冲区:

可以拥有以上两种的缓冲区,通过一种聚合视图来操作底层持有的多种类型Buffer。这种缓冲,jdk nio是没有这种特性的。

ByteBuf主要实现类

pooled:池化,重用ByteBuf对象

Direct:直接内存,内部通过ByteBuffer实现,典型装饰模式

Heap:堆内存,内部持有byte数组

(1)UnpooledDirectByteBuf:

在堆外进行内存分配的非内存池ByteBuf,内部持有ByteBuffer对象,相关操作委托给ByteBuffer实现。

(2)UnpooledHeapByteBuf:

基于堆内存分配非内存池ByteBuf,即内部持有byte数组。

(3)UnpooledUnsafeDirectByteBuf:

和另外一个类UnpooledDirectByteBuf差不多相同,区别在于UnpooledUnsafeDirectByteBuf内部使用基于PlatformDependent相关操作实现ByteBuf,依赖平台。

(4)ReadOnlyByteBufferBuf:

只读ByteBuf,内部持有ByteBuffer对象,相关操作委托给ByteBuffer实现,该ByteBuf限内部使用;

(5)FixedCompositeByteBuf:

用于将多个ByteBuf组合在一起,形成一个虚拟的只读ByteBuf对象,不允许写入和动态扩展。内部使用Object[]将多个ByteBuf组合在一起,一旦FixedCompositeByteBuf对象构建完成,则不会被更改。

(6)CompositeByteBuf:

用于将多个ByteBuf组合在一起,形成一个虚拟的ByteBuf对象,支持读写和动态扩展。内部使用List组合多个ByteBuf。一般使用使用ByteBufAllocator的compositeBuffer()方法,Unpooled的工厂方法compositeBuffer()或wrappedBuffer(ByteBuf... buffers)创建CompositeByteBuf对象。

(7)PooledByteBuf:

基于内存池的ByteBuf,主要为了重用ByteBuf对象,提升内存的使用效率;适用于高负载,高并发的应用中。主要有PooledDirectByteBuf,PooledHeapByteBuf,PooledUnsafeDirectByteBuf三个子类,PooledDirectByteBuf是在堆外进行内存分配的内存池ByteBuf,PooledHeapByteBuf是基于堆内存分配内存池ByteBuf,PooledUnsafeDirectByteBuf也是在堆外进行内存分配的内存池ByteBuf,区别在于PooledUnsafeDirectByteBuf内部使用基于PlatformDependent相关操作实现ByteBuf,具有平台相关性。

ByteBufHolder

利用组合的方式对ByteBuf进行扩展。实际应用中我们经常发现,除了实际的数据负载之外,我们还需要存储各种属性值。HTTP 响应便是一个很好的例子,除了表示为字节的内容,还包括状态码、cookie 等。为了处理这种常见的用例,Netty 提供了 ByteBufHolder。ByteBufHolder 也为 Netty 的高级特性提供了支持,如缓冲区池化,其中可以从池中借用 ByteBuf,并且在需要时自动释放。

ByteBufAllocator

为了降低分配和释放内存的开销,Netty 通过 ByteBufAllocator 实现了(ByteBuf 的)池化,它可以用来分配我们所描述过的任意类型的 ByteBuf 实例。Netty提供了两种ByteBufAllocator的实现:PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。

ReferenceCounted

类似GC引用计数法,Netty中ByteBuf和ByteBufHolder都实现了该接口,其主要通过方法retain(release)来增加(减少)资源被引用的次数,当引用为0时,代表该资源可以被释放。

posted @ 2021-08-05 15:55  新人十三  阅读(1344)  评论(0编辑  收藏  举报