Netty 采用了 Reactor 线程模型的设计。

什么是 Reactor#

============

Wikipedia 的定义是:

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.

从上面的定义可以看出有几个重点:

  1. 事件驱动

  2. 能处理一个或多个输入源

  3. 多路复用、分发事件给对应的处理器

Reactor 线程模型有几个角色:

  1. Reactor:负责响应事件,将事件分发给绑定了该事件的 Handler 处理;

  2. Handler:事件处理器,绑定了某类事件,负责对事件进行处理;

  3. Acceptor:Handler 的一种,绑定了连接事件。当客户端发起连接请求时,Reactor 会将 accept 事件分发给 Acceptor 处理。

简单来说,其核心原理是 Reactor 负责监听事件,在监听到事件之后,分发给相关线程的处理器进行处理。

为什么用 Reactor#

=============

我们先来看看传统阻塞 I/O 模型的缺点

  1. 每个连接都需要独立线程处理,当并发数大时,创建线程数多,占用资源

  2. 采用阻塞 I/O 模型,连接建立后,若当前线程没有数据可读,线程会阻塞在读操作上,造成资源浪费

针对传统阻塞 I/O 模型的两个问题,可以采用如下的方案

  1. 基于池化思想,避免为每个连接创建线程,连接完成后将业务处理交给线程池处理

  2. 基于 I/O 复用模型,多个连接共用同一个阻塞对象,不用等待所有的连接。遍历到有新数据可以处理时,操作系统会通知程序,线程跳出阻塞状态,进行业务逻辑处理

Reactor 线程模型的思想就是 线程池 和 I/O复用 的结合。

为了帮助你更好地了解 Netty 线程模型的设计理念,我们将从最基础的单 Reactor 单线程模型开始介绍,然后逐步增加模型的复杂度,最终到 Netty 目前使用的非常成熟的线程模型设计。

1. 单 Reactor 单线程#

==================

Reactor 对象监听客户端请求事件,收到事件后通过进行分发。

  • 如果是建立连接事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接建立之后的业务请求。

  • 如果是读写事件,则 Reactor 会将事件分发对应的 Handler 来处理,由单线程调用 Handler 对象来完成读取数据、业务处理、发送响应的完整流程。

具体情况如下图所示:

从零开始实现简单 RPC 框架 6:网络通信之 Netty

单 Reactor 单线程的优点就是:线程模型简单,没有引入多线程,自然也就没有多线程并发和竞争的问题。

但其缺点也非常明显,那就是性能瓶颈问题,一个线程只能跑在一个 CPU 上,能处理的连接数是有限的,无法完全发挥多核 CPU 的优势。一旦某个业务逻辑耗时较长,这唯一的线程就会卡在上面,无法处理其他连接的请求,程序进入假死的状态,可用性也就降低了。正是由于这种限制,一般只会在客户端使用这种线程模型。

2. 单 Reactor 多线程#

==================

其流程跟 “单 Reactor 单线程” 的流程差不多,也是 Acceptor 处理连接事件,Handler 处理读写事件。

唯一的区别就是:Handler 处理请求的时候,使用的是 线程池 来处理。

从零开始实现简单 RPC 框架 6:网络通信之 Netty

很明显,单 Reactor 多线程的模型可以充分利用多核 CPU 的处理能力,提高整个系统的吞吐量,但引入多线程模型就要考虑线程并发、数据共享等问题。

在这个模型中,只有一个线程来处理 Reactor 监听到的所有 I/O 事件,其中就包括连接建立事件以及读写事件,当连接数不断增大的时候,这个唯一的 Reactor 线程也会遇到瓶颈。

3. 主从 Reactor 多线程#

===================

为了解决单 Reactor 多线程模型中的问题,我们可以引入多个 Reactor。

  • Reactor 主线程接收建立连接事件,然后给 Acceptor 处理。网络连接建立之后,主 Reactor 会将连接分配给 子 Reactor 进行后续监听。

  • 子 Reactor 分配到连接之后,负责监听该连接上的读写事件。读写事件到来时分发给 Worker 线程池的 Handler 处理。

4. Netty 线程模型#

===============

Netty 同时支持上述几种线程模式,Netty 针对服务器端的设计是在主从 Reactor 多线程模型的基础上进行的修改,如下图所示:

从零开始实现简单 RPC 框架 6:网络通信之 Netty

Netty 抽象出两组线程池:BossGroup 专门用于接收客户端的连接,WorkerGroup 专门用于网络的读写。

BossGroup 里的线程 会监听连接事件,与客户端建立网络连接后,生成相应的 NioSocketChannel 对象,表示一条网络连接。之后会将 NioSocketChannel 注册到 WorkerGroup 中某个线程上。

WorkerGroup 里的线程会监听对应连接上的读写事件,当监听到读写事件的时候,会通过 Pipeline 添加的多个处理器进行处理,每个处理器中都可以包含一定的逻辑,例如编解码、心跳、业务逻辑等。

Netty 的核心组件#

============

介绍完 Netty 优秀的线程模型设计,接下来开始介绍 Netty 的核心组件。

1. EventLoopGroup / EventLoop#

===============================

在前面介绍 Netty 线程模型的时候,提到 BossGroup 和 WorkerGroup,他们就是 EventLoopGroup,一个 EventLoopGroup 当中会包含一个或多个 EventLoop,EventLoopGroup 提供 next 接口,可以从一组 EventLoop 里面按照一定规则获取其中一个 EventLoop 来处理任务。EventLoop 从表面上看是一个不断循环的线程

EventLoop 最常用的实现类是:NioEventLoop,一个 NioEventLoop 包含了一个 Selector 对象, 可以支持多个 Channel 注册在其上,该 NioEventLoop 可以同时服务多个 Channel,每个 Channel 只能与一个 NioEventLoop 绑定,这样就实现了线程与 Channel 之间的关联。

EventLoop 并不是一个纯粹的 I/O 线程,它除了负责 I/O 的读写之外,还兼顾处理以下两类任务:

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-LhfsUzVd-1711168779714)]

最后

[外链图片转存中…(img-7mKCgZ9t-1711168779715)]

[外链图片转存中…(img-pp9gI6a5-1711168779715)]

[外链图片转存中…(img-RUe6dX5L-1711168779716)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐