【前言】
netty作为Java领域中优秀的网络编程框架,其设计思想,代码实现都非常值的我们去学习,同时,更好的了解netty的概念、设计、细节后,我们使用起来也就会更踏实、更称手,从而让netty这件神兵利器在我们手中发挥出最大的威力!!!
netty简介
What is netty?
Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端
netty的优势
API使用简单,使用&开发 门槛低- 预置多种编解码器,支持多种主流协议,亦可定制私有协议。
- 基于灵活且可扩展的事件模型
- 高度可定制的线程模型,真正的无连接数据报套接字支持(自 3.1 起)
- 解决了
Java中NIO使用起来繁琐,空轮询等等问题 - 社区活跃,经历无数个商业&开源项目验证,稳定性好
- 完整的
SSL/TLS,StartTLS支持 - 高吞吐,低时延,更少的资源,减少不必要的内存拷贝
- ...... 总之一句话: 牛x。
netty架构
- 图中可以了解到
netty包含了事件模型,统一的API,零拷贝,以及其丰富的协议支持
netty绕不开的知识点之 IO模型
- 然而,说起
netty的IO模型,我们就不得不说到Unix的IO模型
Unix系统五种I/O模型
- 下面我们以
UDP的socket读为例来介绍一下五种I/O模型(但是在介绍I/O模型之前,我们需要了解下Unix系统函数recvfrom)
recvfrom系统函数介绍
-
说明:
recvfrom函数用来从一个套接口(有的文献称为套接字)接收消息,也可以用来在一个面向连接(如TCP)或非连接(UDP)的套接口上接收数据(但是大部分时候都是在UDP协议中使用) -
recvfrom函数在Java中的应用 (可以看到Java中是通过调用c语言来调用的recvfrom函数),感兴趣可以看下系统函数accept在Java中都有哪几个地方使用到了。 -
recvfrom函数的执行时机
- 好了,介绍完
recvfrom函数,我们紧接着来看下 Unix 的5类I/O模型吧!
阻塞式I/O
- 进程调用
recvfrom系统函数,直到数据包到达且被复制到应用进程的缓冲区中或 中间发生异常时才返回,在这个期间进程会一直等待。进程从调用recvfrom开始到它返回的整段时间内都是被阻塞的,因此,被称为阻塞 式I/O 模型
所以,阻塞式I/O的特点就是在IO执行的两个阶段都被 “阻塞”了
非阻塞式I/O
-
recvfrom调用到达内核时,如果该缓冲区没有数据,就会直接返回EWOULDBLOCK错误,一般都会对非阻塞 I/O 模型进行轮询以检查该状态,看看内核是不是有数据报到来,recvform调用之后,进程并没有被阻塞,而是内核马上返回给进程,从而达到非阻塞的效果 -
如果数据还没准备好,此时会返回一个
EWOULDBLOCK。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用,这个过程通常被称之为轮询 -
如果某次轮询发现数据已经准备好了,那么拷贝数据到用户空间,然后返回成功指示,随后进程进行数据报处理。需要注意:
拷贝数据整个过程,进程仍然是属于阻塞的状态(图中的阶段2) -
值的注意的是,
非阻塞I/O模型往往会消耗大量的cpu,因为其轮询方式是一个很耗cpu的操作
I/O复用(select pool)
- IO 复用模型:进程发起一个 IO 请求,然后把自己注册到一个
select的系统进程,select会监听数据是否准备好,若准备好select会通知进程,进程发起系统调用,CPU 会复制内核空间缓冲区数据到用户空间,同样,复制数据过程也是阻塞的。 - 为什么叫 IO 多路复用呢?因为
select 可以接受多个进程注册,可以监听多个进程的 IO 请求,不像非阻塞式 IO 模型需要进程不断轮询,多路复用模型把询问的任务交给了 select,让多个进程都可以共享 select
信号驱动方式I/O
- 若使用该I/O模型,需要开启套接字的信号驱动式I/O功能,步骤如下:
- 要求进程建立
SIGON信号的信号处理函数 - 设置该套接字的属主,通常使用
fcntl的F_SETOWN设置 - 开启该套接字的信号驱动式I/O,通常通过
fcntl的F_SETFL命令打开O_ASYNC标志
- 要求进程建立
- 通过
sigaction系统调用安装一个信号处理函数,该系统调用将会立即返回,进程继续做其他事情,当有数据报准备就绪时,内核就为该进程产生一个SIGIO信号,之后我们的进程发起recvfrom函数调用,当数据从内核复制到用户空间完成后(需要注意:拷贝数据整个过程,进程仍然是属于阻塞的状态),返回成功指示,进程开始处理数据报
异步I/O (POSIX的aio系列函数)
- 可以看到,进程发起系统调用后,直接返回,不需要阻塞,直到数据从内核空间复制用户空间完成后,系统通知进程 I/O 完成
- 注意异步I/O和信号驱动I/O的主要区别是:
信号驱动I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O是通知我们I/O操作何时完成
五种I/O模型对比
- 根据上图我们可以看出,
阻塞,非阻塞,复用,信号驱动这四种,本质上都是同步I/O,只有第五种异步I/O才是真正的异步处理方式
- 然后说完I/O模型还不够,还需要学习一下线程模型,才能为我们学习netty打下更好的基础!
netty绕不开的知识点之 线程模型
经典/传统 线程模型
-
这个传统线程模型不用多说相信大家都明白,就是
为每个连接都创建一个线程,只要量稍微一上来,系统必然会gg -
大师们为了
改变这糟糕的情况,于是催生了Reactor线程模型
Reactor线程模型
模型特点
-
Reactor模型中有 3 个关键组成:- Reactor:
Reactor在一个单独的线程中运行,负责监听和分发事件,分发给相应的处理程序来对 IO 事件做出反应。 它就像公司的电话接线员,它接听来自客户的电话并将线路转移到对应的联系人 - Handlers:实际上,你可以把他理解为处理器,即执行
读取数据报并执行业务逻辑的地方 - ThreadPool:(严格讲,单Reactor 单线程的模型并没有线程池,所以线程池只是针对后两种Reactor线程模型来说的)
线程池负责处理(decode=>compute=>encode) 这些业务操作
- Reactor:
-
根据
Reactor的数量和线程的数量不同,Reactor又分为有3 种典型的实现单 Reactor 单线程单 Reactor 多线程主从 Reactor 多线程
下面我们分别来看一下这三种线程模型都是什么,以及他们有什么优劣
单 Reactor 单线程
执行流程简述
- Reactor: 对象通过
Select监控客户端请求事件,收到事件后通过Dispatch进行分发 - 如果是建立连接请求事件:则由
Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler对象处理连接完成后的后续业务逻辑 - 如果不是建立连接事件: (
如read ,write),则Reactor 会分发并调用对应的Handler来响应 - Handler: 会完成
read=> (decode=>compute=>encode) =>send的完整业务流程
该线程模型的优缺
- 优点:模型
简单,没有线程安全问题,因为都在一个线程中完成。 - 缺点:
性能问题: 只有一个线程,无法充分发挥多核 CPU 的优势。Handler 在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈;系统可靠性不能保证,如线程进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障
使用场景:客户端的数量有限,业务处理非常快速,比如 Redis
单Reactor 多线程
- 从上图可以看出,这种模型和
单Reactor 单线程模型的主要区别是把业务处理从之前的单一线程,换成线程池处理。
执行流程简述
-
Reactor线程:通过
select监听客户请求,如果是连接建立的事件,通过accept接受连接,并创建一个Handler来处理连接后续的读写事件。这里的Handler只负责响应事件、read和write事件,会将具体的业务处理交由Worker线程池处理 -
Worker线程池:负责处理(
decode=>compute=>encode) 这三个操作
该线程模型的优缺
- 优点: 业务处理
充分利用多核机器的资源,提高性能。 - 缺点: 由于
Reactor仍然是单线程,所以在极个别特殊场景中(如大并发情况下--->双十一,春运抢票等)Reactor线程负责监听和处理所有的客户端连接可能会存在性能问题
主从Reactor 多线程
执行流程简述
- 主 Reactor: 单独监听
server socket,accept新连接,然后将建立的SocketChannel注册给指定的 从Reactor - 从Reactor: 将连接加入到连接队列进行监听,并创建handler进行事件处理。执行事件的读写、分发,然后把剩余的业务处理扔给worker线程池
- Worker线程池:负责处理(
decode=>compute=>encode) 这些操作
该模型优缺
- 优点:充分利用多核机器的资源,提高性能,轻松处理百万并发
- 缺点:实现起来相对复杂一些
- 到这里,我们说了
netty的优点,架构以及I/O模型和线程模型,那么实际编程中,我们需要自己去实现Reactor线程模型吗?答案是不需要的,netty已经帮我们做好了! 是不是想说:哎嘛,真香!!!
【结语】
本来我想把netty的核心组件也放到此文,但是鉴于让文章更清晰明了,不那么臃肿,所以我将在下一篇文章,讲述netty组件以及netty的使用。到这里,此文完~
参考:
gee.cs.oswego.edu/dl/cpjslide…
developer.51cto.com/article/666…
netty.io/index.html
《UNIX网络编程:卷一》