网站开发项目有哪些,广州市住房和建设水务局网站,wordpress 查询当月,深圳市年年卡网络科技有限公司为什么80%的码农都做不了架构师#xff1f; 1、总体结构 先放上一张漂亮的Netty总体结构图#xff0c;下面的内容也主要围绕该图上的一些核心功能做分析#xff0c;但对如Container Integration及Security Support等高级可选功能#xff0c;本文不予分析。… 为什么80%的码农都做不了架构师 1、总体结构 先放上一张漂亮的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新连接并分派请求到处理器链中。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过这种单线程模型不能充分利用多核资源所以实际使用的不多。 2、相比上一种模型该模型在处理器链部分采用了多线程线程池也是后端程序常用的模型。 3、 第三种模型比起第二种模型是将Reactor分成两部分mainReactor负责监听server socketaccept新连接并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket读写网 络数据对业务处理功能其扔给worker线程池完成。通常subReactor个数上可与CPU个数等同。 说完Reacotr模型的三种形式那么Netty是哪种呢其实我还有一种Reactor模型的变种没说那就是去掉线程池的第三种形式的变种这也 是Netty NIO的默认模式。在实现上Netty中的Boss类充当mainReactorNioWorker类充当subReactor默认 NioWorker的个数是Runtime.getRuntime().availableProcessors()。在处理新来的请求 时NioWorker读完已收到的数据到ChannelBuffer中之后触发ChannelPipeline中的ChannelHandler流。 Netty是事件驱动的可以通过ChannelHandler链来控制执行流向。因为ChannelHandler链的执行过程是在 subReactor中同步的所以如果业务处理handler耗时长将严重影响可支持的并发数。这种模型适合于像Memcache这样的应用场景但 对需要操作数据库或者和其他模块阻塞交互的系统就不是很合适。Netty的可扩展性非常好而像ChannelHandler线程池化的需要可以通过在 ChannelPipeline中添加Netty内置的ChannelHandler实现类–ExecutionHandler实现对使用者来说只是 添加一行代码而已。对于ExecutionHandler需要的线程池模型Netty提供了两种可 选1 MemoryAwareThreadPoolExecutor 可控制Executor中待处理任务的上限超过上限时后续进来的任务将被阻 塞并可控制单个Channel待处理任务的上限2 OrderedMemoryAwareThreadPoolExecutor 是 MemoryAwareThreadPoolExecutor 的子类它还可以保证同一Channel中处理的事件流的顺序性这主要是控制事件在异步处 理模式下可能出现的错误的事件顺序但它并不保证同一Channel中的事件都在一个线程中执行通常也没必要。一般来 说OrderedMemoryAwareThreadPoolExecutor 是个很不错的选择当然如果有需要也可以DIY一个。 3、 buffer org.jboss.netty.buffer包的接口及类的结构图如下 该包核心的接口是ChannelBuffer和ChannelBufferFactory,下面予以简要的介绍。 Netty使用ChannelBuffer来存储并操作读写的网络数据。ChannelBuffer除了提供和ByteBuffer类似的方法还提供了 一些实用方法具体可参考其API文档。ChannelBuffer的实现类有多个这里列举其中主要的几个 1HeapChannelBuffer这是Netty读网络数据时默认使用的ChannelBuffer这里的Heap就是Java堆的意思因为 读SocketChannel的数据是要经过ByteBuffer的而ByteBuffer实际操作的就是个byte数组所以 ChannelBuffer的内部就包含了一个byte数组使得ByteBuffer和ChannelBuffer之间的转换是零拷贝方式。根据网络字 节续的不同HeapChannelBuffer又分为BigEndianHeapChannelBuffer和 LittleEndianHeapChannelBuffer默认使用的是BigEndianHeapChannelBuffer。Netty在读网络 数据时使用的就是HeapChannelBufferHeapChannelBuffer是个大小固定的buffer为了不至于分配的Buffer的 大小不太合适Netty在分配Buffer时会参考上次请求需要的大小。 2DynamicChannelBuffer相比于HeapChannelBufferDynamicChannelBuffer可动态自适应大 小。对于在DecodeHandler中的写数据操作在数据大小未知的情况下通常使用DynamicChannelBuffer。 3ByteBufferBackedChannelBuffer这是directBuffer直接封装了ByteBuffer的 directBuffer。 对于读写网络数据的buffer分配策略有两种1通常出于简单考虑直接分配固定大小的buffer缺点是对一些应用来说这个大小限制有时是不 合理的并且如果buffer的上限很大也会有内存上的浪费。2针对固定大小的buffer缺点就引入动态buffer动态buffer之于固定 buffer相当于List之于Array。 buffer的寄存策略常见的也有两种其实是我知道的就限于此1在多线程线程池 模型下每个线程维护自己的读写buffer每次处理新的请求前清空buffer或者在处理结束后清空该请求的读写操作都需要在该线程中完成。 2buffer和socket绑定而与线程无关。两种方法的目的都是为了重用buffer。 Netty对buffer的处理策略是读 请求数据时Netty首先读数据到新创建的固定大小的HeapChannelBuffer中当HeapChannelBuffer满或者没有数据可读 时调用handler来处理数据这通常首先触发的是用户自定义的DecodeHandler因为handler对象是和ChannelSocket 绑定的所以在DecodeHandler里可以设置ChannelBuffer成员当解析数据包发现数据不完整时就终止此次处理流程等下次读事件触 发时接着上次的数据继续解析。就这个过程来说和ChannelSocket绑定的DecodeHandler中的Buffer通常是动态的可重用 BufferDynamicChannelBuffer而在NioWorker中读ChannelSocket中的数据的buffer是临时分配的 固定大小的HeapChannelBuffer这个转换过程是有个字节拷贝行为的。 对ChannelBuffer的创建Netty内部使用的是ChannelBufferFactory接口具体的实现有 DirectChannelBufferFactory和HeapChannelBufferFactory。对于开发者创建 ChannelBuffer可使用实用类ChannelBuffers中的工厂方法。 4、Channel 和Channel相关的接口及类结构图如下 从该结构图也可以看到Channel主要提供的功能如下 1当前Channel的状态信息比如是打开还是关闭等。 2通过ChannelConfig可以得到的Channel配置信息。 3Channel所支持的如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相关的接口及类图 对于使用者来说在ChannelHandler实现类中会使用继承于ChannelEvent的MessageEvent调用其 getMessage()方法来获得读到的ChannelBuffer或被转化的对象。 6、ChannelPipeline Netty 在事件处理上是通过ChannelPipeline来控制事件流通过调用注册其上的一系列ChannelHandler来处理事件这也是典型的拦截 器模式。下面是和ChannelPipeline相关的接口及类图 事件流有两种upstream事件和downstream事件。在ChannelPipeline中其可被注册的ChannelHandler既可以 是 ChannelUpstreamHandler 也可以是ChannelDownstreamHandler 但事件在ChannelPipeline传递过程中只会调用匹配流的ChannelHandler。在事件流的过滤器链 中ChannelUpstreamHandler或ChannelDownstreamHandler既可以终止流程也可以通过调用 ChannelHandlerContext.sendUpstream(ChannelEvent)或 ChannelHandlerContext.sendDownstream(ChannelEvent)将事件传递下去。下面是事件流处理的图示 从上图可见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这里给出简单的介绍。 1FrameDecoderFrameDecoder内部维护了一个 DynamicChannelBuffer成员来存储接收到的数据它就像个抽象模板把整个解码过程模板写好了其子类只需实现decode函数即可。 FrameDecoder的直接实现类有两个1DelimiterBasedFrameDecoder是基于分割符 比如\r\n的解码器可在构造函数中指定分割符。2LengthFieldBasedFrameDecoder是基于长度字段的解码器。如果协 议 格式类似“内容长度”内容、“固定头”“内容长度”动态内容这样的格式就可以使用该解码器其使用方法在API DOC上详尽的解释。 2ReplayingDecoder 它是FrameDecoder的一个变种子类它相对于FrameDecoder是非阻塞解码。也就是说使用 FrameDecoder时需要考虑到读到的数据有可能是不完整的而使用ReplayingDecoder就可以假定读到了全部的数据。 3ObjectEncoder 和ObjectDecoder编码解码序列化的Java对象。 4HttpRequestEncoder和 HttpRequestDecoderhttp协议处理。 下面来看使用FrameDecoder和ReplayingDecoder的两个例子 public class IntegerHeaderFrameDecoder extends FrameDecoder { protected Object decode(ChannelHandlerContext ctx, Channel channel,ChannelBuffer buf) throws Exception { if (buf.readableBytes() lt; 4) { return null; } buf.markReaderIndex(); int length buf.readInt(); if (buf.readableBytes() lt; 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开源项目都会用上一系列优秀的设计模式也完全可以从模式这一点单独拿出一篇分析文 章来尽管我目前没有这样的想法。而在此文完成之后我也没什么兴趣再看Netty的代码了。 转载于:https://my.oschina.net/shajin/blog/156580