netty学习笔记

野性酷女 2022-06-18 10:16 358阅读 0赞

Netty与原生Nio的区别

  • 原生的nio,只简单封闭了ByteBuffer以及Channel,也就是网络IO操作以及对这些字节的缓存,字节的编码与解码、粘包与拆包问题没有API,IO事件的处理只提供了最简单的Handler,没有进一步封装。这就是Netty存在的道理。Java Nio本身只实现了对底层C原生的非阻塞IO、AIO的简单封装,并没有为实际业务场景的使用提供太多的工具。
  • 不过这是符合JDK的设计原则的:关注点放在原始子功能的实现与抽象,给具体业务场景需要的进一步封装提供良好的底层工具
  • Netty通过PipleLine中的Handler链条,实现了对网络字节到业务POJO对象的编码与解码以及响应链条。
  • 通过两个NioEventLoopGroop线程池,实现了实际业务场景需要使用的并发处理。

ByteBuf

ByteBuffer的缺点

  • 长度固定,一旦分配完成,它的容量不能动态扩展与收缩,当需要编码的POJO对象大于ByteBuffer的容量时,会发生索引越界异常
  • 只有一个标识位置的指针Positions,读写的时候需要手工调用flipt和rewind等,使用者需谨慎的操作这些API,否则很容易导致程序处理失败。
  • ByteBuffer的API功能有限,一些高级和实用的特性它不支持,需要使用者自己编程实现。

ByteBuf的工作原理——与ByteBuffer基本类似

  • 7种java基础类型、byte数组、ByteBuffer等的读写;
  • 缓冲区自身的copy和slice等;
  • 设置网络字节序
  • 构造缓冲区实例
  • 操作位置指针等方法

ByteBuf对ByteBuffer的优势

  • 使用两个位置来指针协助缓冲区的读写操作,读操作用readerIndex,写操作用使用writerIndex。

    由于写操作不修改readerIndex指针,读操作不修改writerIndex指针,因此读写不再需要调整位置指针,这极大的简化了缓冲区的读写操作,避免了由于遗漏或者不熟悉flip操作导致的功能异常。

  • 写操作实现了动态扩展。

    ByteBuf对write操作进行了封装,如果可用缓冲区不足,ByteBuf会自动进行动态扩展,对使用者而言,不需要关心底层的校验和扩展细节,只要不超过设置的最大缓冲区容量即可。

Discardable bytes要注意性能问题

需要指出的是,调用discardReadBytes会发生字节数组的内存复制,所以,频繁调用将会导致性能下降,因此在调用它之前要确认你确实需要这样做,例如牺牲性能来换取更多的内存。

ByteBuf转换成标准的ByteBuffer

通过ByteBuffer nioBuffer()将当前的ByteBuf可读的缓冲区转换成ByteBuffer,两者共用一个缓冲区内容引用,虽然共用缓存引用,对ByteBuffer的读写操作并不会修改原BtyteBuf的读写索引,返回后的ByteBuffer也无法感知原Buf的动态扩展操作。

ByteBuf的分类

从内存分配的角度看

  • 堆内存(HeapByteBuf)字节缓冲区

    特点是内存的分配与回收速度快,可以被JVM自动回收;缺点就是需要额外做一次内存复制,将堆内存对应的缓冲区复制到内核Channel中,性能会有一定程序的下降。

  • 直接内存(DirectByteBuf)字节缓冲区

    非堆内存,它在堆外进行内存分配,相比于堆内存,它的分配和回收速度会慢一些,但是将它写入或者从SocketChannel中读取时,由于少了一次内存复制,速度会比堆内存快。

  • 使用经验

    ByteBuf的最佳实践是在IO通信线程的读写缓冲区使用DirectByteBuf,后端业务消息的编程码模块使用HeapByteBuf,这样组合可以达到性能最优。

从内存回收的角度看

基于对象池的ByteBuf和普通的ByteBuf,两者的主要区别就是基于对象池的ByteBuf可以重用ByteBuf对象,它自己维护了一个内存池,可以循环利用创建的ByteBuf,提升内存的使用效率,降低由于高负载导 致的频繁GC。

测试表明使用内存池后的Netty在高负载、大并发的冲击下内存和GC更加平稳。

PipeLine

概念

Netty的ChannelPipeLine和ChannelHandler机制类似于Servlet和Filter过滤器,这类拦截器实际上是职责链模式的一种变形,主要是为了方便事件的拦截和用户业务逻辑的定投。

消息在ChannelPipeLine中流动和传递,PipeLine持有IO事件拦截器ChannelHandler的链表,由ChannelHandler对IO事件进行拦截和处理。

Netty通过实现很多功能强大的的ChannelHandler来提供各种问题的解决方案,如粘包、拆包问题,提供http协议等。

Netty的线程模型

若干疑问

疑问一:为什么NioEventLoop是单线程?

确实是单线程的,因为实际是NioEventLoopGroup线程池在工作,池中包含有多个NioEventLoop。每一个NioEventLoop都包含有自己的Selector,也就是主Reactor接受Socket连接请求之后,从Reactor会dipatcher到线程池中各个NioEventLoop中的Selector中去,并注册OP_READ监听事件,这样就存在与NioEventLoop一样多的Selector在监听IO事件,提高了IO效率。否则只是一个线程在处理,肯定没法有效提高系统资源的利用率。详情可以参考:NIO Reactor模型 & Netty线程模型

疑问二:用的是多路复用而不是AIO?

确实是这样的,原因参考:官方回答
我翻译一下,windows由于实现了IOCP,可以支持AIO,但是在linux下面由于AIO底层也是使用的epoll,也就是说与多路复用使用的是同样的实现,效率自然也就不会有多少提高了。而官方显然并不看重在windows上面的实现:We obviously did not consider Windows as a serious platform so far

疑问三:什么是Reactor模型?

Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式

发表评论

表情:
评论列表 (有 0 条评论,358人围观)

还没有评论,来说两句吧...

相关阅读

    相关 netty学习笔记

    Netty与原生Nio的区别 原生的nio,只简单封闭了ByteBuffer以及Channel,也就是网络IO操作以及对这些字节的缓存,字节的编码与解码、粘包与拆包

    相关 netty学习笔记

    netty是基于nio(非阻塞io)的高性能网络通信框架 什么是阻塞 ? 一直等待在那里,知道有返回值才会处理 例如: i. Socket socket = serve

    相关 netty学习笔记

    Netty架构,特性、模块组件、线程模型(Boss线程、worker线程)以及源码、 netty架构图: ![70][] netty架构五部分: Extensible