Netty源码分析系列之Reactor线程模型

1,667 阅读7分钟

扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,阅读更多Spring源码分析Java并发编程文章。

微信公众号

对于网络编程而言,一方面需要保证基本功能的正确性,另一方面还需要保证程序的高性能。而网络程序高性能的主题之一就是网络IO,不同的IO模型,对程序的性能影响是非常明显的。

BIO线程模型

对于传统的网络框架而言,服务端通常采用的是BIO的通信模型。对于BIO通信模型,它通常使用一个专门的线程来负责接收网络连接,然后再为每一个连接单独创建一个线程来进行数据的读写、编解码、业务处理等操作。其IO模型可以用如下图表示。

BIO线程模型

显然,BIO的通信模型的优缺点很明显。优点就是程序的代码简单,复杂度低,开发人员容易上手。缺点是,对于每一个客户端连接,服务端都需要为其创建一个线程来处理数据,当并发较高时,就会创建很多线程,这对服务端而言简直就是灾难。因为创建太多线程后,系统资源会占用较高,而且CPU在多个线程之间进行切换时,频繁的切换上下文,会严重影响服务的性能。

既然线程创建太多了,那我们是不是可以使用线程池来解决问题呢?当一个新连接创建好以后,我们不再为其单独创建一个连接,而是将其交由线程池来处理,那这种方案是否可行呢?

答案是不行。为什么呢?线程池在这里只能解决线程无限增长的问题,但是在进行读写数据时,由于read操作和write操作都是阻塞的,在这段期间,线程会挂起,什么事情也干不了。当多个客户端来连接时,由于线程池中的线程,都阻塞在前面连接的读写数据操作上了,此时新来的连接,只能等待,所以对于BIO而言,使用线程池最终还是无法解决高并发的问题。

那么怎么办呢?这个时候NIO出现了,NIO是非阻塞IO。对于NIO而言,服务端和客户端之间的读写数据,不再是阻塞的了。基于NIO实现的网络框架,它们底层的IO模型通常是基于Reactor模型来实现的。Reactor模型又可以分为三种:单线程模型、多线程模型、主从多线程模型。

Reactor单线程模型

Reactor单线程模型中,只有一个线程。这个线程既负责客户端的接入,还负责数据的读写、编解码、业务逻辑处理等工作。IO模型示意图如下。

Reactor单线程模型

Reactor单线程使用的是异步非阻塞IO,所有的读写操作都是非阻塞的,因此理论上一个线程可以完成所有的工作。当一个新连接来接入时,通过Acceptor类可以进行TCP的连接,当TCP连接创建完成后,可以通过Dispatcher类将对应的请求数据(即ByteBuffer)派发到指定的Handler上进行编解码操作,最后再通过该线程将数据发送给客户端。 对于一个并发量较小的场景,可以使用单线程模型来处理。但是当并发较高时,单线程就无法满足了。理由如下:

  1. 一个NIO线程显然无法支撑多个连接的接入,即便NIO线程的CPU负荷达到100%,也无法满足海量数据的编解码、读取和发送。
  2. 当CPU负载较高后,处理就会变慢,这样就会造成大量的客户端出现连接超时。当客户端发现连接超时后,又会尝试进行重新请求,这样更加会加重NIO线程所在的CPU的负载,最终就会导致系统负载高,处理慢,成为系统的性能瓶颈。
  3. 可靠性低。一旦NIO线程因为处理数据中出现异常,或者进入到死循环,那将导致整个系统不可用。

Reactor多线程模型

为了解决Reactor单线程模型的问题,Reactor多线程模型出现了。在Reactor多线程模型中,由一个NIO线程来负责客户端的接入,连接创建完成后,再由一组线程来处理数据的读写、编解码、业务处理等操作。示意图如下。

Reactor多线程

在Reactor多线程模型中,由一个单独的NIO线程来充当Acceptor的角色,它负责监听服务端的端口,并接收客户端的连接。然后由一个线程池来处理数据的读写、编解码等操作。线程池可以采用Java中的线程池,它有一个任务队列和多个NIO线程,因此一个NIO线程可以同时处理多个连接,但是一个连接只属于一个NIO线程。 Reactor多线程完美的解决了单线程存在的问题,它也几乎能满足大部分应用场景。但是由于它只使用一个NIO线程来负责处理新连接的接入,因此在特殊场景下,例如新连接的创建,服务端需要进行安全认证等操作,由于认证可能会耗时较长,这个时候再使用一个线程来负责处理百万连接,显然无法满足要求,这最终会成为系统的性能瓶颈。

Reactor主从多线程模型

Reactor主从多线程模型则解决了多线程模型的缺点,主从多线程模型中由一组NIO线程来负责处理新连接的接入,另外一组NIO线程来处理IO读写、编解码、业务逻辑处理等操作。因此它是两个线程池,负责新连接接入的线程池称之为主线程池,负责数据读写、编解码操作的线程池称之为从线程池。其示意图如下:

Reactor主从多线程

当一个客户端来连接服务端时,主线程池会从线程池中选择出一个NIO线程,来充当Acceptor的角色,负责新连接的接入。当连接创建完成后,再将其通过Dispatcher派发到主线程池,由主线程来进行安全认证等操作,当安全认证等操作完成后,会将这个新连接绑定到从线程池的一个NIO线程上,后续则由这个NIO线程来进行数据的读写、编解码等操作。

Netty对Reactor三种线程模型的支持

Netty作为一款高性能的网络框架,它底层的网络IO模型采用了Reactor线程模型。在Netty中,它能很方便的在Reactor三种IO模型中进行切换。下面就来看看它是如何进行切换的。

  1. 在Netty中使用Reactor单线程模型

Netty对Reactor单线程模型的支持

  1. 在Netty中使用Reactor多线程模型

Netty对Reactor多线程模型的支持

  1. 在Netty中使用Reactor主从多线程模型

Netty对Reactor主从多线程模型的支持

总结

  • 本文主要介绍了BIO的IO模型,以及Reactor的三种IO模型,最后通过代码,简单演示了在Netty如何使用Reactor三种线程模型。关于BIO、NIO更详细的介绍,以及BIO、NIO和Netty的代码示例,可以参考这篇文章:如何从BIO演进到NIO,再到Netty

参考

  • 本文参考了李林锋所编写的《Netty权威指南》、《Netty进阶之路-跟着案例学Netty》、《分布式服务框架原理与实践》。
  • 文中的图片来源于极客时间《Netty源码剖析与实战》

微信公众号