掌握Netty笔记

2022-06-18 架构

本篇内容主要整理了 Netty 框架底层原理、细节实现、实际操作等相关的内容

优点

Netty 是一个基于 NIO 的客户端/服务器编程框架,提供异步的、事件驱动的编程框架。所有 IO 操作都是异步非阻塞的,通过Future-Listener 机制,用户可以方便地主动获取或者通过通知机制获得 IO 操作结果。

与 JDK 原生 NIO 相比,Netty 提供了相对十分简单易用的 API,既支持异步服务也支持阻塞 IO 的服务,优点总结:

  • 封装了简单易用的 API 降低了开发门槛
  • 支持多种编解码功能,支持多种主流协议
  • 定制能力强,可以通过 ChannelHandler 对通信框架进行灵活扩展
  • 性能高,社区成熟迭代快

高并发 IO 基础原理

整理系统调用基础原理。

Linux 底层相关

Linux 中一切都看作是文件,包括普通文件,目录文件,字符设备文件(如键盘,鼠标…),块设备文件(如硬盘,光驱…),套接字等等。文件描述符,当应用程序请求内核打开/新建一个文件时,内核会返回一个文件描述符 File descriptor 用于对应这个打开/新建的文件,fd 本质上就是一个非负整数,是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。

在 Linux 系统中通过 select/epoll 系统调用实现一个进程监视多个 fd ,当某个 fd 就绪(一般是内核缓冲区可读/可写)内核就把就绪状态返回给应用程序,应用程序根据就绪的状态进行相应的 IO 系统调用。

目前支持IO多路复用的系统调用有 select、epoll 等。

select 系统调用几乎在所有的操作系统上都有支持,具有良好的跨平台特性。

epoll 是在 Linux 2.6 内核中提出的,是 select 系统调用的 Linux 增强版本。

IO 调用原理

用户程序进行 IO 的读写实际是依赖于底层的 read & write 两大系统调用,其中 read 系统调用并不是直接把数据从物理设备读取到内存中,write 系统调用也不是直接把数据写入到物理设备,两种操作都会涉及缓冲区。用户程序在其进程缓冲区内操作数据,通过 read & write 系统调用与操作系统内核缓冲区交互。操作系统内核来完成数据在内核缓冲区和物理设备(如磁盘)之间的交换。

主要的 IO 模型

整理四种主要的 IO 模型

IO模型示意图

同步阻塞 IO(Blocking IO)

  • 用户程序发起 read 调用后线程进入阻塞挂起状态
  • 内核收到请求后等待数据(从磁盘、网卡)全部进入内核缓冲区后,将数据复制到用户缓冲区,然后返回结果
  • 用户程序恢复运行,解析数据

同步阻塞IO示意图

优点:开发简单,阻塞期间不占用 CPU 资源;缺点:每个连接都需占用独立的线程浪费资源,线程上下文切换开销很大。不适合高并发场景。

同步非阻塞 IO(Non-blocking IO)

Linux 的 socket 连接默认是同步阻塞,也可以指定为同步非阻塞。

  • 用户程序发起同步非阻塞的 socket read 调用后线程进入阻塞挂起状态
    • 情况1:内核缓冲区数据没有准备好,立即返回调用失败;此时用户程序应尝试重新发起调用
    • 情况2:内核缓冲区数据已处理好,将数据复制到用户缓冲区,然后返回结果
  • 用户程序恢复运行,解析数据

应用程序需要发起轮询 IO 系统调用,直到完成 IO 系统调用为止。

同步非阻塞IO示意图

优点:每次发起的 IO 系统调用在内核等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。 同步非阻塞IO的缺点:不断地轮询内核,这将占用大量的 CPU 时间,导致效率低下。不适合高并发场景。

IO 多路复用(IO Multiplexing)

IO 多路复用通过 select/epoll 系统调用实现单个应用程序的线程不断地轮询成百上千的 socket 连接,当某个或者某些socket网络连接有 IO 就绪的状态,就返回对应的可以执行的读写操作。发起一个 IO 多路复用的 read 系统调用流程如下:

  • 将需要 read 操作的 socket fd 注册到 select/epoll 选择器中,如 java.nio.channels.Selector
  • 调用选择器的查询方法,内核会返回一个就绪的 fd 列表。
  • 用户线程获得已就绪状态的 fd 发起 read 系统调用,用户线程阻塞,内核将数据从内核缓冲区复制到用户缓冲区
  • 复制完成后内核返回结果,用户线程解除阻塞

和 NIO 模型相似,多路复用 IO 是通过 select/epoll 轮询查找已就绪的 fd。注册在选择器上的 socket 连接一般都设置成为同步非阻塞模型

IO多路复用示意图

优点:使用 select/epoll 时一个选择器查询线程可以同时处理成千上万个连接(Connection),系统不必创建大量的线程。Java 的 NIO 就是使用的 IO 多路复用模型。

缺点:本质上 select/epoll 系统调用是阻塞式的,属于同步IO,都需要在读写事件就绪后由系统调用本身负责进行读写,也就是说这个读写过程是阻塞的。

如何彻底地解除线程的阻塞,就必须使用异步 IO 模型。

异步IO(Asynchronous IO)

用户线程通过系统调用向内核注册某个 IO 操作,内核在整个 IO 操作(包括数据准备、数据复制)完成后,通知用户程序执行后续的业务操作。

在整个内核的数据处理过程中,包括内核将数据从网络物理设备(网卡)读取到内核缓冲区、将内核缓冲区的数据复制到用户缓冲区,用户程序都不需要阻塞。

  • 用户线程发起了异步 IO 类型的 read 系统调用,立刻就可以开始去做其他的事,用户线程不阻塞
  • 内核开始准备数据,等数据准备完成后把数据从内核缓冲区复制到用户缓冲区(用户空间的内存)
  • 内核会给用户线程发送一个信号(Signal),或者回调用户线程注册的回调接口,通知用户线程 read 操作完成了
  • 用户线程读取用户缓冲区的数据,完成后续的业务操作

异步IO示意图

特点:在内核等待数据和复制数据的两个阶段,用户线程都不是阻塞的。用户线程需要接收内核的IO操作完成的事件,或者用户线程需要注册一个IO操作完成的回调函数。正因为如此,异步IO有的时候也被称为信号驱动IO。

缺点:应用程序仅需要进行事件的注册与接收,其余的工作都留给了操作系统内核,对内核实现要求较高。

优点:理论上来说异步 IO 是真正的异步输入输出,它的吞吐量高于 IO 多路复用模型的吞吐量。

目前 Windows 通过 IOCP 实现了真正的异步 IO,而在 Linux 系统下,异步 IO 模型在 2.6 版本才引入,目前并不完善,在性能上没有明显的优势。Linux 系统大多数的高并发服务器端的程序还是采用 IO 多路复用模型。

JDK NIO

三大组件

  • 通道 Channel
  • 缓冲区 Buffer
  • 选择器 Selector

Reactor 反应器模式

应用实例:Nginx,Redis,Netty

反应器模式由Reactor反应器线程、Handlers处理器两大角色组成:

  • Reactor反应器线程的职责:负责响应IO事件,并且分发到Handlers处理器。

  • Handlers处理器的职责:非阻塞的执行业务处理逻辑。

Search

    Post Directory