【Netty】Netty框架介绍
⼀、Netty简介
Netty是由JBOSS提供的⼀个java开源框架,现为Github上的独⽴项⽬。Netty提供异步的、事件驱动的⽹络应⽤程序框架和⼯具,⽤以
快速开发⾼性能、⾼可靠性的⽹络服务器和客户端程序。
也就是说,Netty是⼀个基于NIO的客户、服务器端的编程框架,使⽤Netty可以确保你快速和简单的开发出⼀个⽹络应⽤,例如实现了
某种协议的客户、服务端应⽤。Netty相当于简化和流线化了⽹络应⽤的编程开发过程,例如:基于TCP和UDP的socket服务开发。
“快速”和“简单”并不⽤产⽣维护性或性能上的问题。Netty是⼀个吸收了多种协议(包括FTP、SMTP、HTTP等各种⼆进制⽂本协议)的
实现经验,并经过相当精⼼设计的项⽬。最终,Netty成功的找到了⼀种⽅式,在保证易于开发的同时还保证了其应⽤的性能,稳定性和伸
缩性
1.1Netty的特点
设计优雅
适⽤于各种传输类型的统⼀API-阻塞和⾮阻塞Socket
基于灵活且可扩展的事件模型,可以清晰地分离关注点
⾼度可定制的线程模型-单线程,⼀个或多个线程池
真正的⽆连接数据报套接字⽀持(⾃3.1起)
使⽤⽅便
详细记录的Javadoc,⽤户指南和⽰例
没有其他依赖项,JDK5(Netty3.x)或6(Netty4.x)就⾜够了
⾼性能
吞吐量更⾼,延迟更低
减少资源消耗
最⼩化不必要的内存复制
安全
完整的SSL/TLS和StartTLS⽀持
社区活跃,不断更新
社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会被加⼊
1.2Netty常见使⽤场景
互联⽹⾏业
在分布式系统中,各个节点之间需要远程服务调⽤,⾼性能的RPC框架必不可少,Netty作为异步⾼新能的通信框架,往往作为基础通信
组件被这些RPC框架使⽤。
典型的应⽤有:阿⾥分布式服务框架Dubbo的RPC框架使⽤Dubbo协议进⾏节点间通信,Dubbo协议默认使⽤Netty作为基础通信组
件,⽤于实现各进程节点之间的内部通信。
游戏⾏业
⽆论是⼿游服务端还是⼤型的⽹络游戏,Java语⾔得到了越来越⼴泛的应⽤。Netty作为⾼性能的基础通信组件,它本⾝提供了
TCP/UDP和HTTP协议栈。
⾮常⽅便定制和开发私有协议栈,账号登录服务器,地图服务器之间可以⽅便的通过Netty进⾏⾼性能的通信
⼤数据领域
经典的Hadoop的⾼性能通信和序列化组件Avro的RPC框架,默认采⽤Netty进⾏跨界点通信,它的NettyService基于Netty框架⼆次封
装实现
⼆、Netty⾼性能设计
2.1Netty线程模型
Netty主要基于主从Reactors多线程模型(参考:)(如下图)做了⼀定的修改,其中主从Reactor多线程模型有多个Reactor:
MainReactor和SubReactor:
MainReactor负责客户端的连接请求,并将请求转交给SubReactor
SubReactor负责相应通道的IO读写请求
⾮IO请求(具体逻辑处理)的任务则会直接写⼊队列,等待workerthreads进⾏处理
这⾥引⽤DougLee⼤神的Reactor介绍:⾥⾯关于主从Reactor多线程模型的图
特别说明的是:
虽然Netty的线程模型基于主从Reactor多线程,借⽤了MainReactor和SubReactor的结构,但是实际实现上,SubReactor和Worker线
程在同⼀个线程池中:
1EventLoopGroupbossGroup=newNioEventLoopGroup();
2EventLoopGroupworkerGroup=newNioEventLoopGroup();
3ServerBootstraprver=newServerBootstrap();
(bossGroup,workerGroup).channel()
上⾯代码中的bossGroup和workerGroup是Bootstrap构造⽅法中传⼊的两个对象,这两个group均是线程池
bossGroup线程池则只是在bind某个端⼝后,获得其中⼀个线程作为MainReactor,专门处理端⼝的accept事件,每个端⼝对应⼀个
boss线程
workerGroup线程池会被各个SubReactor和worker线程充分利⽤
2.2异步处理
异步的概念和同步相对。当⼀个异步过程调⽤发出后,调⽤者不能⽴刻得到结果。实际处理这个调⽤的部件在完成后,通过状态、通知
和回调来通知调⽤者。
Netty中的I/O操作是异步的,包括bind、write、connect等操作会简单的返回⼀个ChannelFuture,调⽤者并不能⽴刻获得结果,通过
Future-Listener机制,⽤户可以⽅便的主动获取或者通过通知机制获得IO操作结果。
当future对象刚刚创建时,处于⾮完成状态,调⽤者可以通过返回的ChannelFuture来获取操作执⾏的状态,注册监听函数来执⾏完成后
的操作,常见有如下:
通过isDone⽅法来判断当前操作是否完成
通过isSuccess⽅法来判断已完成的当前操作是否成功
通过getCau⽅法来获取已完成的当前操作失败的原因
通过isCancelled⽅法来判断已完成的当前操作是否被取消
通过addListener⽅法来注册监听器,当操作已完成(isDone⽅法返回完成),将会通知指定的监听器;如果future对象已完成,则理解通
知指定的监听器
例如下⾯的代码中绑定端⼝是异步操作,当绑定操作处理完,将会调⽤相应的监听器处理逻辑
(port).addListener(future->{
2if(ess()){
n(newDate()+":端⼝["+port+"]绑定成功!");
4}el{
n("端⼝["+port+"]绑定失败!");
6}
7});
相⽐传统阻塞I/O,执⾏I/O操作后线程会被阻塞住,直到操作完成;异步处理的好处是不会造成线程阻塞,线程在I/O操作期间可以执⾏别
的程序,在⾼并发情形下会更稳定和更⾼的吞吐量。
三、Netty架构设计
前⾯介绍完Netty相关⼀些理论介绍,下⾯从功能特性、模块组件、运作过程来介绍Netty的架构设计
3.1功能特性
传输服务
⽀持BIO和NIO
容器集成
⽀持OSGI、JBossMC、Spring、Guice容器
协议⽀持
HTTP、Protobuf、⼆进制、⽂本、WebSocket等⼀系列常见协议都⽀持。
还⽀持通过实⾏编码解码逻辑来实现⾃定义协议
Core核⼼
可扩展事件模型、通⽤通信API、⽀持零拷贝的ByteBuf缓冲对象
3.2模块组件
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,异步的客户端TCPSocket连接
NioServerSocketChannel,异步的服务器端TCPSocket连接
NioDatagramChannel,异步的UDP连接
NioSctpChannel,异步的客户端Sctp连接
NioSctpServerChannel,异步的Sctp服务器端连接
这些通道涵盖了UDP和TCP⽹络IO以及⽂件IO.
Selector
Netty基于Selector对象实现I/O多路复⽤,通过Selector,⼀个线程可以监听多个连接的Channel事件,当向⼀个Selector中注册Channel
后,Selector内部的机制就可以⾃动不断地查询(lect)这些注册的Channel是否有已就绪的I/O事件(例如可读,可写,⽹络连接完成等),这样
程序就可以很简单地使⽤⼀个线程⾼效地管理多个Channel。
NioEventLoop
NioEventLoop中维护了⼀个线程和任务队列,⽀持异步提交执⾏任务,线程启动时会调⽤NioEventLoop的run⽅法,执⾏I/O任务和⾮
I/O任务:
I/O任务
即lectionKey中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中定义的事件传播⽅法(例如
annelRead(Object)和(Object))转发到其最近的处理程序。
1I/ORequest
2viaChannelor
3ChannelHandlerContext
4|
5+---------------------------------------------------+---------------+
6|ChannelPipeline||
7||/|
8|+---------------------++-----------+----------+|
9||InboundHandlerN||OutboundHandler1||
10|+----------+----------++-----------+----------+|
11|/|||
12|||/|
13|+----------+----------++-----------+----------+|
14||InboundHandlerN-1||OutboundHandler2||
15|+----------+----------++-----------+----------+|
16|/|.|
17|..|
18|_EVT()_EVT()|
19|[methodcall][methodcall]|
20|..|
21|.|/|
22|+----------+----------++-----------+----------+|
23||InboundHandler2||OutboundHandlerM-1||
24|+----------+----------++-----------+----------+|
25|/|||
26|||/|
27|+----------+----------++-----------+----------+|
28||InboundHandler1||OutboundHandlerM||
29|+----------+----------++-----------+----------+|
30|/|||
31+---------------+-----------------------------------+---------------+
32||/
33+---------------+-----------------------------------+---------------+
34||||
35|[()][()]|
36||
37|NettyInternalI/OThreads(TransportImplementation)|
38+-------------------------------------------------------------------+
⼊站事件由⾃下⽽上⽅向的⼊站处理程序处理,如图左侧所⽰。⼊站Handler处理程序通常处理由图底部的I/O线程⽣成的⼊站数据。
通常通过实际输⼊操作(例如(ByteBuffer))从远程读取⼊站数据。
出站事件由上下⽅向处理,如图右侧所⽰。出站Handler处理程序通常会⽣成或转换出站传输,例如write请求。I/O线程通常执⾏实际
的输出操作,例如(ByteBuffer)。
在Netty中每个Channel都有且仅有⼀个ChannelPipeline与之对应,它们的组成关系如下:
⼀个Channel包含了⼀个ChannelPipeline,⽽ChannelPipeline中⼜维护了⼀个由ChannelHandlerContext组成的双向链表,并且每
个ChannelHandlerContext中⼜关联着⼀个ChannelHandler。⼊站事件和出站事件在⼀个双向链表中,⼊站事件会从链表head往后传递到
最后⼀个⼊站的handler,出站事件会从链表tail往前传递到最前⼀个出站的handler,两种类型的handler互不⼲扰。
四、⼯作原理架构
初始化并启动Netty服务端过程如下:
1publicstaticvoidmain(String[]args){
2//创建mainReactor
3NioEventLoopGroupboosGroup=newNioEventLoopGroup();
4//创建⼯作线程组
5NioEventLoopGroupworkerGroup=newNioEventLoopGroup();
6
7finalServerBootstraprverBootstrap=newServerBootstrap();
8rverBootstrap
9//组装NioEventLoopGroup
(boosGroup,workerGroup)
11//设置channel类型为NIO类型
l()
13//设置连接配置参数
(_BACKLOG,1024)
ption(_KEEPALIVE,true)
ption(_NODELAY,true)
17//配置⼊站、出站事件handler
andler(newChannelInitializer
19@Override
20protectedvoidinitChannel(NioSocketChannelch){
21//配置⼊站、出站事件channel
ne().addLast(...);
ne().addLast(...);
24}
25});
26
27//绑定端⼝
28intport=8080;
(port).addListener(future->{
30if(ess()){
n(newDate()+":端⼝["+port+"]绑定成功!");
32}el{
n("端⼝["+port+"]绑定失败!");
34}
35});
36}
基本过程如下:
1初始化创建2个NioEventLoopGroup,其中boosGroup⽤于Accetpt连接建⽴事件并分发请求,workerGroup⽤于处理I/O读写事件和
业务逻辑
2基于ServerBootstrap(服务端启动引导类),配置EventLoopGroup、Channel类型,连接参数、配置⼊站、出站事件handler
3绑定端⼝,开始⼯作
结合上⾯的介绍的NettyReactor模型,介绍服务端Netty的⼯作架构图:
rver端包含1个BossNioEventLoopGroup和1个WorkerNioEventLoopGroup,NioEventLoopGroup相当于1个事件循环组,这个组⾥
包含多个事件循环NioEventLoop,每个NioEventLoop包含1个lector和1个事件循环线程。
每个BossNioEventLoop循环执⾏的任务包含3步:
1轮询accept事件
2处理acceptI/O事件,与Client建⽴连接,⽣成NioSocketChannel,并将NioSocketChannel注册到某个WorkerNioEventLoop的
Selector上
3处理任务队列中的任务,runAllTasks。任务队列中的任务包括⽤户调⽤e或schedule执⾏的任务,或者其它线程提
交到该eventloop的任务
每个WorkerNioEventLoop循环执⾏的任务包含3步:
1轮询read、write事件
2处理I/O事件,即read、write事件,在NioSocketChannel可读、可写事件发⽣时进⾏处理
3处理任务队列中的任务,runAllTasks
其中任务队列中的task有3种典型使⽤场景
1⽤户程序⾃定义的普通任务
l().eventLoop().execute(newRunnable(){
2@Override
3publicvoidrun(){
4//...
5}
6});
2⾮当前reactor线程调⽤channel的各种⽅法
例如在推送系统的业务线程⾥⾯,根据⽤户的标识,找到对应的channel引⽤,然后调⽤write类⽅法向该⽤户推送消息,就会进⼊到这
种场景。最终的write会提交到任务队列中后被异步消费。
3⽤户⾃定义定时任务
l().eventLoop().schedule(newRunnable(){
2@Override
3publicvoidrun(){
4
5}
6},60,S);
五、总结
现在稳定推荐使⽤的主流版本还是Netty4,Netty5中使⽤了ForkJoinPool,增加了代码的复杂度,但是对性能的改善却不明显,所以这
个版本不推荐使⽤,官⽹也没有提供下载链接。
Netty⼊门门槛相对较⾼,其实是因为这⽅⾯的资料较少,并不是因为他有多难,⼤家其实都可以像搞透Spring⼀样搞透Netty。在学
习之前,建议先理解透整个框架原理结构,运⾏过程,可以少⾛很多弯路。
作者:caison
链接:/p/6681bfa36c4f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,⾮商业转载请注明出处。
本文发布于:2022-12-28 13:38:39,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/46952.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |