从零开始实现简单 RPC 框架 6:网络通信之 Netty,最新阿里+头条+腾讯大厂Java笔试真题
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)[外链图片转存中…(img-LhfsUzVd-1711168779714)][外链图片转存中…(img-7mKCgZ9t-1711168779715)][外链图片转存中…(img-pp9gI6a5-1711168779715)][外链图片转存中…(img-RUe6dX5L-1711168779716)]本文已被CODING开源项
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.
从上面的定义可以看出有几个重点:
-
事件驱动
-
能处理一个或多个输入源
-
多路复用、分发事件给对应的处理器
Reactor 线程模型有几个角色:
-
Reactor:负责响应事件,将事件分发给绑定了该事件的 Handler 处理;
-
Handler:事件处理器,绑定了某类事件,负责对事件进行处理;
-
Acceptor:Handler 的一种,绑定了连接事件。当客户端发起连接请求时,Reactor 会将 accept 事件分发给 Acceptor 处理。
简单来说,其核心原理是 Reactor 负责监听事件,在监听到事件之后,分发给相关线程的处理器进行处理。
为什么用 Reactor#
=============
我们先来看看传统阻塞 I/O 模型的缺点:
-
每个连接都需要独立线程处理,当并发数大时,创建线程数多,占用资源
-
采用阻塞 I/O 模型,连接建立后,若当前线程没有数据可读,线程会阻塞在读操作上,造成资源浪费
针对传统阻塞 I/O 模型的两个问题,可以采用如下的方案
-
基于池化思想,避免为每个连接创建线程,连接完成后将业务处理交给线程池处理
-
基于 I/O 复用模型,多个连接共用同一个阻塞对象,不用等待所有的连接。遍历到有新数据可以处理时,操作系统会通知程序,线程跳出阻塞状态,进行业务逻辑处理
Reactor 线程模型的思想就是 线程池 和 I/O复用 的结合。
为了帮助你更好地了解 Netty 线程模型的设计理念,我们将从最基础的单 Reactor 单线程模型开始介绍,然后逐步增加模型的复杂度,最终到 Netty 目前使用的非常成熟的线程模型设计。
1. 单 Reactor 单线程#
==================
Reactor 对象监听客户端请求事件,收到事件后通过进行分发。
-
如果是建立连接事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接建立之后的业务请求。
-
如果是读写事件,则 Reactor 会将事件分发对应的 Handler 来处理,由单线程调用 Handler 对象来完成读取数据、业务处理、发送响应的完整流程。
具体情况如下图所示:
单 Reactor 单线程的优点就是:线程模型简单,没有引入多线程,自然也就没有多线程并发和竞争的问题。
但其缺点也非常明显,那就是性能瓶颈问题,一个线程只能跑在一个 CPU 上,能处理的连接数是有限的,无法完全发挥多核 CPU 的优势。一旦某个业务逻辑耗时较长,这唯一的线程就会卡在上面,无法处理其他连接的请求,程序进入假死的状态,可用性也就降低了。正是由于这种限制,一般只会在客户端使用这种线程模型。
2. 单 Reactor 多线程#
==================
其流程跟 “单 Reactor 单线程” 的流程差不多,也是 Acceptor 处理连接事件,Handler 处理读写事件。
唯一的区别就是:Handler 处理请求的时候,使用的是 线程池 来处理。
很明显,单 Reactor 多线程的模型可以充分利用多核 CPU 的处理能力,提高整个系统的吞吐量,但引入多线程模型就要考虑线程并发、数据共享等问题。
在这个模型中,只有一个线程来处理 Reactor 监听到的所有 I/O 事件,其中就包括连接建立事件以及读写事件,当连接数不断增大的时候,这个唯一的 Reactor 线程也会遇到瓶颈。
3. 主从 Reactor 多线程#
===================
为了解决单 Reactor 多线程模型中的问题,我们可以引入多个 Reactor。
-
Reactor 主线程接收建立连接事件,然后给 Acceptor 处理。网络连接建立之后,主 Reactor 会将连接分配给 子 Reactor 进行后续监听。
-
子 Reactor 分配到连接之后,负责监听该连接上的读写事件。读写事件到来时分发给 Worker 线程池的 Handler 处理。
4. Netty 线程模型#
===============
Netty 同时支持上述几种线程模式,Netty 针对服务器端的设计是在主从 Reactor 多线程模型的基础上进行的修改,如下图所示:
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开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
最后
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-LhfsUzVd-1711168779714)]
最后
[外链图片转存中…(img-7mKCgZ9t-1711168779715)]
[外链图片转存中…(img-pp9gI6a5-1711168779715)]
[外链图片转存中…(img-RUe6dX5L-1711168779716)]
更多推荐
所有评论(0)