【Netty】Netty框架介绍
⼀、Netty简介
  Netty是由JBOSS提供的⼀个java开源框架,现为 Github上的独⽴项⽬。Netty提供异步的、事件驱动的⽹络应⽤程序框架和⼯具,⽤以快速开发⾼性能、⾼可靠性的⽹络服务器和客户端程序。
  也就是说,Netty 是⼀个基于NIO的客户、服务器端的编程框架,使⽤Netty 可以确保你快速和简单的开发出⼀个⽹络应⽤,例如实现了某种协议的客户、服务端应⽤。Netty相当于简化和流线化了⽹络应⽤的编程开发过程,例如:基于TCP和UDP的socket服务开发。 
  “快速”和“简单”并不⽤产⽣维护性或性能上的问题。Netty 是⼀个吸收了多种协议(包括FTP、SMTP、HTTP等各种⼆进制⽂本协议)的实现经验,并经过相当精⼼设计的项⽬。最终,Netty 成功的到了⼀种⽅式,在保证易于开发的同时还保证了其应⽤的性能,稳定性和伸缩性
1.1 Netty的特点
设计优雅
适⽤于各种传输类型的统⼀API - 阻塞和⾮阻塞Socket
基于灵活且可扩展的事件模型,可以清晰地分离关注点
⾼度可定制的线程模型 - 单线程,⼀个或多个线程池
真正的⽆连接数据报套接字⽀持(⾃3.1起)
使⽤⽅便
详细记录的Javadoc,⽤户指南和⽰例
没有其他依赖项,JDK 5(Netty 3.x)或6(Netty 4.x)就⾜够了
⾼性能
吞吐量更⾼,延迟更低
减少资源消耗
最⼩化不必要的内存复制
安全
完整的SSL / TLS和StartTLS⽀持
社区活跃,不断更新
社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会被加⼊
1.2 Netty常见使⽤场景
互联⽹⾏业
在分布式系统中,各个节点之间需要远程服务调⽤,⾼性能的RPC框架必不可少,Netty作为异步⾼新能的通信框架,往往作为基础通信组件被这些RPC框架使⽤。
典型的应⽤有:阿⾥分布式服务框架Dubbo的RPC框架使⽤Dubbo协议进⾏节点间通信,Dubbo协议默认使⽤Netty作为基础通信组件,⽤于实现各进程节点之间的内部通信。
游戏⾏业
⽆论是⼿游服务端还是⼤型的⽹络游戏,Java语⾔得到了越来越⼴泛的应⽤。Netty作为⾼性能的基础通信组件,它本⾝提供了TCP/UDP和HTTP协议栈。
⾮常⽅便定制和开发私有协议栈,账号登录服务器,地图服务器之间可以⽅便的通过Netty进⾏⾼性能的通信
⼤数据领域
经典的Hadoop的⾼性能通信和序列化组件Avro的RPC框架,默认采⽤Netty进⾏跨界点通信,它的Netty Service基于Netty框架⼆次封装实现
⼆、Netty⾼性能设计
2.1 Netty线程模型
  Netty主要基于主从Reactors多线程模型(参考:)(如下图)做了⼀定的修改,其中主从Reactor多线程模型有多个Reactor:MainReactor和SubReactor:
MainReactor负责客户端的连接请求,并将请求转交给SubReactor
SubReactor负责相应通道的IO读写请求
⾮IO请求(具体逻辑处理)的任务则会直接写⼊队列,等待worker threads进⾏处理
  这⾥引⽤Doug Lee⼤神的Reactor介绍:⾥⾯关于主从Reactor多线程模型的图
  特别说明的是:
  虽然Netty的线程模型基于主从Reactor多线程,借⽤了MainReactor和SubReactor的结构,但是实际实现上,SubReactor和Worker线程在同⼀个线程池中:
1 EventLoopGroup bossGroup = new NioEventLoopGroup();
2 EventLoopGroup workerGroup = new NioEventLoopGroup();
3 ServerBootstrap server = new ServerBootstrap();
up(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
  上⾯代码中的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⽅法来判断已完成的当前操作是否成功
通过getCause⽅法来获取已完成的当前操作失败的原因
通过isCancelled⽅法来判断已完成的当前操作是否被取消
通过addListener⽅法来注册,当操作已完成(isDone⽅法返回完成),将会通知指定的;如果future对象已完成,则理解通知指定的
  例如下⾯的代码中绑定端⼝是异步操作,当绑定操作处理完,将会调⽤相应的处理逻辑
1 serverBootstrap.bind(port).addListener(future -> {
2if (future.isSuccess()) {
3        System.out.println(new Date() + ": 端⼝[" + port + "]绑定成功!");
4    } else {
5        println("端⼝[" + 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,异步的客户端 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处理,并通过调⽤C
hannelHandlerContext中定义的事件传播⽅法(例如ChannelHandlerContext.fireChannelRead(Object)和ChannelOutboundInvoker.write(Object))转发到其最近的处理程序。
1                                                  I/O Request
2                                            via Channel or
3                                        ChannelHandlerContext
4                                                      |
5  +---------------------------------------------------+---------------+
6  |                          ChannelPipeline        |              |
7  |                                                  \|/              |
8  |    +---------------------+            +-----------+----------+    |
9  |    | Inbound Handler  N  |            | Outbound Handler  1  |    |
10  |    +----------+----------+            +-----------+----------+    |
11  |              /|\                                  |              |
12  |              |                                  \|/              |
13  |    +----------+----------+            +-----------+----------+    |
14  |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
15  |    +----------+----------+            +-----------+----------+    |
16  |              /|\                                  .              |
17  |              .                                  .              |
18  | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
19  |        [ method call]                      [method call]        |
20  |              .                                  .              |
21  |              .                                  \|/              |
22  |    +----------+----------+            +-----------+----------+    |
23  |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
24  |    +----------+----------+            +-----------+----------+    |
25  |              /|\                                  |              |
26  |              |                                  \|/              |
27  |    +----------+----------+            +-----------+----------+    |
28  |    | Inbound Handler  1  |            | Outbound Handler  M  |    |
29  |    +----------+----------+            +-----------+----------+    |
30  |              /|\                                  |              |
31  +---------------+-----------------------------------+---------------+
32                  |                                  \|/
33  +---------------+-----------------------------------+---------------+
34  |              |                                  |              |
35  |      [ ad() ]                    [ Socket.write() ]    |
36  |                                                                  |
37  |  Netty Internal I/O Threads (Transport Implementation)            |
38  +-------------------------------------------------------------------+
  ⼊站事件由⾃下⽽上⽅向的⼊站处理程序处理,如图左侧所⽰。⼊站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服务端过程如下:
1public static void main(String[] args) {
2// 创建mainReactor
3        NioEventLoopGroup boosGroup = new NioEventLoopGroup();
4// 创建⼯作线程组
5        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
6
7final ServerBootstrap serverBootstrap = new ServerBootstrap();
8        serverBootstrap
9// 组装NioEventLoopGroup
10                .group(boosGroup, workerGroup)
11// 设置channel类型为NIO类型
springboot原理pdf12                .channel(NioServerSocketChannel.class)
13// 设置连接配置参数
14                .option(ChannelOption.SO_BACKLOG, 1024)
15                .childOption(ChannelOption.SO_KEEPALIVE, true)
16                .childOption(ChannelOption.TCP_NODELAY, true)
17// 配置⼊站、出站事件handler
18                .childHandler(new ChannelInitializer<NioSocketChannel>() {
19                    @Override
20protected void initChannel(NioSocketChannel ch) {
21// 配置⼊站、出站事件channel
22                        ch.pipeline().addLast(...);
23                        ch.pipeline().addLast(...);
24                    }
25    });
26
27// 绑定端⼝
28int port = 8080;
29        serverBootstrap.bind(port).addListener(future -> {
30if (future.isSuccess()) {
31                System.out.println(new Date() + ": 端⼝[" + port + "]绑定成功!");
32            } else {
33                println("端⼝[" + port + "]绑定失败!");
34            }
35        });
36 }
基本过程如下:
1 初始化创建2个NioEventLoopGroup,其中boosGroup⽤于Accetpt连接建⽴事件并分发请求, workerGroup⽤于处理I/O读写事件和
业务逻辑
2 基于ServerBootstrap(服务端启动引导类),配置EventLoopGroup、Channel类型,连接参数、配置⼊站、出站事件handler
3 绑定端⼝,开始⼯作
  结合上⾯的介绍的Netty Reactor模型,介绍服务端Netty的⼯作架构图:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。