【Netty源码分析摘录】(二)Netty源码分析系列之Reactor线程模型


系列文章:
【Netty源码分析摘录】(一)如何从BIO演进到NIO,再到Netty
【Netty源码分析摘录】(二)Netty源码分析系列之Reactor线程模型
【Netty源码分析摘录】(三)服务端Channel初始化
【Netty源码分析摘录】(四)服务端Channel注册
【Netty源码分析摘录】(五)服务端Channel的端口绑定
【Netty源码分析摘录】(六)NioEventLoop的创建与启动
【Netty源码分析摘录】(七)NioEventLoop的执行流程 & 空轮询bug
【Netty源码分析摘录】(八)新连接的接入
【Netty源码分析摘录】(九)backlog(最大连接数)与TCP三次握手之间不得不说的事

前言

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

1. BIO线程模型

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

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

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

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

那么怎么办呢?这个时候NIO出现了,NIO是非阻塞IO。对于NIO而言,服务端和客户端之间的读写数据,不再是阻塞的了。基于NIO实现的网络框架,它们底层的IO模型通常是基于Reactor模型来实现的。

2. Reactor模型

Reactor模型又可以分为三种:单线程模型、多线程模型、主从多线程模型。

2.1 Reactor单线程模型

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

Reactor单线程使用的是异步非阻塞IO,所有的读写操作都是非阻塞的,因此理论上一个线程可以完成所有的工作。当一个新连接来接入时,通过Acceptor类可以进行TCP的连接,当TCP连接创建完成后,可以通过Dispatcher类将对应的请求数据(即ByteBuffer)派发到指定的Handler上进行编解码操作,最后再通过该线程将数据发送给客户端。

对于一个并发量较小的场景,可以使用单线程模型来处理。但是当并发较高时,单线程就无法满足了。理由如下:

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

2.2 Reactor多线程模型

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

示意图如下:

在这里插入图片描述
在Reactor多线程模型中,由一个单独的NIO线程来充当Acceptor的角色,它负责监听服务端的端口,并接收客户端的连接。然后由一个线程池来处理数据的读写、编解码等操作。线程池可以采用Java中的线程池,它有一个任务队列和多个NIO线程,因此一个NIO线程可以同时处理多个连接,但是一个连接只属于一个NIO线程。

Reactor多线程完美的解决了单线程存在的问题,它也几乎能满足大部分应用场景。但是由于它只使用一个NIO线程来负责处理新连接的接入,因此在特殊场景下,例如新连接的创建,服务端需要进行安全认证等操作,由于认证可能会耗时较长,这个时候再使用一个线程来负责处理百万连接,显然无法满足要求,这最终会成为系统的性能瓶颈。

2.3 Reactor主从多线程模型

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

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

一个从线程池内的线程负责多个channel的IO操作,单个从线程内,是同步的,多个从线程之间是异步的。

例如从线程A负责 c1,c2 ,从线程B负责c3,c4,假设 c1、c2同时产生消息,那么A要依次处理c1,c2的消息,如果c1消息处理的慢, 则c2就要受影响,但是不影响B处理消息。

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

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

Netty里对应mainReactor的角色叫做“Boss”,而对应subReactor的角色叫做”Worker”。Boss负责分配请求,Worker负责执行,好像也很贴切!

以TCP的Server端为例,Netty 3.7中,这两个对应的实现类分别为NioServerBoss和NioWorker(Server和Client的Worker没有区别,因为建立连接之后,双方就是对等的进行传输了)。

在4.0之后,区分NioBoss和NioWorker的做法稍微繁琐了点,干脆就将这些合并成了NioEventLoop,从此这两个角色就不做区分了。

3.1 在Netty中使用Reactor单线程模型

在这里插入图片描述
ServerBootstrap.group()接收一个参数,说明仅使用一个线程池,接收线程池和IO操作线程池均复用该线程池,且线程池定义1个线程上限,因此是Reactor单线程模型

3.2 在Netty中使用Reactor多线程模型

在这里插入图片描述
ServerBootstrap.group()接收一个参数,说明仅使用一个线程池,接收线程池和IO操作线程池均复用该线程池,且线程池定义未定义线程上限,系统会默认提供cpu*2个线程数作为上限,因此是Reactor多线程模型。

当然可以通过明文指定线程数,只要你指定大于1的值均可以认为是多线程模型。

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

在这里插入图片描述

注意ServerBootstrap.group()接收2个参数,第一个是主线程池,第二个是从线程池。

下面我们来看Netty的多线程部分。一旦对应的Boss或者Worker启动,就会分配给它们一个线程去一直执行。对应的概念为BossPool和WorkerPool。对于每个服务器端通道NioServerSocketChannel,Boss的Reactor有一个线程,而Worker的线程数由Worker线程池大小决定,但是默认最大不会超过CPU核数*2,当然,这个参数可以通过NioServerSocketChannelFactory构造函数的参数来设置。

最后我们比较关心一个问题,我们之前ChannlePipeline中的ChannleHandler是在哪个线程执行的呢?答案是在Worker线程里执行的,并且会阻塞Worker的EventLoop(一个EventLoop和一个worker线程进行绑定,一对一的关系)。(这里的阻塞是指没有消息时,线程会阻塞,当然不是一直阻塞,可以带超时退出功能)例如,在NioWorker中,读取消息完毕之后,会触发MessageReceived事件,这会使得Pipeline中的handler都得到执行。

4. 总结

本文主要介绍了BIO的IO模型,以及Reactor的三种IO模型,最后通过代码,简单演示了在Netty如何使用Reactor三种线程模型。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值