diff --git a/Middleware.md b/Middleware.md index 561bbca..8e176bf 100644 --- a/Middleware.md +++ b/Middleware.md @@ -2558,6 +2558,163 @@ jute.maxbuffer # Netty +## Netty逻辑架构 + +![Netty逻辑架构](images/Middleware/Netty逻辑架构.png) + +Netty内部逻辑的流转: + +![Netty内部逻辑的流转](images/Middleware/Netty内部逻辑的流转.png) + +### 网络通信层 + +网络通信层的职责是**执行网络I/O的操作**,它支持多种网络协议和I/O模型的连接操作。当网络数据读取到内核缓冲区后,会触发各种网络事件,这些网络事件会分发给事件调度层进行处理。三个核心组件包括: + +- **BootStrap和ServerBootStrap** + + 主要负责整个Netty程序的启动、初始化、服务器连接等过程,它相当于一条主线,串联了Netty的其它核心组件。Bootstrap和ServerBootStrap十分相似,两者的区别在于: + + - Bootstrap可用于连接远端服务器,只绑定一个EventLoopGroup + - ServerBootStrap则用于服务端启动绑定本地端口,会绑定两个EventLoopGroup,通常称为Boss和Worker(Boss 会不停地接收新的连接,然后将连接分配给一个个Worker处理连接) + +- **Channel** + + 提供了基本的API用于网络I/O操作,如register、bind、connect、read、write、flush 等。Netty的Channel是以JDK NIO Channel为基础的,相比较于JDK NIO,Netty的Channel提供了更高层次的抽象,同时屏蔽了底层Socket的复杂性,赋予了Channel更加强大的功能,在使用Netty时基本不需要再与Java Socket类直接打交道。 + + Channel常用的实现类有: + + - NioServerSocketChannel:异步TCP服务端 + - NioSocketChannel:异步TCP客户端 + - OioServerSocketChannel:同步TCP服务端 + - OioSocketChannel:同步TCP客户端 + - NioDatagramChannel:异步UDP连接 + - OioDatagramChannel:同步UDP连接 + + Channel常见的状态事件回调有: + + - channelRegistered:Channel创建后被注册到EventLoop上 + - channelUnregistered:Channel创建后未注册或者从EventLoop取消注册 + - channelActive:Channel处于就绪状态,可以被读写 + - channelInactive:Channel处于非就绪状态 + - channelRead:Channel可以从远端读取到数据 + - channelReadComplete:Channel读取数据完成 + + + +### 事件调度层 + +事件调度层的职责是通过Reactor线程模型对各类事件进行聚合处理,通过Selector主循环线程集成多种事件(I/O事件、信号事件、定时事件等),实际的业务处理逻辑是交由服务编排层中相关的Handler完成。两个核心组件包括: + +- **EventLoopGroup、EventLoop** + + EventLoopGroup是Netty的核心处理引擎,本质是一个线程池,主要负责接收I/O请求,并分配线程执行处理请求。EventLoopGroup的实现类**NioEventLoopGroup**也是 Netty 中最被推荐使用的线程模型。是基于NIO模型开发的,可以把NioEventLoopGroup理解为一个线程池,每个线程负责处理多个Channel,而同一个Channel只会对应一个线程。 + + ![Netty事件调度层](images/Middleware/Netty事件调度层.png) + + - 一个 EventLoopGroup 往往包含一个或者多个 EventLoop。EventLoop 用于处理 Channel 生命周期内的所有 I/O 事件,如 accept、connect、read、write 等 I/O 事件 + - EventLoop 同一时间会与一个线程绑定,每个 EventLoop 负责处理多个 Channel + - 每新建一个 Channel,EventLoopGroup 会选择一个 EventLoop 与其绑定。该 Channel 在生命周期内都可以对 EventLoop 进行多次绑定和解绑 + + 其实EventLoopGroup是Netty Reactor线程模型的具体实现方式,Netty通过创建不同的EventLoopGroup参数配置,就可以支持Reactor的三种线程模型: + + - **单线程模型**:EventLoopGroup只包含一个EventLoop,Boss和Worker使用同一个EventLoopGroup + - **多线程模型**:EventLoopGroup包含多个EventLoop,Boss和Worker使用同一个EventLoopGroup + - **主从多线程模型**:EventLoopGroup包含多个EventLoop,Boss是主Reactor,Worker是从Reactor,它们分别使用不同的EventLoopGroup,主Reactor负责新的网络连接Channel创建,然后把Channel注册到从Reactor + + + +### 服务编排层 + +服务编排层的职责是负责组装各类服务,是Netty的核心处理链,用以实现网络事件的动态编排和有序传播。核心组件包括: + +- **ChannelPipeline** + + ChannelPipeline 是 Netty 的核心编排组件,负责组装各种 ChannelHandler,实际数据的编解码以及加工处理操作都是由 ChannelHandler 完成的。ChannelPipeline 可以理解为ChannelHandler 的实例列表——内部通过双向链表将不同的 ChannelHandler 链接在一起。当 I/O 读写事件触发时,ChannelPipeline 会依次调用 ChannelHandler 列表对 Channel 的数据进行拦截和处理。 + + ChannelPipeline 是线程安全的,因为每一个新的 Channel 都会对应绑定一个新的 ChannelPipeline。一个 ChannelPipeline 关联一个 EventLoop,一个 EventLoop 仅会绑定一个线程。 + + ChannelPipeline、ChannelHandler 都是高度可定制的组件。开发者可以通过这两个核心组件掌握对 Channel 数据操作的控制权。下面我们看一下 ChannelPipeline 的结构图: + + ![ChannelPipeline结构图](images/Middleware/ChannelPipeline结构图.png) + + ChannelPipeline中包含入站ChannelInboundHandler和出站 ChannelOutboundHandler两种处理器,结合客户端和服务端的数据收发流程: + + ![ClientServerChannelPipeline](images/Middleware/ClientServerChannelPipeline.png) + + + +- **ChannelHandler & ChannelHandlerContext** + + 数据的编解码工作以及其他转换工作实际都是通过 ChannelHandler 处理的。ChannelHandlerContext 用于保存 ChannelHandler 上下文,通过 ChannelHandlerContext 可以知道 ChannelPipeline 和 ChannelHandler 的关联关系。ChannelHandlerContext 可以实现 ChannelHandler 之间的交互,ChannelHandlerContext 包含了 ChannelHandler 生命周期的所有事件,如 connect、bind、read、flush、write、close 等。 + + ![ChannelHandler](images/Middleware/ChannelHandler.png) + + 每创建一个 Channel 都会绑定一个新的 ChannelPipeline,ChannelPipeline 中每加入一个 ChannelHandler 都会绑定一个 ChannelHandlerContext。 + + + +## 线程模型 + +### 单Reactor单线程 + + + +### 单Reactor多线程 + + + +### 主从Reactor多线程 + + + + + +## 核心设计 + +### 定时器TimerTask + + + +### 时间轮HashedWheelTimer + + + +### 无锁队列mpsc queue + + + +### FastThreadLocal + + + +### ByteBuf + + + +### 编解码协议 + +netty-codec模块主要负责编解码工作,通过编解码实现原始字节数据与业务实体对象之间的相互转化。Netty支持大多数业界主流协议的编解码器,如**HTTP、HTTP2、Redis、XML**等,为开发者节省了大量的精力。此外该模块提供了抽象的编解码类**ByteToMessageDecoder**和**MessageToByteEncoder**,通过继承这两个类可以轻松实现自定义的编解码逻辑。 + +![Netty协议](images/Middleware/Netty协议.png) + + + +### 拆包粘包 + +- 拆包/粘包的解决方案 + + - **消息长度固定**:每个数据报文都需要一个固定的长度。当接收方累计读取到固定长度的报文后,就认为已经获得一个完整的消息。当发送方的数据小于固定长度时,则需要空位补齐。消息定长法使用非常简单,但是缺点也非常明显,无法很好设定固定长度的值,如果长度太大会造成字节浪费,长度太小又会影响消息传输,所以在一般情况下消息定长法不会被采用。 + + - **特定分隔符** + + 既然接收方无法区分消息的边界,那么我们可以在每次发送报文的尾部加上特定分隔符,接收方就可以根据特殊分隔符进行消息拆分。 + + - **消息长度 + 消息内容** + + 消息长度 + 消息内容是项目开发中最常用的一种协议,如上展示了该协议的基本格式。消息头中存放消息的总长度,例如使用 4 字节的 int 值记录消息的长度,消息体实际的二进制的字节数据。接收方在解析数据时,首先读取消息头的长度字段 Len,然后紧接着读取长度为 Len 的字节数据,该数据即判定为一个完整的数据报文。 + + + ## Netty流程 从功能上,流程可以分为服务启动、建立连接、读取数据、业务处理、发送数据、关闭连接以及关闭服务。整体流程如下所示(图中没有包含关闭的部分): diff --git a/OS.md b/OS.md index 56e06cb..f7b6d21 100644 --- a/OS.md +++ b/OS.md @@ -15,6 +15,15 @@ Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Bloc ## 基础概念 +![IO请求流程](images/OS/IO请求流程.png) + +I/O请求可以分为两个阶段,分别为调用阶段和执行阶段。 + +- 第一个阶段为I/O调用阶段,即用户进程向内核发起系统调用 +- 第二个阶段为I/O执行阶段。此时,内核等待I/O请求处理完成返回。该阶段分为两个过程:首先等待数据就绪,并写入内核缓冲区;随后将内核缓冲区数据拷贝至用户态缓冲区 + + + **① 阻塞(Blocking)和非阻塞(Non-blocking)** 阻塞和非阻塞发生在内核等待数据可操作(可读或可写)时,指做事时是否需要等待应答。 @@ -31,7 +40,7 @@ Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Bloc -### 阻塞I/O +### 阻塞I/O(BIO) 阻塞IO情况下,当用户调用`read`后,用户线程会被阻塞,等内核数据准备好并且数据从内核缓冲区拷贝到用户态缓存区后`read`才会返回。阻塞分两个阶段: @@ -40,9 +49,11 @@ Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Bloc ![阻塞IO](images/OS/阻塞IO.png) +应用进程向内核发起 I/O 请求,发起调用的线程一直等待内核返回结果。一次完整的 I/O 请求称为BIO(Blocking IO,阻塞 I/O),所以 BIO 在实现异步操作时,只能使用多线程模型,一个请求对应一个线程。但是,线程的资源是有限且宝贵的,创建过多的线程会增加线程切换的开销。 + -### 非阻塞I/O +### 非阻塞I/O(NIO) 非阻塞的 `read` 请求在数据未准备好的情况下立即返回,可以继续往下执行,此时应用程序不断轮询内核,询问数据是否准备好,当数据没有准备好时,内核立即返回EWOULDBLOCK错误。直到数据准备好后,内核将数据拷贝到应用程序缓冲区,`read` 请求才获取到结果。 @@ -52,6 +63,8 @@ Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Bloc 注意,**这里最后一次 read 调用,获取数据的过程,是一个同步的过程,是需要等待的过程。这里的同步指的是内核态的数据拷贝到用户程序的缓存区这个过程。** +应用进程向内核发起 I/O 请求后不再会同步等待结果,而是会立即返回,通过轮询的方式获取请求结果。NIO 相比 BIO 虽然大幅提升了性能,但是轮询过程中大量的系统调用导致上下文切换开销很大。所以,单独使用非阻塞 I/O 时效率并不高,而且随着并发量的提升,非阻塞 I/O 会存在严重的性能浪费。 + ### I/O多路复用 @@ -60,12 +73,18 @@ Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Bloc ![IO-多路复用](images/OS/IO-多路复用.png) +多路复用实现了一个线程处理多个 I/O 句柄的操作。多路指的是多个数据通道,复用指的是使用一个或多个固定线程来处理每一个 Socket。select、poll、epoll 都是 I/O 多路复用的具体实现,线程一次 select 调用可以获取内核态中多个数据通道的数据状态。多路复用解决了同步阻塞 I/O 和同步非阻塞 I/O 的问题,是一种非常高效的 I/O 模型。 + -### 同步I/O +### 信号驱动I/O 无论 `read` 和 `send` 是 `阻塞I/O`,还是 `非阻塞I/O` 都是同步调用。因为在 `read` 调用时,内核将数据从内核空间拷贝到用户空间的过程都是需要等待的,即这个过程是同步的,如果内核实现的拷贝效率不高,`read` 调用就会在这个同步过程中等待比较长的时间。 +![信号驱动I/O](images/OS/信号驱动IO.png) + +信号驱动 I/O 并不常用,它是一种半异步的 I/O 模型。在使用信号驱动 I/O 时,当数据准备就绪后,内核通过发送一个 SIGIO 信号通知应用进程,应用进程就可以开始读取数据了。 + ### 异步I/O @@ -76,6 +95,8 @@ Linux/Unix常见IO模型:**阻塞(Blocking I/O)**、**非阻塞(Non-Bloc ![异步IO](images/OS/异步IO.png) +异步 I/O 最重要的一点是从内核缓冲区拷贝数据到用户态缓冲区的过程也是由系统异步完成,应用进程只需要在指定的数组中引用数据即可。异步 I/O 与信号驱动 I/O 这种半异步模式的主要区别:信号驱动 I/O 由内核通知何时可以开始一个 I/O 操作,而异步 I/O 由内核通知 I/O 操作何时已经完成。 + ## Reactor模式 diff --git a/images/Middleware/ChannelHandler.png b/images/Middleware/ChannelHandler.png new file mode 100644 index 0000000..aaa520c Binary files /dev/null and b/images/Middleware/ChannelHandler.png differ diff --git a/images/Middleware/ChannelPipeline结构图.png b/images/Middleware/ChannelPipeline结构图.png new file mode 100644 index 0000000..fe10342 Binary files /dev/null and b/images/Middleware/ChannelPipeline结构图.png differ diff --git a/images/Middleware/ClientServerChannelPipeline.png b/images/Middleware/ClientServerChannelPipeline.png new file mode 100644 index 0000000..f1e3f56 Binary files /dev/null and b/images/Middleware/ClientServerChannelPipeline.png differ diff --git a/images/Middleware/Netty事件调度层.png b/images/Middleware/Netty事件调度层.png new file mode 100644 index 0000000..aed23c1 Binary files /dev/null and b/images/Middleware/Netty事件调度层.png differ diff --git a/images/Middleware/Netty内部逻辑的流转.png b/images/Middleware/Netty内部逻辑的流转.png new file mode 100644 index 0000000..f5cd2fb Binary files /dev/null and b/images/Middleware/Netty内部逻辑的流转.png differ diff --git a/images/Middleware/Netty协议.png b/images/Middleware/Netty协议.png new file mode 100644 index 0000000..6bb3494 Binary files /dev/null and b/images/Middleware/Netty协议.png differ diff --git a/images/Middleware/Netty逻辑架构.png b/images/Middleware/Netty逻辑架构.png new file mode 100644 index 0000000..1ffc7a5 Binary files /dev/null and b/images/Middleware/Netty逻辑架构.png differ diff --git a/images/OS/IO请求流程.png b/images/OS/IO请求流程.png new file mode 100644 index 0000000..ad26634 Binary files /dev/null and b/images/OS/IO请求流程.png differ diff --git a/images/OS/信号驱动IO.png b/images/OS/信号驱动IO.png new file mode 100644 index 0000000..eab4e42 Binary files /dev/null and b/images/OS/信号驱动IO.png differ