【IO】ByteBuf和Channel和Pipeline

BytetBuf

ByteBuf就是JDK nio中Buffer的新轮子
buffer 的主要目的进行流量整形,把突发的大数量较小规模的 I/O 整理成平稳的小数量较大规模的 I/O,以减少响应次数

ByteBuffer:

  1. 长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当需要编码的POJO对象大于ByteBuffer的容量时,会发生索引越界异常;
  2. ByteBuffer只有一个标识位控的指针position,读写的时候需要手工调用flip()和rewind()等,使用者必须小心谨慎地处理这些API,否则很容易导致程序处理失败;
  3. ByteBuffer的API功能有限,一些高级和实用的特性它不支持,需要使用者自己编程实现。
  1. 需要的话,可以自定义buffer类型;
  2. 通过组合buffer类型,可实现透明的zero-copy;
  3. 提供动态的buffer类型,如StringBuffer一样,容量是按需扩展;
    • 如果c<t,则n从阈值t(4MB)开始,以每次增加2倍的方式扩容,直到双倍后的大小小于c;
    • 如果c>t,则n=c/t*t+t
  4. 无需调用flip()方法;方法反转(讲Buffer从读模式变成写模式)
  5. 常常比ByteBuffer(JDK的)快
  6. 使用了读写两个指针,分别记录读写的位置,复杂操作更简单

堆内存和直接内存

NIO的Buffer提供了一个可以不经过JVM内存直接访问系统物理内存的类——DirectBuffer。 DirectBuffer类继承自ByteBuffer,但和普通的ByteBuffer不同,普通的ByteBuffer仍在JVM堆上分配内存,其最大内存受到最大堆内存的限制;而DirectBuffer直接分配在物理内存中,并不占用堆空间,其可申请的最大内存受操作系统限制。

直接内存的读写操作比普通Buffer快,但它的创建、销毁比普通Buffer慢(猜测原因是DirectBuffer需向OS申请内存涉及到用户态内核态切换,而后者则直接从堆内存划内存即可)。

因此直接内存使用于需要大内存空间且频繁访问的场合,不适用于频繁申请释放内存的场合。

Note:DirectBuffer并没有真正向OS申请分配内存,其最终还是通过调用Unsafe的allocateMemory()来进行内存分配。不过JVM对Direct Memory可申请的大小也有限制,可用-XX:MaxDirectMemorySize=1M设置,这部分内存不受JVM垃圾回收管理。

最佳实践:在I/O通信线程的读写缓冲区使用DirectByteBuf,后端业务消息的编解码模块使用HeapByteBuf。

从内存回收角度看,ByteBuf分2类:

基于对象池的ByteBuf和普通ByteBuf。

两者的主要区别就是基于对象池的ByteBuf可以重用ByteBuf对象,它自己维护一个内存池,可以循环利用创建的ByteBuf,提高内存使用效率,降低由于高负载导致的频繁GC。测试表明使用内存池后的Netty在高负载、大并发的冲击下内存和GC更加平稳。

内存池化

池化的简单实现思路,是基于JVM堆内存之上,构建更高一层内存池,通过调用内存池allocate方法获取内存空间,调用release方法将内存区域归还内存池。内存池面临的首要问题是碎片回收,内存池在频繁申请和释放空间后,还能有尽可能连续的内存空间用于大块内存空间的分配。基于这个需求,有两种算法用于优化这一块的内存分配:伙伴系统和slab系统。

netty4相对于netty3的一大改进就是引入了内存池化技术,用以解决高速网络通信过程中,netty造成的应用内存锯齿状消费和大量gc的问题。

Netty中的零拷贝

其实netty中并没有实现真正的零拷贝,netty中的零拷贝更多的应该理解为少拷贝或者说复用(reuse),操作系统的零拷贝是避免了CPU将数据从一个内存区域拷贝到另一个内存区域,而netty中的数据操作全部是在用户态。当真正要通过netty将数据发送到网络时,仍然需要将数据从用户态拷贝到内核态,此时就无法做到真正的零拷贝了。
Netty的零拷贝(或者说ByteBuf的复用)主要体现在以下几个方面:

  • DirectByteBuf通过直接在堆外分配内存的方式,避免了数据从堆内拷贝到堆外的过程
  • 通过组合ByteBuf类:即CompositeByteBuf,将多个ByteBuf合并为一个逻辑上的ByteBuf, 而不需要进行数据拷贝
  • 通过各种包装方法, 将 byte[]、ByteBuf、ByteBuffer等包装成一个ByteBuf对象,而不需要进行数据的拷贝
  • 通过slice方法, 将一个ByteBuf分解为多个共享同一个存储区域的ByteBuf, 避免了内存的拷贝,这在需要进行拆包操作时非常管用
  • 通过FileRegion包装的FileChannel.tranferTo方法进行文件传输时, 可以直接将文件缓冲区的数据发送到目标Channel, 减少了通过循环write方式导致的内存拷贝。但是这种方式是需要得到操作系统的零拷贝的支持的,如果netty所运行的操作系统不支持零拷贝的特性,则netty仍然无法做到零拷贝

Channel

  • Channel:封装了jdk原生的channel,提供统一的API,作为其它各个功能组件的容器。

  • ChannelPipeline:责任链模式的核心组件,ChannelHandler的容器,按顺序组织各个ChannelHandler,并在它们之间转发事件。

  • ChannelHandlerContext:封装一个具体的ChannelHandler,并为ChannelHandler的执行提供一个线程环境(ChannelHandlerInvoker)可以理解为ChannelPipeline链路上的一个节点,节点里面包含有指向前后节点的指针,事件在各个ChannelHandler之间传递,靠的就是ChannelHandlerContext。

  • ChannelHandlerInvoker:顾名思义,是ChannelHandler的一个Invoker,它存在的意义是为ChannelHandler提供一个运行的线程环境,默认的实现DefaultChannelHandlerInvoker有一个EventExecutor类型的成员,就是Netty的EventLoop线程,所以默认ChannelHandler的处理逻辑在EventLoop线程内。当然也可以提供不同的实现,替换默认的线程模型。

从Netty内部IO线程接读到IO数据,依次经过N个Handler到达最内部的逻辑处理单元,这种称之为Inbound Handler;从Channel发出IO请求,依次经过M个Handler到达Netty内部IO线程,这种称之为Outbound Handler。内部代码实现流程则是:Head -> Tail (Inbound),Tail -> Head (Outbound)。

网关

网关类产品的主要功能就是消息的预处理和转发,请求和响应对象都是“朝生夕灭”类型的,在高并发场景下,一定要防止不合理的内存申请,具体措施如下。

(1)内存按需分配。不要一次性申请较大的内存来保存较小的消息,造成内存空间浪费,引发频繁GC问题。
(2)不要频繁地创建和释放对象。这会增加GC的负担,降低系统的吞吐量,可以采用内存池等机制优化内存的申请和释放。
(3)减少对象拷贝。对于透传类的消息,尽量把涉及业务逻辑处理的字段放入Header,不要对 Body 做解码,直接透传到后端服务即可。这样可以大幅减少内存的申请和对象拷贝,降低内存占用,提升性能。
(4)流控机制必不可少。除了客户端并发连接数流控、QPS流控,还需要针对内存占用等指标做流控,防止业务高峰期的OOM。

(1)Netty作为一个通用的 NIO框架,不能对用户的应用场景进行假设,可以使用它做流式计算,也可以用它做 RCP 框架,不同的应用场景,传输的码流大小千差万别,无论初始化时分配的是 32KB 还是 1MB,都会随着应用场景的变化而变得不合适。因此,Netty根据上次实际读取的码流大小对下次的接收 Buffer 缓冲区进行预测和调整,能够最大限度地满足不同行业的应用场景的需要。
(2)综合性能更高。分配容量过大会导致内存占用开销增加,后续的Buffer处理性能下降;容量过小需要频繁地内存扩张来接收大的请求消息,同样会导致性能下降。
(3)更节约内存。假如通常情况请求消息大小平均值为1MB左右,接收缓冲区大小为1.2MB,突然某个客户发送了一个10MB的附件,接收缓冲区扩张为10MB以读取该附件,如果缓冲区不能收缩,每次缓冲区创建都会分配 10MB 的内存,但是后续所有的消息都是 1MB左右的,这样会导致内存的浪费,如果并发客户端过多,可能会导致内存溢出并宕机。

参考

https://www.jianshu.com/p/f7c668cd05cd

https://www.itcodemonkey.com/article/4655.html

https://blog.csdn.net/qq157538651/article/details/93537187

https://www.cnblogs.com/z-sm/p/6235157.html

https://sq.163yun.com/blog/article/213832853624152064

https://www.cnblogs.com/xys1228/p/6088805.html

http://www.52im.net/thread-99-1-1.html

https://blog.csdn.net/zjuclh/article/details/51002491

http://docs.52im.net/extend/docs/src/netty4/

posted @ 2019-08-28 10:38  colin_xun  阅读(543)  评论(0编辑  收藏  举报