欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Netty浅析

程序员文章站 2023-11-10 21:32:58
Netty是JBoss出品的高效的Java NIO开发框架,关于其使用,可参考我的另一篇文章 netty使用初步。本文将主要分析Netty实现方面的东西,由于精力有限,本人并没有对其源码做了极细致的研 究。如果下面的内容有错误或不严谨的地方,也请指正和谅解。对于Netty使用者来说,Netty提供了 ......

netty是jboss出品的高效的java nio开发框架,关于其使用,可参考我的另一篇文章 netty使用初步。本文将主要分析netty实现方面的东西,由于精力有限,本人并没有对其源码做了极细致的研 究。如果下面的内容有错误或不严谨的地方,也请指正和谅解。对于netty使用者来说,netty提供了几个典型的example,并有详尽的api doc和guide doc,本文的一些内容及图示也来自于netty的文档,特此致谢。

1、总体结构

 
Netty浅析
netty实现原理浅析

先放上一张漂亮的netty总体结构图,下面的内容也主要围绕该图上的一些核心功能做分析,但对如container integration及security support等高级可选功能,本文不予分析。

2、网络模型

netty是典型的reactor模型结构,关于reactor的详尽阐释,可参考posa2,这里不做概念性的解释。而应用java nio构建reactor模式,doug lea(就是那位让人无限景仰的大爷)在“scalable io in java”中给了很好的阐述。这里截取其ppt中经典的图例说明 reactor模式的典型实现:

1、这是最简单的单reactor单线程模型。reactor线程是个多面手,负责多路分离套接字,accept新连接,并分派请求到处理器链中。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际使用的不多。

 
Netty浅析
netty实现原理浅析

2、相比上一种模型,该模型在处理器链部分采用了多线程(线程池),也是后端程序常用的模型。

 
Netty浅析
netty实现原理浅析

3、 第三种模型比起第二种模型,是将reactor分成两部分,mainreactor负责监听server socket,accept新连接,并将建立的socket分派给subreactor。subreactor负责多路分离已连接的socket,读写网 络数据,对业务处理功能,其扔给worker线程池完成。通常,subreactor个数上可与cpu个数等同。

 
Netty浅析
netty实现原理浅析

说完reacotr模型的三种形式,那么netty是哪种呢?其实,我还有一种reactor模型的变种没说,那就是去掉线程池的第三种形式的变种,这也 是netty nio的默认模式。在实现上,netty中的boss类充当mainreactor,nioworker类充当subreactor(默认 nioworker的个数是runtime.getruntime().availableprocessors())。在处理新来的请求 时,nioworker读完已收到的数据到channelbuffer中,之后触发channelpipeline中的channelhandler流。

netty是事件驱动的,可以通过channelhandler链来控制执行流向。因为channelhandler链的执行过程是在 subreactor中同步的,所以如果业务处理handler耗时长,将严重影响可支持的并发数。这种模型适合于像memcache这样的应用场景,但 对需要操作数据库或者和其他模块阻塞交互的系统就不是很合适。netty的可扩展性非常好,而像channelhandler线程池化的需要,可以通过在 channelpipeline中添加netty内置的channelhandler实现类–executionhandler实现,对使用者来说只是 添加一行代码而已。对于executionhandler需要的线程池模型,netty提供了两种可 选:1) memoryawarethreadpool[executor]可控制executor中待处理任务的上限(超过上限时,后续进来的任务将被阻 塞),并可控制单个channel待处理任务的上限;2) orderedmemoryawarethreadpoolexecutor 是 memoryawarethreadpoolexecutor 的子类,它还可以保证同一channel中处理的事件流的顺序性,这主要是控制事件在异步处 理模式下可能出现的错误的事件顺序,但它并不保证同一channel中的事件都在一个线程中执行(通常也没必要)。一般来 说,orderedmemoryawarethreadpoolexecutor 是个很不错的选择,当然,如果有需要,也可以diy一个。

3、 buffer

org.jboss.netty.buffer包的接口及类的结构图如下:

 
Netty浅析
netty实现原理浅析

该包核心的接口是channelbuffer和channelbufferfactory,下面予以简要的介绍。

netty使用channelbuffer来存储并操作读写的网络数据。channelbuffer除了提供和bytebuffer类似的方法,还提供了 一些实用方法,具体可参考其api文档。channelbuffer的实现类有多个,这里列举其中主要的几个:

1)heapchannelbuffer:这是netty读网络数据时默认使用的channelbuffer,这里的heap就是java堆的意思,因为 读socketchannel的数据是要经过bytebuffer的,而bytebuffer实际操作的就是个byte数组,所以 channelbuffer的内部就包含了一个byte数组,使得bytebuffer和channelbuffer之间的转换是零拷贝方式。根据网络字 节续的不同,heapchannelbuffer又分为bigendianheapchannelbuffer和 littleendianheapchannelbuffer,默认使用的是bigendianheapchannelbuffer。netty在读网络 数据时使用的就是heapchannelbuffer,heapchannelbuffer是个大小固定的buffer,为了不至于分配的buffer的 大小不太合适,netty在分配buffer时会参考上次请求需要的大小。

2)dynamicchannelbuffer:相比于heapchannelbuffer,dynamicchannelbuffer可动态自适应大 小。对于在decodehandler中的写数据操作,在数据大小未知的情况下,通常使用dynamicchannelbuffer。

3)bytebufferbackedchannelbuffer:这是directbuffer,直接封装了bytebuffer的 directbuffer。

对于读写网络数据的buffer,分配策略有两种:1)通常出于简单考虑,直接分配固定大小的buffer,缺点是,对一些应用来说这个大小限制有时是不 合理的,并且如果buffer的上限很大也会有内存上的浪费。2)针对固定大小的buffer缺点,就引入动态buffer,动态buffer之于固定 buffer相当于list之于array。

buffer的寄存策略常见的也有两种(其实是我知道的就限于此):1)在多线程(线程池) 模型下,每个线程维护自己的读写buffer,每次处理新的请求前清空buffer(或者在处理结束后清空),该请求的读写操作都需要在该线程中完成。 2)buffer和socket绑定而与线程无关。两种方法的目的都是为了重用buffer。

netty对buffer的处理策略是:读 请求数据时,netty首先读数据到新创建的固定大小的heapchannelbuffer中,当heapchannelbuffer满或者没有数据可读 时,调用handler来处理数据,这通常首先触发的是用户自定义的decodehandler,因为handler对象是和channelsocket 绑定的,所以在decodehandler里可以设置channelbuffer成员,当解析数据包发现数据不完整时就终止此次处理流程,等下次读事件触 发时接着上次的数据继续解析。就这个过程来说,和channelsocket绑定的decodehandler中的buffer通常是动态的可重用 buffer(dynamicchannelbuffer),而在nioworker中读channelsocket中的数据的buffer是临时分配的 固定大小的heapchannelbuffer,这个转换过程是有个字节拷贝行为的。

对channelbuffer的创建,netty内部使用的是channelbufferfactory接口,具体的实现有 directchannelbufferfactory和heapchannelbufferfactory。对于开发者创建 channelbuffer,可使用实用类channelbuffers中的工厂方法。

4、channel

和channel相关的接口及类结构图如下:

 
Netty浅析
netty实现原理浅析

从该结构图也可以看到,channel主要提供的功能如下:

1)当前channel的状态信息,比如是打开还是关闭等。

2)通过channelconfig可以得到的channel配置信息。

3)channel所支持的如read、write、bind、connect等io操作。

4)得到处理该channel的channelpipeline,既而可以调用其做和请求相关的io操作。

在channel实现方面,以通常使用的nio socket来说,netty中的nioserversocketchannel和niosocketchannel分别封装了java.nio中包含的 serversocketchannel和socketchannel的功能。

5、channelevent

如前所述,netty是事件驱动的,其通过channelevent来确定事件流的方向。一个channelevent是依附于channel的 channelpipeline来处理,并由channelpipeline调用channelhandler来做具体的处理。下面是和 channelevent相关的接口及类图:

 
Netty浅析
netty实现原理浅析

对于使用者来说,在channelhandler实现类中会使用继承于channelevent的messageevent,调用其 getmessage()方法来获得读到的channelbuffer或被转化的对象。

6、channelpipeline

netty 在事件处理上,是通过channelpipeline来控制事件流,通过调用注册其上的一系列channelhandler来处理事件,这也是典型的拦截 器模式。下面是和channelpipeline相关的接口及类图:

 
Netty浅析
netty实现原理浅析

事件流有两种,upstream事件和downstream事件。在channelpipeline中,其可被注册的channelhandler既可以 是 channelupstreamhandler 也可以是channeldownstreamhandler ,但事件在channelpipeline传递过程中只会调用匹配流的channelhandler。在事件流的过滤器链 中,channelupstreamhandler或channeldownstreamhandler既可以终止流程,也可以通过调用 channelhandlercontext.sendupstream(channelevent)或 channelhandlercontext.senddownstream(channelevent)将事件传递下去。下面是事件流处理的图示:

 
Netty浅析
netty实现原理浅析

从上图可见,upstream event是被upstream handler们自底向上逐个处理,downstream event是被downstream handler们自顶向下逐个处理,这里的上下关系就是向channelpipeline里添加handler的先后顺序关系。简单的理 解,upstream event是处理来自外部的请求的过程,而downstream event是处理向外发送请求的过程。

服务端处 理请求的过程通常就是解码请求、业务逻辑处理、编码响应,构建的channelpipeline也就类似下面的代码片断:

channelpipeline pipeline = channels.pipeline();
pipeline.addlast("decoder", new myprotocoldecoder());
pipeline.addlast("encoder", new myprotocolencoder());
pipeline.addlast("handler", new mybusinesslogichandler());

 

其中,myprotocoldecoder是channelupstreamhandler类型,myprotocolencoder是 channeldownstreamhandler类型,mybusinesslogichandler既可以是 channelupstreamhandler类型,也可兼channeldownstreamhandler类型,视其是服务端程序还是客户端程序以及 应用需要而定。

补充一点,netty对抽象和实现做了很好的解耦。像org.jboss.netty.channel.socket包, 定义了一些和socket处理相关的接口,而org.jboss.netty.channel.socket.nio、 org.jboss.netty.channel.socket.oio等包,则是和协议相关的实现。

7、codec framework

对于请求协议的编码解码,当然是可以按照协议格式自己操作channelbuffer中的字节数据。另一方面,netty也做了几个很实用的codec helper,这里给出简单的介绍。

1)framedecoder:framedecoder内部维护了一个 dynamicchannelbuffer成员来存储接收到的数据,它就像个抽象模板,把整个解码过程模板写好了,其子类只需实现decode函数即可。 framedecoder的直接实现类有两个:(1)delimiterbasedframedecoder是基于分割符 (比如/r/n)的解码器,可在构造函数中指定分割符。(2)lengthfieldbasedframedecoder是基于长度字段的解码器。如果协 议 格式类似“内容长度”+内容、“固定头”+“内容长度”+动态内容这样的格式,就可以使用该解码器,其使用方法在api doc上详尽的解释。

2)replayingdecoder: 它是framedecoder的一个变种子类,它相对于framedecoder是非阻塞解码。也就是说,使用 framedecoder时需要考虑到读到的数据有可能是不完整的,而使用replayingdecoder就可以假定读到了全部的数据。

3)objectencoder 和objectdecoder:编码解码序列化的java对象。

4)httprequestencoder和 httprequestdecoder:http协议处理。

下面来看使用framedecoder和replayingdecoder的两个例子:

public class integerheaderframedecoder extends framedecoder {
    protected object decode(channelhandlercontext ctx, channel channel,
                channelbuffer buf) throws exception {
        if (buf.readablebytes() < 4) {
            return null;
        }
        buf.markreaderindex();
        int length = buf.readint();
        if (buf.readablebytes() < length) {
            buf.resetreaderindex();
            return null;
        }
        return buf.readbytes(length);
    }
}

 

而使用replayingdecoder的解码片断类似下面的,相对来说会简化很多。

public class integerheaderframedecoder2 extends replayingdecoder {
    protected object decode(channelhandlercontext ctx, channel channel,
            channelbuffer buf, voidenum state) throws exception {
        return buf.readbytes(buf.readint());
    }
}

 

就实现来说,当在replayingdecoder子类的decode函数中调用channelbuffer读数据时,如果读失败,那么 replayingdecoder就会catch住其抛出的error,然后replayingdecoder接手控制权,等待下一次读到后续的数据后继 续decode。

8、小结

尽管该文行至此处将止,但该文显然没有将netty实现原理深入浅出的说全说透。当我打算写这篇文章时,也是一边看netty的代码,一边总结些可写的东 西,但前后断断续续,到最后都没了多少兴致。我还是爱做一些源码分析的事情,但精力终究有限,并且倘不能把源码分析的结果有条理的托出来,不能产生有意义 的心得,这分析也没什么价值和趣味。而就分析netty代码的感受来说,netty的代码很漂亮,结构上层次上很清晰,不过这种面向接口及抽象层次对代码 跟踪很是个问题,因为跟踪代码经常遇到接口和抽象类,只能借助于工厂类和api doc,反复对照接口和实现类的对应关系。

对java架构技术感兴趣的同学,欢迎加qq群619881427,一起学习,相互讨论。

群内已经有小伙伴将知识体系整理好(源码,笔记,ppt,学习视频),欢迎加群免费领取。

分享给喜欢的java的,喜欢编程,有梦想成为架构师的程序员们,希望能够帮助到你们。

不是的java的程序员也没关系,帮忙转发给更多朋友!谢谢。

分享一个小技巧点击阅读原文也。。可以轻松获取学习资料哦!

关注微信公众号:java架构师学习,观看更多的java技术干货分享

Netty浅析