做网站的工资高吗,防城港门面做网站的,海口seo网站推广,wordpress自定义关键词链接文章转载自  一文理解Netty模型架构 
本文基于Netty4.1展开介绍相关理论模型#xff0c;使用场景#xff0c;基本组件、整体架构#xff0c;知其然且知其所以然#xff0c;希望给读者提供学习实践参考。 
1 Netty简介 Netty是 一个异步事件驱动的网络应用程序框架#xff0c;用…转载自  一文理解Netty模型架构 
本文基于Netty4.1展开介绍相关理论模型使用场景基本组件、整体架构知其然且知其所以然希望给读者提供学习实践参考。 
1 Netty简介 Netty是 一个异步事件驱动的网络应用程序框架用于快速开发可维护的高性能协议服务器和客户端。 JDK原生NIO程序的问题 
JDK原生也有一套网络应用程序API但是存在一系列问题主要如下 
NIO的类库和API繁杂使用麻烦你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等需要具备其它的额外技能做铺垫例如熟悉Java多线程编程因为NIO编程涉及到Reactor模式你必须对多线程和网路编程非常熟悉才能编写出高质量的NIO程序可靠性能力补齐开发工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等NIO编程的特点是功能开发相对容易但是可靠性能力补齐工作量和难度都非常大JDK NIO的BUG例如臭名昭著的epoll bug它会导致Selector空轮询最终导致CPU 100%。官方声称在JDK1.6版本的update18修复了该问题但是直到JDK1.7版本该问题仍旧存在只不过该bug发生概率降低了一些而已它并没有被根本解决
Netty的特点 
Netty的对JDK自带的NIO的API进行封装解决上述问题主要特点有 
设计优雅 适用于各种传输类型的统一API - 阻塞和非阻塞Socket 基于灵活且可扩展的事件模型可以清晰地分离关注点 高度可定制的线程模型 - 单线程一个或多个线程池 真正的无连接数据报套接字支持自3.1起使用方便 详细记录的Javadoc用户指南和示例 没有其他依赖项JDK 5Netty 3.x或6Netty 4.x就足够了高性能 吞吐量更高延迟更低 减少资源消耗 最小化不必要的内存复制安全 完整的SSL / TLS和StartTLS支持社区活跃不断更新 社区活跃版本迭代周期短发现的BUG可以被及时修复同时更多的新功能会被加入
Netty常见使用常见 
Netty常见的使用场景如下 
互联网行业 在分布式系统中各个节点之间需要远程服务调用高性能的RPC框架必不可少Netty作为异步高新能的通信框架,往往作为基础通信组件被这些RPC框架使用。 典型的应用有阿里分布式服务框架Dubbo的RPC框架使用Dubbo协议进行节点间通信Dubbo协议默认使用Netty作为基础通信组件用于实现各进程节点之间的内部通信。游戏行业 无论是手游服务端还是大型的网络游戏Java语言得到了越来越广泛的应用。Netty作为高性能的基础通信组件它本身提供了TCP/UDP和HTTP协议栈。 非常方便定制和开发私有协议栈账号登录服务器地图服务器之间可以方便的通过Netty进行高性能的通信大数据领域 经典的Hadoop的高性能通信和序列化组件Avro的RPC框架默认采用Netty进行跨界点通信它的Netty Service基于Netty框架二次封装实现
有兴趣的读者可以了解一下目前有哪些开源项目使用了 NettyRelated projects 
2 Netty高性能设计 
Netty作为异步事件驱动的网络高性能之处主要来自于其I/O模型和线程处理模型前者决定如何收发数据后者决定如何处理数据 
I/O模型 
用什么样的通道将数据发送给对方BIO、NIO或者AIOI/O模型在很大程度上决定了框架的性能 
阻塞I/O 
传统阻塞型I/O(BIO)可以用下图表示 特点 
每个请求都需要独立的线程完成数据read业务处理数据write的完整操作
问题 
当并发数较大时需要创建大量线程来处理连接系统资源占用较大连接建立后如果当前线程暂时没有数据可读则线程就阻塞在read操作上造成线程资源浪费
I/O复用模型 在I/O复用模型中会用到select这个函数也会使进程阻塞但是和阻塞I/O所不同的的这两个函数可以同时阻塞多个I/O操作而且可以同时对多个读操作多个写操作的I/O函数进行检测直到有数据可读或可写时才真正调用I/O操作函数 Netty的非阻塞I/O的实现关键是基于I/O复用模型这里用Selector对象表示 Netty的IO线程NioEventLoop由于聚合了多路复用器Selector可以同时并发处理成百上千个客户端连接。当线程从某客户端Socket通道进行读写数据时若没有数据可用时该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作所以单独的线程可以管理多个输入和输出通道。 
由于读写操作都是非阻塞的这就可以充分提升IO线程的运行效率避免由于频繁I/O阻塞导致的线程挂起一个I/O线程可以并发处理N个客户端连接和读写操作这从根本上解决了传统同步阻塞I/O一连接一线程模型架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。 
基于buffer 
传统的I/O是面向字节流或字符流的以流式的方式顺序地从一个Stream 中读取一个或多个字节, 因此也就不能随意改变读取指针的位置。 
在NIO中, 抛弃了传统的 I/O流, 而是引入了Channel和Buffer的概念. 在NIO中, 只能从Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel。 
基于buffer操作不像传统IO的顺序操作, NIO 中可以随意地读取任意位置的数据 
线程模型 
数据报如何读取读取之后的编解码在哪个线程进行编解码后的消息如何派发线程模型的不同对性能的影响也非常大。 
事件驱动模型 
通常我们设计一个事件处理模型的程序有两种思路 
轮询方式 线程不断轮询访问相关事件发生源有没有发生事件有发生事件就调用事件处理逻辑。事件驱动方式 发生事件主线程把事件放入事件队列在另外线程不断循环消费事件列表中的事件调用事件对应的处理逻辑处理事件。事件驱动方式也被称为消息通知方式其实是发布-订阅模式的思路。
以GUI的逻辑处理为例说明两种逻辑的不同 
轮询方式 线程不断轮询是否发生按钮点击事件如果发生调用处理逻辑事件驱动方式 发生点击事件把事件放入事件队列在另外线程消费的事件列表中的事件根据事件类型调用相关事件处理逻辑
这里借用OReilly 大神关于事件驱动模型解释图 主要包括4个基本组件 
事件队列event queue接收事件的入口存储待处理事件分发器event mediator将不同的事件分发到不同的业务逻辑单元事件通道event channel分发器与处理器之间的联系渠道事件处理器event processor实现业务逻辑处理完成后会发出事件触发下一步操作
可以看出相对传统轮询模式事件驱动有如下优点 
可扩展性好分布式的异步架构事件处理器之间高度解耦可以方便扩展事件处理逻辑高性能基于队列暂存事件能方便并行异步处理事件
Reactor线程模型 
Reactor是反应堆的意思Reactor模型是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。 服务端程序处理传入多路请求并将它们同步分派给请求对应的处理线程Reactor模式也叫Dispatcher模式即I/O多了复用统一监听事件收到事件后分发(Dispatch给某进程)是编写高性能网络服务器的必备技术之一。 
Reactor模型中有2个关键组成 Reactor Reactor在一个单独的线程中运行负责监听和分发事件分发给适当的处理程序来对IO事件做出反应。 它就像公司的电话接线员它接听来自客户的电话并将线路转移到适当的联系人  Handlers 处理程序执行I/O事件要完成的实际事件类似于客户想要与之交谈的公司中的实际官员。Reactor通过调度适当的处理程序来响应I/O事件处理程序执行非阻塞操作 取决于Reactor的数量和Handler线程数量的不同Reactor模型有3个变种 
单Reactor单线程单Reactor多线程主从Reactor多线程
可以这样理解Reactor就是一个执行while (true) { selector.select(); ...}循环的线程会源源不断的产生新的事件称作反应堆很贴切。 
篇幅关系这里不再具体展开Reactor特性、优缺点比较有兴趣的读者可以参考我之前另外一篇文章《理解高性能网络模型》 
Netty线程模型 
Netty主要基于主从Reactors多线程模型如下图做了一定的修改其中主从Reactor多线程模型有多个ReactorMainReactor和SubReactor 
MainReactor负责客户端的连接请求并将请求转交给SubReactorSubReactor负责相应通道的IO读写请求非IO请求具体逻辑处理的任务则会直接写入队列等待worker threads进行处理
这里引用Doug Lee大神的Reactor介绍Scalable IO in Java里面关于主从Reactor多线程模型的图 特别说明的是 虽然Netty的线程模型基于主从Reactor多线程借用了MainReactor和SubReactor的结构但是实际实现上SubReactor和Worker线程在同一个线程池中 
EventLoopGroup bossGroup  new NioEventLoopGroup();
EventLoopGroup workerGroup  new NioEventLoopGroup();
ServerBootstrap server  new ServerBootstrap();
server.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) 
上面代码中的bossGroup 和workerGroup是Bootstrap构造方法中传入的两个对象这两个group均是线程池 
bossGroup线程池则只是在bind某个端口后获得其中一个线程作为MainReactor专门处理端口的accept事件每个端口对应一个boss线程workerGroup线程池会被各个SubReactor和worker线程充分利用
异步处理 
异步的概念和同步相对。当一个异步过程调用发出后调用者不能立刻得到结果。实际处理这个调用的部件在完成后通过状态、通知和回调来通知调用者。 
Netty中的I/O操作是异步的包括bind、write、connect等操作会简单的返回一个ChannelFuture调用者并不能立刻获得结果通过Future-Listener机制用户可以方便的主动获取或者通过通知机制获得IO操作结果。 
当future对象刚刚创建时处于非完成状态调用者可以通过返回的ChannelFuture来获取操作执行的状态注册监听函数来执行完成后的操常见有如下操作 
通过isDone方法来判断当前操作是否完成通过isSuccess方法来判断已完成的当前操作是否成功通过getCause方法来获取已完成的当前操作失败的原因通过isCancelled方法来判断已完成的当前操作是否被取消通过addListener方法来注册监听器当操作已完成(isDone方法返回完成)将会通知指定的监听器如果future对象已完成则理解通知指定的监听器
例如下面的的代码中绑定端口是异步操作当绑定操作处理完将会调用相应的监听器处理逻辑 serverBootstrap.bind(port).addListener(future - {if (future.isSuccess()) {System.out.println(new Date()  : 端口[  port  ]绑定成功!);} else {System.err.println(端口[  port  ]绑定失败!);}}); 
相比传统阻塞I/O执行I/O操作后线程会被阻塞住, 直到操作完成异步处理的好处是不会造成线程阻塞线程在I/O操作期间可以执行别的程序在高并发情形下会更稳定和更高的吞吐量。 
3 Netty架构设计 
前面介绍完Netty相关一些理论介绍下面从功能特性、模块组件、运作过程来介绍Netty的架构设计 
功能特性 传输服务 支持BIO和NIO容器集成 支持OSGI、JBossMC、Spring、Guice容器协议支持 HTTP、Protobuf、二进制、文本、WebSocket等一系列常见协议都支持。 还支持通过实行编码解码逻辑来实现自定义协议Core核心 可扩展事件模型、通用通信API、支持零拷贝的ByteBuf缓冲对象
模块组件 
Bootstrap、ServerBootstrap 
Bootstrap意思是引导一个Netty应用通常由一个Bootstrap开始主要作用是配置整个Netty程序串联各个组件Netty中Bootstrap类是客户端程序的启动引导类ServerBootstrap是服务端启动引导类。 
Future、ChannelFuture 
正如前面介绍在Netty中所有的IO操作都是异步的不能立刻得知消息是否被正确处理但是可以过一会等它执行完成或者直接注册一个监听具体的实现就是通过Future和ChannelFutures他们可以注册一个监听当操作执行成功或失败时监听会自动触发注册的监听事件。 
Channel 
Netty网络通信的组件能够用于执行网络I/O操作。 Channel为用户提供 
当前网络连接的通道的状态例如是否打开是否已连接网络连接的配置参数 例如接收缓冲区大小提供异步的网络I/O操作(如建立连接读写绑定端口)异步调用意味着任何I / O调用都将立即返回并且不保证在调用结束时所请求的I / O操作已完成。调用立即返回一个ChannelFuture实例通过注册监听器到ChannelFuture上可以I / O操作成功、失败或取消时回调通知调用方。支持关联I/O操作与对应的处理程序
不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应下面是一些常用的 Channel 类型 
NioSocketChannel异步的客户端 TCP Socket 连接NioServerSocketChannel异步的服务器端 TCP Socket 连接NioDatagramChannel异步的 UDP 连接NioSctpChannel异步的客户端 Sctp 连接NioSctpServerChannel异步的 Sctp 服务器端连接 这些通道涵盖了 UDP 和 TCP网络 IO以及文件 IO.
Selector 
Netty基于Selector对象实现I/O多路复用通过 Selector, 一个线程可以监听多个连接的Channel事件, 当向一个Selector中注册Channel 后Selector 内部的机制就可以自动不断地查询(select) 这些注册的Channel是否有已就绪的I/O事件(例如可读, 可写, 网络连接完成等)这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。 
NioEventLoop 
NioEventLoop中维护了一个线程和任务队列支持异步提交执行任务线程启动时会调用NioEventLoop的run方法执行I/O任务和非I/O任务 
I/O任务 即selectionKey中ready的事件如accept、connect、read、write等由processSelectedKeys方法触发。非IO任务 添加到taskQueue中的任务如register0、bind0等任务由runAllTasks方法触发。
两种任务的执行时间比由变量ioRatio控制默认为50则表示允许非IO任务执行的时间与IO任务的执行时间相等。 
NioEventLoopGroup 
NioEventLoopGroup主要管理eventLoop的生命周期可以理解为一个线程池内部维护了一组线程每个线程(NioEventLoop)负责处理多个Channel上的事件而一个Channel只对应于一个线程。 
ChannelHandler 
ChannelHandler是一个接口处理I / O事件或拦截I / O操作并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序。 
ChannelHandler本身并没有提供很多方法因为这个接口有许多的方法需要实现方便使用期间可以继承它的子类 
ChannelInboundHandler用于处理入站I / O事件ChannelOutboundHandler用于处理出站I / O操作
或者使用以下适配器类 
ChannelInboundHandlerAdapter用于处理入站I / O事件ChannelOutboundHandlerAdapter用于处理出站I / O操作ChannelDuplexHandler用于处理入站和出站事件
ChannelHandlerContext 
保存Channel相关的所有上下文信息同时关联一个ChannelHandler对象 
ChannelPipline 
保存ChannelHandler的List用于处理或拦截Channel的入站事件和出站操作。 ChannelPipeline实现了一种高级形式的拦截过滤器模式使用户可以完全控制事件的处理方式以及Channel中各个的ChannelHandler如何相互交互。 
下图引用Netty的Javadoc4.1中ChannelPipline的说明描述了ChannelPipeline中ChannelHandler通常如何处理I/O事件。 I/O事件由ChannelInboundHandler或ChannelOutboundHandler处理并通过调用ChannelHandlerContext中定义的事件传播方法例如ChannelHandlerContext.fireChannelReadObject和ChannelOutboundInvoker.writeObject转发到其最近的处理程序。 I/O Requestvia Channel orChannelHandlerContext|------------------------------------------------------------------|                           ChannelPipeline         |               ||                                                  \|/              ||    ---------------------            ---------------------    ||    | Inbound Handler  N  |            | Outbound Handler  1  |    ||    --------------------            ---------------------    ||              /|\                                  |               ||               |                                  \|/              ||    --------------------            ---------------------    ||    | Inbound Handler N-1 |            | Outbound Handler  2  |    ||    --------------------            ---------------------    ||              /|\                                  .               ||               .                                   .               || ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()||        [ method call]                       [method call]         ||               .                                   .               ||               .                                  \|/              ||    --------------------            ---------------------    ||    | Inbound Handler  2  |            | Outbound Handler M-1 |    ||    --------------------            ---------------------    ||              /|\                                  |               ||               |                                  \|/              ||    --------------------            ---------------------    ||    | Inbound Handler  1  |            | Outbound Handler  M  |    ||    --------------------            ---------------------    ||              /|\                                  |               |-----------------------------------------------------------------|                                  \|/-----------------------------------------------------------------|               |                                   |               ||       [ Socket.read() ]                    [ Socket.write() ]     ||                                                                   ||  Netty Internal I/O Threads (Transport Implementation)            |------------------------------------------------------------------- 
入站事件由自下而上方向的入站处理程序处理如图左侧所示。 入站Handler处理程序通常处理由图底部的I / O线程生成的入站数据。 通常通过实际输入操作例如SocketChannel.readByteBuffer从远程读取入站数据。 
出站事件由上下方向处理如图右侧所示。 出站Handler处理程序通常会生成或转换出站传输例如write请求。 I/O线程通常执行实际的输出操作例如SocketChannel.writeByteBuffer。 
在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应, 它们的组成关系如下: 一个 Channel 包含了一个 ChannelPipeline, 而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表, 并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。入站事件和出站事件在一个双向链表中入站事件会从链表head往后传递到最后一个入站的handler出站事件会从链表tail往前传递到最前一个出站的handler两种类型的handler互不干扰。 
工作原理架构 
初始化并启动Netty服务端过程如下 public static void main(String[] args) {// 创建mainReactorNioEventLoopGroup boosGroup  new NioEventLoopGroup();// 创建工作线程组NioEventLoopGroup workerGroup  new NioEventLoopGroup();final ServerBootstrap serverBootstrap  new ServerBootstrap();serverBootstrap // 组装NioEventLoopGroup .group(boosGroup, workerGroup)// 设置channel类型为NIO类型.channel(NioServerSocketChannel.class)// 设置连接配置参数.option(ChannelOption.SO_BACKLOG, 1024).childOption(ChannelOption.SO_KEEPALIVE, true).childOption(ChannelOption.TCP_NODELAY, true)// 配置入站、出站事件handler.childHandler(new ChannelInitializerNioSocketChannel() {Overrideprotected void initChannel(NioSocketChannel ch) {// 配置入站、出站事件channelch.pipeline().addLast(...);ch.pipeline().addLast(...);}});// 绑定端口int port  8080;serverBootstrap.bind(port).addListener(future - {if (future.isSuccess()) {System.out.println(new Date()  : 端口[  port  ]绑定成功!);} else {System.err.println(端口[  port  ]绑定失败!);}});
} 
基本过程如下1 初始化创建2个NioEventLoopGroup其中boosGroup用于Accetpt连接建立事件并分发请求 workerGroup用于处理I/O读写事件和业务逻辑2 基于ServerBootstrap(服务端启动引导类)配置EventLoopGroup、Channel类型连接参数、配置入站、出站事件handler3 绑定端口开始工作
结合上面的介绍的Netty Reactor模型介绍服务端Netty的工作架构图 server端包含1个Boss NioEventLoopGroup和1个Worker NioEventLoopGroupNioEventLoopGroup相当于1个事件循环组这个组里包含多个事件循环NioEventLoop每个NioEventLoop包含1个selector和1个事件循环线程。 
每个Boss NioEventLoop循环执行的任务包含3步 
1 轮询accept事件2 处理accept I/O事件与Client建立连接生成NioSocketChannel并将NioSocketChannel注册到某个Worker NioEventLoop的Selector上 *3 处理任务队列中的任务runAllTasks。任务队列中的任务包括用户调用eventloop.execute或schedule执行的任务或者其它线程提交到该eventloop的任务。
每个Worker NioEventLoop循环执行的任务包含3步 
1 轮询read、write事件2 处I/O事件即read、write事件在NioSocketChannel可读、可写事件发生时进行处理3 处理任务队列中的任务runAllTasks。
其中任务队列中的task有3种典型使用场景 
1 用户程序自定义的普通任务
ctx.channel().eventLoop().execute(new Runnable() {Overridepublic void run() {//...}
}); 2 非当前reactor线程调用channel的各种方法 例如在推送系统的业务线程里面根据用户的标识找到对应的channel引用然后调用write类方法向该用户推送消息就会进入到这种场景。最终的write会提交到任务队列中后被异步消费。  3 用户自定义定时任务 
ctx.channel().eventLoop().schedule(new Runnable() {Overridepublic void run() {}
}, 60, TimeUnit.SECONDS); 
4 总结 
现在稳定推荐使用的主流版本还是Netty4Netty5 中使用了 ForkJoinPool增加了代码的复杂度但是对性能的改善却不明显所以这个版本不推荐使用官网也没有提供下载链接。 
Netty 入门门槛相对较高其实是因为这方面的资料较少并不是因为他有多难大家其实都可以像搞透 Spring 一样搞透 Netty。在学习之前建议先理解透整个框架原理结构运行过程可以少走很多弯路。 
(本文同时发表于作者个人博客 www.jianshu.com/u/ced6b70c7…) 
参考 
Netty入门与实战仿写微信 IM 即时通讯系统 
Netty官网 
Netty 4.x学习笔记 - 线程模型 
Netty入门与实战 
理解高性能网络模型 
Netty基本原理介绍 
software-architecture-patterns.pdf 
Netty高性能之道 —— 李林锋 
《Netty In Action》 
《Netty权威指南》