Netty中的Buffer——此篇就够了

314 阅读4分钟

由于细节内容实在太多啦,所以只把部分知识点整理出来粗略的介绍,每个小节点里面都有更细化的内容!

接下来开始分享啦

Buffer

Java NIO中的Buffer用于和NIO通道进行交互,数据可以从通道读入缓冲区,也可以从缓冲区写入到通道中.所以说,Buffer其实就是一块可以读写数据的内存,我们将其包装为一个Java对象来提供一系列读写操作.  Netty并没有直接使用Java NIO的Buffer实现,而是自己实现了一套Buffer框架来满足自己的业务或者性能需求.

ByteBuf的基本原理

读写指针的作用

不同于NIO Buffer的读写指针共用原理,ByteBuf拥有readerIndex,writerIndex两个指针.下面我们就来详细的讲解一下ByteBuf的内部原理.

     +-------------------+------------------+------------------+
     | discardable bytes |  readable bytes  |  writable bytes  |
     |                   |     (CONTENT)    |                  |
     +-------------------+------------------+------------------+
     |                   |                  |                  |
     0      <=      readerIndex   <=   writerIndex    <=    capacity

从示意图中我们可以看出readerIndex和writerIndex最多可以将整个内容空间划分为三块:废弃区,可读区和可写区.下面我们就来看一下不同操作下的两个指针的变化.

  • 在初始化状态下,假设capacity为20,readerIndex和writerIndex都为0,整个空间中只存在可写区.此时只能写,不能读,进行读操作会抛出异常.

       +---------------------------------------------------------+
       |             writable bytes (got more space)             |
       +---------------------------------------------------------+
       |                                                         |
    

    readerIndex(0) writerIndex(0) <= capacity

  • 写入10个字节的数据,writerIndex指向10,readerIndex不会改变,所有内容空间中有可读区和可写区.大小都是10字节.

       +-------------------+------------------+------------------+
       |  readable bytes  |  writable bytes                      |
       |     (CONTENT)    |                                      |
       +--------- --------+------------------+------------------
       |                  |                                      |
     readerIndex(0) <= writerIndex(10)           <=        capacity
    
  • 读取5个字节的内容,writerIndex不变,readerIndex加5,指向了5.此时内容空间分为了5字节的废弃区,5字节的可读区和10字节的可写区.

    +-------------------+------------------+------------------+ | discardable bytes | readable bytes | writable bytes | | | (CONTENT) | | +-------------------+------------------+------------------+ | | | | 0 <= readerIndex(5) <= writerIndex(10) <= capacity

  • 调用discardReadBytes方法后,将废弃区的内容舍弃掉,readerIndex又指向了0,writerIndex指向了5,相当于可读区和可写区整体向左平移了5个字节.

       +------------------+--------------------------------------+
       |  readable bytes  |    writable bytes (got more space)   |
       +------------------+--------------------------------------+
       |                  |                                      |
    

    readerIndex (0) <= writerIndex (5) <= capacity

零拷贝

OS层次上Zero-copy,就是在操作数据时,不需要将数据buffer从一个内存区域拷贝到另一个内存区域,因为减少了一次内存的拷贝,因此CPU的效率得到了提升.  Netty的zero-copy体现在很多方面.比如Buffer的compose,duplicate,slice操作时不会拷贝底层的数据.而是通过ByteBuf对象的组合来实现上述的操作

  • Netty提供了CompositeByteBuf类,可以将多个ByteBuf组合成一个逻辑上的Buffer,避免了各个buffer之间的拷贝,CompositeByteBuf并不拥有底层的数据,而是通过拥有两个buffer对象,从这两个buffer对象中获取数据来对外提供看似合并了的数据.比如我们将一份协议数据的头部buffer和消息体buffer合并成一个Buffer.

Netty中的Buffer——此篇就够了

  • 如上图所示,所有底层的数据还是存储在header和body这两个真实的buffer中.
  • 对于ByteBuf的slice和duplicate操作也是如此,不同的buffer共享了相同的底层数据,而不是进行底层数据的拷贝.具体使用到的Buffer类型为DuplicatedByteBuf和SlicedByteBuf.谁说是共享的底层数据,但是通过对writerIndex和readerIndex两个指针的操作来实现slice和duplicate的功能.
  • Netty使用wrap操作将byte数组转化为ByteBuf对象时,将byte数组包裹到对象中,而不是拷贝数组存放到对象中.
  • Netty 中使用 FileRegion 实现文件传输的零拷贝, 不过在底层 FileRegion 是依赖于 Java NIO FileChannel.transfer 的零拷贝功能.

Pool和Reference Count

4.0之后的版本实现了高性能的Buffer池,分配策略则是结合了buddy allocation和slab allocation的jemalloc变种,实现类为PoolArena,这样的话,可以在频繁分配和释放Buffer时缓解GC压力,还可以在初始化新buffer时减少内存带宽消耗(初始化时不可避免的要给buffer数组赋初始值).  ByteBuf引入了Reference Count机制,你需要在不适用它的时候调用ReferenceCountUtil.release方法来减少它的引用.

总结

通过上面的介绍,相信大家对Buffer方面有了进一步的认识,希望能够帮助到大家!