Netty原理架构解析
Netty原理架构解析
转载⾃:
本⽂转载关于Netty的原理架构解析,⽅便之后巩固复习
Netty是⼀个异步事件驱动的⽹络应⽤程序框架,⽤于快速开发可维护的⾼性能协议服务器和客户端。JDK原⽣也有⼀套⽹络应⽤程序API,NIO,但是存在⼀些问题使得⽤起来不是很⽅便,主要如下:
1. NIO的类库和API繁杂,使⽤⿇烦。使⽤时需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
2. 需要具备其他的额外技能做铺垫。例如熟悉Java多线程编程,因为NIO编程涉及到Reactor模式,你必须对多线程和⽹络编程⾮常熟悉,才能编写出
⾼质量的NIO程序
3. 可靠性能⼒补齐,开发⼯作量和难度都⾮常⼤。例如客户端⾯临断连重连、⽹络闪断、半包读写、失败缓存、⽹络拥塞和异常码流的处理等等。
NIO编程的特点是功能开发相对容易,但是可靠性能⼒补齐⼯作量和难度都⾮常⼤
4. JDK NIO的Bug。例如臭名昭著的Epoll Bug,它会导致Selector空轮询,最终导致CPU 100%。官⽅声称在JDK 1.6版本的update 18修复了该问
reactor线程模型 java题,但是直到JDK1.7版本该问题仍旧存在,只不过该Bug发⽣概率降低了⼀些⽽已,它并没有被根本解决
Netty对JDK⾃带的NIO的API进⾏封装,解决上述问题,主要特点有:
1. 设计优雅,适⽤于各种传输类型的统⼀API阻塞和⾮阻塞Socket;基于灵活且可扩展的事件模型,可以清晰地分析关注点;⾼度可定制的线程模型-
单线程,⼀个或多个线程池;真正的⽆连接数据报套接字⽀持
2. 使⽤⽅便,详细记录的Javadoc,⽤户指南和⽰例;没有其他依赖项,JDK5(Netty
3.x)或6 (Netty
4.x) 就⾜够了
3. ⾼性能,吞吐量更⾼,延迟更低;减少资源消耗;最⼩化不必要的内存复制
4. 安全,完整的SSL/TLS和StartTLS⽀持
5. 社区活跃,不断更新,社区活跃,版本迭代周期短,发现的Bug可以被及时修复,同时,更多的新功能会被加⼊
Netty常见的使⽤场景如下:
1. 互联⽹⾏业。在分布式系统中,各个节点之间需要远程服务调⽤,⾼性能的RPC框架必不可少,Netty作为异步⾼性能的通信框架,往往作为基础通
信组件被这些RPC框架使⽤。典型的应⽤有:阿⾥分布式服务框架Dubbo的RPC框架使⽤Dubbo协议进⾏节点间通信,Dubbo协议默认使⽤Netty 作为基础通信组件,⽤于实现各进程节点之间的内部通信
2. 游戏⾏业。⽆论是⼿游服务端还是⼤型的⽹络游戏,Java语⾔得到了越来越⼴泛的应⽤。Netty作为⾼性能的基础通信组件,它本⾝提供了
TCP/UDP和HTTP协议栈。⾮常⽅便定制和开发私有协议栈,账号登录服务器,地图服务器之间可以⽅便的通过Netty进⾏⾼性能的通信
3. ⼤数据领域。经典的Hadoop的⾼性能通信和序列化组件Avro的RPC框架,默认采⽤Netty进⾏跨节点通信,它的Netty Service基于Netty框架⼆次封
装实现
Netty⾃以为异步事件驱动的⽹络,⾼性能之处主要来⾃于其I/O模型和线程处理模型,前者决定如何收发数据,后者决定如何处理数据
阻塞I/O
传统阻塞I/O(BIO)的特点是:
1. 每个请求都需要独⽴的线程完成数据read,业务处理,数据write的完整操作问题
2. 当并发数较⼤时,需要创建⼤量线程来处理连接,系统资源占⽤较⼤
3. 连接建⽴后,如果当前线程暂时没有数据可读,则线程就阻塞在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线程的运⾏效率,避免由于频繁IO阻塞导致的线程挂起
⼀个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O-连接-线程模型,架构的性能、弹性伸缩能⼒和可靠性都得到了极⼤的提升
基于Buffer
传统的IO是⾯向字节流活字符流的,以流式的⽅式顺序地从⼀个stream中读取⼀个或多个字节,因此也就不能随意改变读取指针的位置
在NIO中,抛弃了传统的IO流,,⽽是引⼊了Channel和Buffer的概念。在NIO中,只能从channel中读取数据到Buffer中或将数据从Buffer中写⼊到channel中
基于Buffer操作不像传统IO的顺序操作,NIO中可以随意地读取任意位置的数据
事件驱动模型
通常,我们设计⼀个事件处理模型的程序有两种思路:
1. 轮询⽅式,线程不断轮询访问相关事件发⽣源有没有发⽣事件,有发⽣事件就调⽤事件处理逻辑
2. 事件驱动⽅式,发⽣事件,主线程吧事件放⼊事件队列,在另外线程不断循环消费事件列表中的事件,调⽤事件对应的处理逻辑处理事件。事件驱
动⽅式也被成为消息通知⽅式,其实是设计模式中的观察者模式的思路
主要包括4个组件:
1. 事件队列:接收事件的⼊⼝,存储待处理事件
2. 分发器:将不同的事件分发到不同的业务逻辑单元
3. 事件通道:分发器与处理器之间的联系渠道
4. 事件处理器:实现业务逻辑,处理完成后会发出事件,触发下⼀步操作
可以看出,相对传统轮询模式,事件驱动有如下优点:
1. 可扩展性好,分布式的异步架构,事件处理器之间⾼度解耦,可以⽅便扩展事件处理逻辑
2. ⾼性能,基于队列暂存事件,能⽅便并⾏异步处理事件
Reactor线程模型
Reactor是反应堆的意思,Reactor模型是指通过⼀个或多个输⼊同时传递给服务处理器的服务请求的事件驱动处理模式
服务端程序处理传⼊多路请求,并将它们同步分派给请求对应的处理线程,Reactor模式也叫Dispatcher模式,即I/O多路复⽤统⼀监听事件,收到事件后分发(Dispatch给某进程),是编写⾼性能⽹络服务器的必备技术之⼀
Reactor模型中有2个关键组成:
1. Reactor,在⼀个单独的线程中运⾏,负责监听和分发事件,分发给适当的处理程序来对IO事件作出反应。
2. Handlers,处理程序执⾏IO事件要完成的实际事件,,reactor通过调⽤适当的处理程序来响应IO事件,处理程序执⾏⾮阻塞操作
取决于Reactor的数量和Handler线程数量的不同,Reactor模型有3个变种:
1. 单reactor 单线程
2. 单reactor 多线程
3. 主从reactor 多线程
Netty线程模型
Netty主要是基于主从Reactors多线程模型(如下图)做了⼀些修改,其中主从reactor多线程模型有多个reactor:
1. MainReactor负责客户端的连接请求,并将请求转交给SubReactor
2. SubReactor负责相应通道的IO读写请求
3. ⾮IO请求(具体逻辑处理)的任务则会直接进⼊写⼊队列,等到worker threads进⾏处理
这⾥引⽤Doug Lee⼤神的Reactor介绍:Scalable IO in Java⾥⾯关于主从Reactor多线程模型的图:
特别说明的是:虽然Netty的线程模型基于主从Reactor多线程,借⽤了MainReactor和SubReactor的结构。但是实际实现上SubReactor和Worker线程在同⼀个线程池中
1. bossGroup线程池则只是在bind某个端⼝后,获得其中⼀个线程作为MainReactor,专门处理端⼝的Accept事件,每个端⼝对应⼀个boss线程
2. workerGroup线程池会被各个SubReactor和Worker线程充分利⽤
异常处理
异步的概念和同步相对。当⼀个异步过程调⽤发出后,调⽤者不能⽴刻得到结果。实际处理这个调⽤的部件在完成后,通过状态、通知和回调来通知调⽤者
Netty中的IO操作是异步的,包括Bind、Write、Connect等操作会简单的返回⼀个channelFuture
调⽤者并不能⽴刻获得结果,⽽是通过Future-Listener机制,⽤户可以⽅便的主动获取或通过通知机制来获得IO操作结果
当future对象刚刚创建时,处于⾮完成状态,调⽤者可以通过返回的ChannelFuture来获取操作执⾏的状态,注册监听函数来执⾏完成后的操作
模块组件
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 的 Javadoc 4.1 中 ChannelPipeline 的说明,描述了 ChannelPipeline 中 ChannelHandler 通常如何处理 I/O 事件。
I/O 事件由 ChannelInboundHandler 或 ChannelOutboundHandler 处理,并通过调⽤ ChannelHandlerContext 中定义的事件传播⽅法。
例如 ChannelHandlerContext.fireChannelRead(Object)和 ChannelOutboundInvoker.write(Object)转发到其最近的处理程序。
⼊站事件由⾃下⽽上⽅向的⼊站处理程序处理,如图左侧所⽰。⼊站 Handler 处理程序通常处理由图底部的 I/O 线程⽣成的⼊站数据。
通常通过实际输⼊操作(例如 ad(ByteBuffer))从远程读取⼊站数据。
出站事件由上下⽅向处理,如图右侧所⽰。出站 Handler 处理程序通常会⽣成或转换出站传输,例如 write 请求。
I/O 线程通常执⾏实际的输出操作,例如 SocketChannel.write(ByteBuffer)。
在 Netty 中每个 Channel 都有且仅有⼀个 ChannelPipeline 与之对应,它们的组成关系如下:
⼀个 Channel 包含了⼀个 ChannelPipeline,⽽ ChannelPipeline 中⼜维护了⼀个由 ChannelHandlerContext 组成的双向链表,并且每个ChannelHandlerContext 中⼜关联着⼀个 ChannelHandler。
⼊站事件和出站事件在⼀个双向链表中,⼊站事件会从链表 head 往后传递到最后⼀个⼊站的 handler,出站事件会从链表 tail 往前传递到最前⼀个出站的 handler,两种类型的 handler 互不⼲扰。
Netty ⼯作原理架构
初始化并启动 Netty 服务端过程如下:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论