前言
千呼万唤始出来,经过5章的NIO学习,终于迎来了Netty,本章主要是对Netty做一个介绍和Netty的线程模型做一个分析。撸起袖子,准备好卫生纸,一起来看吧。
Java NIO的问题
经过前面章节的学习你应该能感受到NIO的问题,就是类比较多,方法也比较多,而且复杂,开发工作量和难度都非常大,还需要考虑网络问题、数据丢包和异常流的处理等等。
NIO是底层API,它的实现依赖于操作系统针对IO操作的APIs,使用NIO会经常发现代码在Linux上正常运行,但在Windows上就会出现问题。JDK的NIO还有一个Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。
总之直接使用Java NIO会面临一系列的问题,Netty的其出现就是为了解决NIO的不足
Netty的介绍
下面是从Netty官网趴下来的对Netty的定义
Netty是由JBOSS提供的一个java开源框架,Netty 是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty 是一个 NIO 客户端服务器框架,它可以快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和精简了 TCP 和 UDP 套接字服务器等网络编程。
简单理解:Netty是基于NIO(Nonblocking I/O,非阻塞IO)实现的网络通信框架,相比传统IO他的并发性能得到了很大提高,它在NIO基础上封装了NIO的使用细节,让网络编程变得更加高效简洁,大大简化了NIO的开发过程。
Netty拥有高性能、高吞吐量、低延迟、消耗资源少;零拷贝的特点,同时支持SSL/TLS 和 StartTLS ,社区活跃、版本迭代周期短。
Netty是互联网最流行的NIO框架,广泛应用于互联网领域,游戏领域,大数据领域,比较知名的ElasticSearch,Dubbo等技术都用到了Netty。
Netty的线程模型
线程模型的学习对于理解Netty是比不可少的,我们从线程模型的演变一步一步来讲解
BIO线程模型
首选是传统的BIO线程模型,这个在第一章我们有过了解,它的特点是:
- 一个请求需要创建一个线程去处理
- 如果read不到数据,线程会阻塞
所以这种线程模型导致的问题就是
- 高并发场景下会频繁创建很多线程,频繁的创建和销毁线程对系统性能损耗非常大
- read不到数据线程进行阻塞,这导致线程资源的浪费
Reactor线程模型
Reactor(也叫Dispacher)线程模型不再为每个请求创建线程 ,而是基于 I/O 复用模型,多个请求连接同一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当请求中有数据,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理,一个线程可以处理多个连接的业务,如图:
先来理解两个概念
- Reactor:即图中的ServiceHandler,Reactor负责监听和事件分发,分发给适当的处理程序来对 IO 事件做出反应, 在单独的线程中执行。就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人;
- Handlers:Reactor监听到IO事件,需要调度应用程序来处理即:Handler,Handler是真正处理 I/O 事件的程序,类似于接电话的实际的人员。
简单理解就是Reactor线程模型有两个角色 ,一个负责接待请求(Reactor), 一个负责处理请求(Handlers)
Reactor单线程
另外Reactor有三种模式 :Reactor 单线程模型 、单 Reactor 多线程模型 、
Reactor主从多线程模型 。Netty框架是基于主从Reactor多线程模型进行改进(多个主Reactor)。 下面是Reactor单线程模型,如图:
Reactor单线程模型工作流程如下
- Reactor 通过 Select 监控客户端请求,收到事件后通过 Dispatch 进行请求分发
- 如果是请求事件是建立连接,则由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler 。
- 如果不是连接事件,则 Reactor 会分发调用连接对应的 Handler 来处理请求
- Handler 会完成数据read,进行业务处理最后通过 Send 将响应结果返回给 Client
Reactor单线程模型优点是模型简单,缺点也是比较明显
- 线程单一,如果并发高,业务处理慢,很容易导致性能瓶颈,
- 如果线程故障或终止,整个系统不可用
所以这种模型不太适合高并发场景,和业务处理比较耗时的场景。
Reactor多线程
接下来看一下单Reactor 多线程模型 ,先看图把:
Reactor多线程模式工作流程如下
- Reactor 通过 Select 监控客户端请求,收到事件后通过 Dispatch 进行请求分发
- 如果是请求事件是建立连接,则由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler 。
- 如果不是连接事件,则 Reactor 会分发调用连接对应的 Handler 来处理请求
- Handler 只负责响应事件,不做具体业务处理,通过 Read 读取数据后,会分发给后面的 Worker 线程池进行业务处理
- Worker 线程池会分配独立的线程完成真正的业务处理,将响应结果发给 Handler 进行处理。
- Handler 收到响应结果后通过 Send 将响应结果返回给 Client。
这种模型引入了线程池,Reactor线程负责接收连接和响应事件,具体IO事件交给线程池分配的线程处理。优点是可以充分利用CPU,提供整体性能,但是Reactor承担所有的事件监听和分发,容易成为性能瓶颈,多线程的数据共享也是一个问题。
主从Reactor多线程
单Reactor多线程的问题是Reactor在单线程中执行可能会成为瓶颈,那么主从Reactor多线程就解决了这个问题 ,看图:
主从Reactor多线程模式工作流程如下
- Reactor 主线程 MainReactor 对象通过 Select 监听连接事件,收到事件后通过 Acceptor 接收,处理建立连接事件。
- Acceptor 处理建立连接事件后,MainReactor 将连接分配 Reactor 子线程给 SubReactor 进行处理。
- SubReactor 将连接加入连接队列进行监听,并创建一个 Handler 用于处理各种连接事件,
- 当有新的事件发生时,SubReactor 会调用连接对应的 Handler 处理
- Handler 通过 Read 读取数据后,会分发给后面的 Worker 线程池进行业务处理
- Worker 线程池会分配独立的线程完成真正的业务处理,将响应结果发给 Handler 进行处理
- Handler 收到响应结果后通过 Send 将响应结果返回给 Client
这种模式的优点比较明显,Reactor主线程只需要接收新连接,子线程完成后续的业务处理,Reactor的压力得到了分担。这种模型在许多项目中广泛使用,包括 Nginx 主从 Reactor 多进程模型,Memcached 主从多线程,Netty 主从多线程模型的支持。
Netty的线程模型
Netty是基于主从Reactor多线程模型实现,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果,Netty线程模型如下图:
Netty线程模型中有两个Group,分别是Boss Group和Worker Group,的BossGroup 用于Accetpt连接建立事件并分发请求,workerGroup用于处理I/O读写事件和业务逻辑。其中关键组件如下
- Channel
Netty网络通信的组件,能够用于执行网络IO操作。 - Selector
Netty基于Selector对象实现I/O多路复用,即:一个线程可以监听多个连接的Channel事件, 当channel向Selector注册 后,Selector 就可以自动不断地查询(select) 注册的Channel是否有已就绪的I/O事件(例如可读, 可写, 网络连接完成等)。 - NioEventLoop
NioEventLoop表示一个不断循环的执行处理任务的线程, 每个NioEventLoop 都有一个 selector。用于监听注册的Channel的IO事件。
NioEventLoop中维护一个线程和任务队列,支持异步提交任务,线程启动时会调用NioEventLoop的run方法,执行IO和非IO任务。 其中IO任务由prossSelectedKeys触发,非IO任务由runAlltasks触发。 - NioEventLoopGroup
NioEventLoopGroup用来管理EventLoop,可以理解为线程组,内部维护了一组线程
Netty线程模型工作流程如下:
- Boss Group里面的NioEventLoop会轮询accept事件,遇到有新的连接,就生成NioSocketChannel,并把这个Channel注册到Worker Group的Selector上
- Worker Group轮询read和write事件,在可读或者可写条件满足时,就进行处理
Worker Group和Boss Group都是通过里面的NioEventLoop来操作的。NioEventLoop中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用NioEventLoop的run方法
每个Boss NioEventLoop循环执行的步骤有3步
- 轮询检查accept事件
- 处理accept事件,与client建立连接, 生成NioSocketChannel, 并将其注册到某个 Worker NIOEventLoop上的 selector ,这样Channel的IO事件就交给WorkGroup去监听了。
- 最后执行一个runAllTasks方法,用于处理任务队列中的任务
每个 Worker NIOEventLoop 循环执行的步骤:
- 轮询read, write 事件
- 处理 I/O 事件, 即 read, write 事件, 再对应NioSocketChannel 处理
- 最后执行一个runAllTasks方法,用于处理任务队列中的任务
总结
本篇文件的内容主要介绍了一下Netty,以及三种 Reactor 线程模型,以及Netty的线程模型,Netty的线程模型主要是基于主从Reactor多线程模型升级,在下一章节我们来实战Netty。