Netty
是一个高性能、异步事件驱动的NIO
框架,它提供了对TCP
、UDP
和文件传输的支持。做为当前最流行的NIO
框架,Netty
在互联网领域、大数据分布式计算领域、游戏行业、通讯行业等得到了普遍的应用,一些业界著名的开源组件也是基于Netty
的NIO
框架构建。java
Netty
利用 Java
高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API
构建一个客户端/服务端,其具备高并发、传输快、封装好等特色。编程
高并发 Netty
是一款基于NIO
(Nonblocking I/O
,非阻塞IO
)开发的网络通讯框架,对比于BIO
(Blocking I/O
,阻塞IO
),它的并发性能获得了很大提升 。segmentfault
传输快 Netty
的传输快其实也是依赖了NIO
的一个特性——零拷贝。数组
封装好
Netty封装了NIO操做的不少细节,提供易于使用的API,还有心跳、重连机制、拆包粘包方案等特性,使开发者能可以快速高效的构建一个稳健的高并发应用。缓存
JDK
原生 NIO
程序的问题安全
JDK
原生也有一套网络应用程序 API
,可是存在一系列问题,主要以下:服务器
NIO
的类库和 API
繁杂,使用麻烦。你须要熟练掌握 Selector
、ServerSocketChannel
、SocketChannel
、ByteBuffer
等。Java
多线程编程,由于 NIO
编程涉及到 Reactor
模式,你必须对多线程和网路编程很是熟悉,才能编写出高质量的 NIO
程序。NIO
编程的特色是功能开发相对容易,可是可靠性能力补齐工做量和难度都很是大。JDK NIO
的 Bug
。例如臭名昭著的 Epoll Bug
,它会致使 Selector
空轮询,最终致使 CPU 100%
。 官方声称在 JDK 1.6
版本的 update 18
修复了该问题,可是直到 JDK 1.7
版本该问题仍旧存在,只不过该 Bug
发生几率下降了一些而已,它并无被根本解决。Netty
的特色网络
Netty
对 JDK
自带的 NIO
的 API
进行封装,解决上述问题,主要特色有:多线程
API
阻塞和非阻塞 Socket
;基于灵活且可扩展的事件模型,能够清晰地分离关注点;高度可定制的线程模型 - 单线程,一个或多个线程池;真正的无链接数据报套接字支持(自 3.1
起)。Javadoc
,用户指南和示例;没有其余依赖项,JDK 5
(Netty 3.x
)或 6
(Netty 4.x
)就足够了。安全,完整的 SSL/TLS
和 StartTLS
支持。架构
Bug
能够被及时修复,同时,更多的新功能会被加入。服务端:
ServerBootStrap
实例Reactor
线程池:EventLoopGroup
,EventLoop
就是处理全部注册到本线程的Selector
上面的Channel
Channel
ChannelPipeline
和handler
,网络时间以流的形式在其中流转,handler
完成多数的功能定制:好比编解码 SSl
安全认证channel
后,由Reactor
线程:NioEventLoop
执行pipline
中的方法,最终调度并执行channelHandler
客户端:
主要功能特性以下图:
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
做为异步事件驱动的网络,高性能之处主要来自于其 I/O
模型和线程处理模型,前者决定如何收发数据,后者决定如何处理数据。
I/O 模型
用什么样的通道将数据发送给对方,BIO
、NIO
或者 AIO
,I/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
一链接一线程模型,架构的性能、弹性伸缩能力和可靠性都获得了极大的提高。
Netty
线程模型
Netty
主要基于主从 Reactors
多线程模型(以下图)作了必定的修改,其中主从 Reactor
多线程模型有多个 Reactor
:
MainReactor
负责客户端的链接请求,并将请求转交给 SubReactor
。SubReactor
负责相应通道的 IO
读写请求。
非 IO
请求(具体逻辑处理)的任务则会直接写入队列,等待 worker threads
进行处理。
这里引用 Doug Lee
大神的 Reactor
介绍:Scalable IO in Java
里面关于主从 Reactor
多线程模型的图:
特别说明的是:虽然 Netty
的线程模型基于主从 Reactor
多线程,借用了 MainReactor
和 SubReactor
的结构。可是实际实现上 SubReactor
和 Worker
线程在同一个线程池中。
Netty
的零拷贝
是在发送数据的时候,传统的实现方式是:
File.read(bytes); Socket.send(bytes);
这种方式须要四次数据拷贝和四次上下文切换:
read buffer
socket buffer
socket buffer
拷贝到网卡接口(硬件)的缓冲区零拷贝的概念
明显上面的第二步和第三步是没有必要的,经过java
的FileChannel.transferTo
方法,能够避免上面两次多余的拷贝(固然这须要底层操做系统支持)
transferTo
,数据从文件由DMA
引擎拷贝到内核read buffer
DMA
从内核read buffer
将数据拷贝到网卡接口buffer
上面的两次操做都不须要CPU
参与,因此就达到了零拷贝。
Netty
中的零拷贝主要体如今三个方面:
bytebuffer
Netty
发送和接收消息主要使用bytebuffer
,bytebuffer
使用对外内存(DirectMemory
)直接进行Socket
读写。缘由:若是使用传统的堆内存进行
Socket
读写,JVM
会将堆内存buffer
拷贝一份到直接内存中而后再写入socket
,多了一次缓冲区的内存拷贝。DirectMemory
中能够直接经过DMA发送到网卡接口。
Composite Buffers
传统的ByteBuffer
,若是须要将两个ByteBuffer
中的数据组合到一块儿,咱们须要首先建立一个size=size1+size2
大小的新的数组,而后将两个数组中的数据拷贝到新的数组中。
可是使用Netty
提供的组合ByteBuf
,就能够避免这样的操做,由于CompositeByteBuf
并无真正将多个Buffer
组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。
FileChannel.transferTo
的使用Netty
中使用了FileChannel的transferTo
方法,该方法依赖于操做系统实现零拷贝。