关于java:Netty简介及ByteBuf组件原理

42次阅读

共计 3847 个字符,预计需要花费 10 分钟才能阅读完成。

Netty 是什么

Netty 是由 JBOSS 提供的一个 Java 开源框架。Netty 提供异步的、基于事件驱动的网络应用程序框架,用以疾速开发高性能、高可靠性的网络 IO 程序,是目前最风行的 NIO 框架,Netty 在互联网畛域、大数据分布式计算畛域、游戏行业、通信行业等取得了宽泛的利用,出名的 Elasticsearch、Dubbo 框架外部都采纳了 Netty。

为什么应用 Netty

JavaNIO 的缺点

NIO 的类库和 API 繁冗,应用麻烦,你须要熟练掌握 Selector、ByteBuffer ServerSocketChannel、SocketChannel 等。
须要具备其它的额定技能做铺垫,例如相熟 Java 多线程编程,因为 NIO 编程波及到 Reactor 模式,你必须对多线程和网络编程十分相熟,能力编写出高质量的 NIO 程序。
可靠性能力补齐,开发工作量和难度都十分大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异样码流的解决等等,NIO 编程的特点是性能开发绝对容易,然而可靠性能力补齐工作量和难度都十分大。
JDK NIO 的 BUG,例如 epoll bug,它会导致 Selector 空轮询,最终导致 CPU 100%。官网宣称在 JDK1.6 版本的 update18 修复了该问题,然而直到 JDK1.7 版本该问题仍旧存在,只不过该 bug 产生概率升高了一些而已,它并没有被基本解决。

Netty 的长处从以下几个方面形容

应用简略:封装了 NIO 的很多细节,应用更简略;

功能强大:预置了多种编解码性能,反对多种支流协定;

定制能力强:能够通过 ChannelHandler 对通信框架进行灵便的扩大;

性能高:通过与其余业界支流的 NIO 框架比照,Netty 的综合性能最优;

稳固:Netty 修复了曾经发现的 NIO 的 bug,让开发人员能够专一于业务自身;

社区沉闷:Netty 是沉闷的开源我的项目,版本迭代周期短,bug 修复速度快。

Netty 的线程模型

Netty 采纳的线程模型是 Reactor 线程模型。Reactor 模型,是指通过一个或多个输出同时传递给服务处理器的服务申请的事件驱动解决模式。服务端程序处理传入多路申请,并将它们同步分派给申请对应的解决线程。

Reactor 模式也叫 Dispatcher 模式,即 I / O 多了复用对立监听事件,收到事件后散发 (Dispatch) 给某过程,是编写高性能网络服务器的必备技术之一。

Reactor 模型中有 2 个要害组件

Reactor 在一个独自的线程中运行,负责监听和散发事件,分发给适当的处理程序来对 IO 事件做出反馈。它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人。
Handlers 处理程序执行 I / O 事件要实现的理论事件,相似于客户想要与之交谈的公司中的理论官员。Reactor 通过调度适当的处理程序来响应 I / O 事件,处理程序执行非阻塞操作。

Netty 的 Reactor 的具体实现:

Netty 通过 Reactor 模型基于多路复用器接管并解决用户申请,外部实现了两个线程池,boss 线程池和 work 线程池,其中 boss 线程池的线程负责解决申请的 accept 事件,当接管到 accept 事件的申请时,把对应的 socket 封装到一个 NioSocketChannel 中,并交给 work 线程池,其中 work 线程池负责申请的 read 和 write 事件,由对应 Handler 解决。其中 MainReactor 负责客户端的连贯申请,并将申请转交给 SubReactor。SubReactor 负责相应通道的 IO 读写申请。非 IO 申请(具体逻辑解决)的工作则会间接写入队列,期待 worker threads 进行解决。
Netty 的组件
Bootstrap、ServerBootstrap:Bootstrap 意思是疏导,一个 Netty 利用通常由一个 Bootstrap 开始,次要作用是配置整个 Netty 程序,串联各个组件,Netty 中 Bootstrap 类是客户端程序的启动疏导类,ServerBootstrap 是服务端启动疏导类。Future、ChannelFuture:在 Netty 中所有的 IO 操作都是异步的,不能立即得悉音讯是否被正确处理,然而能够过一会等它执行实现或者间接注册一个监听,具体的实现就是通过 Future 和 ChannelFutures,他们能够注册一个监听,当操作执行胜利或失败时监听会主动触发注册的监听事件。Selector:Netty 基于 Selector 对象实现 I / O 多路复用,通过 Selector,一个线程能够监听多个连贯的 Channel 事件,当向一个 Selector 中注册 Channel 后,Selector 外部的机制就能够主动一直地查问(select) 这些注册的 Channel 是否有已就绪的 I / O 事件(例如可读,可写,网络连接实现等),这样程序就能够很简略地应用一个线程高效地治理多个 Channel。
ChannelHandler:是一个接口,解决 I / O 事件或拦挡 I / O 操作,并将其转发到其 ChannelPipeline 中的下一个处理程序。ChannelHandler 自身并没有提供很多办法,因为这个接口有许多的办法须要实现,方便使用期间能够继承它的子类,如下:

ChannelInboundHandler 用于解决入站 I / O 事件;
ChannelOutboundHandler 用于解决出站 I / O 操作。

或者应用以下适配器类:

ChannelInboundHandlerAdapter 用于解决入站 I / O 事件;
ChannelOutboundHandlerAdapter 用于解决出站 I / O 操作。

ChannelHandlerContext:保留 Channel 相干的所有上下文信息,同时关联一个 ChannelHandler 对象;ChannelPipline:保留 ChannelHandler 的 List,用于解决或拦挡 Channel 的入站事件和出站操作。ChannelPipeline 实现了一种高级模式的拦挡过滤器模式,使用户能够齐全管制事件的解决形式,以及 Channel 中各个的 ChannelHandler 如何互相交互。

read 操作从 head 往 tail,write 操作从 tail 往 head

**ByteBuf 组件

JavaNIO 提供了缓存容器(ByteBuffer),然而应用简单。因而 Netty 引入缓存 ButeBuf,一串字节数组形成。ByteBuf 的个性:

池化 – 能够重用池中 ByteBuf 实例,更节约内存,缩小内存溢出的可能;
读写指针拆散,不须要像 ByteBuffer 一样切换读写模式;
能够主动扩容;
反对链式调用,应用更晦涩;
很多中央体现零拷贝,例如 slice、duplicate、CompositeByteBuf。

ByteBuf 提供了两个指针变量来反对程序读取和写入操作 —- 读取操作的 readerIndex 和写入操作的 writerIndex。
下图显示了如何通过两个指针将缓冲区分为三个区域:

| 可抛弃字节 | 可读字节 | 可写入字节 | (内容) |
0 <= readerIndex <= writerIndex <= 容量可读字节 (理论内容)通过调用 discardReadBytes()来抛弃读取的字节以回收未应用的区域。能够应用 mark 和 reset 重置 readIndex 索引和 writerIndex 索引,来反复读取目标。扩容规定:如果写入后数据大小未超过 512,则抉择下一个 16 的整数倍,例如写入后大小为 12,则扩容后 capacity 是 16;如果写入后数据大小超过 512,则抉择下一个 2^n- 1 之前的容量,扩容不能超过 max capacity 会报错。ByteBuf 依据内存类型分类:基于间接内存的 ByteBuf(默认)和基于堆内存的 ByteBuf。ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);// 基于堆内存 ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);// 基于堆外内存 ByteBuf 实现池化:没有池化,每次都创立新的 ByteBuf 实例,这个操作对间接内存代价低廉,就算是堆内存,也会减少 GC 压力。
通过重用 ByteBuf 实例,并且采纳了与 jemalloc 相似的内存调配算法晋升调配效率,高并发时,池化性能更节约内存,缩小内存溢出的可能。池化性能是否开启,能够通过上面的零碎环境变量来设置 -Dio.netty.allocator.type={unpooled|pooled} //4.1 当前对于非安卓平台默认池化,安卓平台非池化。

对于堆外内存的回收

Netty 这里采纳了援用计数法来管制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口,每个 ByteBuf 对象的初始计数为 1:

调用 release 办法计数减 1,如果计数为 0,ByteBuf 内存被回收;

调用 retain 办法计数加 1,示意调用者没用完之前,其它 handler 即便调用了 release 也不会造成回收;

当计数为 0 时,底层内存会被回收,这时即便 ByteBuf 对象还在,其各个办法均无奈失常应用。

本期分享就到这里,关注咱们定期更新更多精彩内容~ 作者:数新网络链接:https://juejin.cn/post/7236668847835037752 起源:稀土掘金著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。

正文完
 0