当前位置 : 主页 > 网络编程 > 其它编程 >

Netty:入门(1)

来源:互联网 收集:自由互联 发布时间:2023-07-02
参考文章《超详细的Netty入门》《Netty框架学习之(一Netty框架简介》《Netty框架学习之(二&# 参考文章: 《超详细的 Netty 入门》 《Netty框架学习之(一):Netty框架简介》 《Netty框架学习之
参考文章《超详细的Netty入门》《Netty框架学习之(一Netty框架简介》《Netty框架学习之(二&#

参考文章:

《超详细的 Netty 入门》

《Netty框架学习之(一):Netty框架简介》

《Netty框架学习之(二):Netty组件简介》

《图解高性能网络架构:Reactor 和 Proactor》

相关文章:

《IO 模型与多路复用》

《Java NIO》

《性能优化:文件传输之DMA、零拷贝与直接IO》

        写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。

前言

        在此前的文章中,我们已经对NIO等内容做了基本介绍,但是java NIO类库和API繁杂,学习成本高,还需要熟悉Java多线程编程才能写出高质量的NIO程序。因此便有了封装好的网络编程框架Netty。(建议在阅读本文前先了解完《IO 模型与多路复用》)。

目录

前言

一、线程模型

        1、传统阻塞IO线程模

        2、Reactor模型

        2.1、单 Reactor 单进程 / 线程

        2.2、单 Reactor 多线程 / 多进程

        2.3、多 Reactor 多进程 / 线程

二、Netty的基本介绍

        1、Netty是什么

        2、Netty的运行步骤

        2.1、内部流程介绍

        2.2、样例展示

        2.3、核心组件


一、线程模型

        目前存在的线程模式:

  • 传统阻塞IO的服务模型
  • Reactor模型

        1、传统阻塞IO线程模

        这种模型采用阻塞IO获取输入的数据,每个连接都需要独立的线程来处理逻辑。存在的问题就是,当并发数很大时,就需要创建很多的线程,占用大量的资源。连接创建后,如果当前线程没有数据可读,该线程将会阻塞在读数据的方法上,造成线程资源浪费。

              

        2、Reactor模型

        与传统阻塞IO线程模对应的就是基于NIO封装而成的Reactor事件驱动模型。事实上,Reactor 模式也叫 Dispatcher 模式,即 I/O 多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程 / 线程。

        先来复习下NIO使用的多路复用技术,只需要一个线程就可以监控所有的连接,正如下图这样。

          

        Reactor模式主要由 Reactor 和处理资源池这两个核心部分组成,它俩负责的事情如下:

  • Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件;
  • 处理资源池负责处理事件,如 read -> 业务逻辑 -> send;

        Reactor模式是灵活多变的,可以应对不同的业务场景,灵活在于:

  • Reactor 的数量可以只有一个,也可以有多个;
  • 处理资源池可以是单个进程 / 线程,也可以是多个进程 /线程;

        于是Reactor模式就有以下实现

  • 单 Reactor 单进程 / 线程;
  • 单 Reactor 多线程 / 进程;
  • 多 Reactor 多进程 / 线程;

        2.1、单 Reactor 单进程 / 线程

        可以看到进程里有 Reactor、Acceptor、Handler 这三个对象:

  • Reactor 对象的作用是监听和分发事件;
  • Acceptor 对象的作用是获取连接;
  • Handler 对象的作用是处理业务;

        运行流程如下:

        (1)Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;         (2)如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;         (3)如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应; Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。

        单 Reactor 单进程的方案因为全部工作都在同一个进程内完成,所以实现起来比较简单,不需要考虑进程间通信,也不用担心多进程竞争。

        但是,这种方案存在 2 个缺点:

(1)因为只有一个进程,无法充分利用 多核 CPU 的性能; (2)Handler 对象在业务处理时,整个进程是无法处理其他连接的事件的,如果业务处理耗时比较长,那么就造成响应的延迟;

        2.2、单 Reactor 多线程 / 多进程

        如果要克服单 Reactor 单线程 / 进程方案的缺点,那么就需要引入多线程 / 多进程,这样就产生了单 Reactor 多线程 / 多进程的方案。

        (1)Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;         (2)如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;         (3)如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;

        与前一种模式不同之处在于:

        (1)Handler 对象不再负责业务处理,只负责数据的接收和发送,Handler 对象通过 read 读取到数据后,会将数据发给子线程里的 Processor 对象进行业务处理;         (2)子线程里的 Processor 对象就进行业务处理,处理完后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client;

        单Reator多线程的方案优势在于能够充分利用多核 CPU 的能,那既然引入多线程,那么自然就带来了多线程竞争资源的问题。         另外,因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方。

        2.3、多 Reactor 多进程 / 线程

        要解决单 Reactor的问题,就是将单 Reactor实现成多 Reactor,这样就产生了第 多 Reactor 多进程 / 线程的方案。

        (1)主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程;         (2)子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件。         (3)如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应。         (4)Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。

        多 Reactor 多线程的方案虽然看起来复杂的,但是实际实现时比单 Reactor 多线程的方案要简单的多,原因如下:

  • 主线程和子线程分工明确,主线程只负责接收新连接,子线程负责完成后续的业务处理。
  • 主线程和子线程的交互很简单,主线程只需要把新连接传给子线程,子线程无须返回数据,直接就可以在子线程将处理结果发送给客户端。

        Netty 正是采用了多Reactor 多线程的方案,而Nginx采用了多 Reactor 多进程的方案,不过方案与标准的多 Reactor 多进程有些差异。

        具体差异表现在主进程中仅仅用来初始化 socket,并没有创建 mainReactor 来 accept 连接,而是由子进程的 Reactor 来 accept 连接,通过锁来控制一次只有一个子进程进行 accept(防止出现惊群现象),子进程 accept 新连接后就放到自己的 Reactor 进行处理,不会再分配给其他子进程。

二、Netty的基本介绍

        1、Netty是什么

        Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

        Netty 对 JDK 自带的 NIO 的 API 进行了良好的封装,解决了上述问题。且Netty拥有高性能、 吞吐量更高,延迟更低,减少资源消耗,最小化不必要的内存复制等优点。Netty 现在都在用的是4.x,5.x版本已经废弃,Netty 4.x 需要JDK 6以上版本支持。

        2、Netty的运行步骤

        2.1、内部流程介绍

        Netty模型是基于主从Reactor多线程模型设计的。

        Netty有两组线程池,一个Boss Group,它专门负责客户端连接,另一个Work Group,专门负责网络读写;

  • Boss Group和Work Group的类型都是NIOEventLoopGroup;
  • NIOEventLoopGroup相当于一个事件循环组,这个组包含了多个事件循环,每一个循环都是NIOEventLoop;
  • NIOEventLoop表示一个不断循环执行处理任务的线程,每个NIOEventLoop都有一个Selector,用于监听绑定在其上的ocketChannel的网络通讯;
  • Boss Group下的每个NIOEventLoop的执行步骤有3步: 

        (1). 轮询accept连接事件;

        (2). 处理accept事件,与client建立连接,生成一个NioSocketChannel,并将其注册到某个work group下的NioEventLoop的selector上;

        (3). 处理任务队列的任务,即runAllTasks;

  • 每个Work Group下的NioEventLoop循环执行以下步骤:

        (1). 轮询read、write事件;

        (2). 处理read、write事件,在对应的NioSocketChannel处理;

        (3). 处理任务队列的任务,即runAllTasks;

  • 每个Work Group下的NioEventLoop在处理NioSocketChannel业务时,会使用pipeline(管道),管道中维护了很多 handler 处理器用来处理 channel 中的数据。

        2.2、样例展示

        Netty服务段代码样例

         Netty客户端代码样例

         运行流程如下

        2.3、核心组件

        Bootstrap:主要作用是配置整个 Netty 程序,串联各个组件,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类。

        Channel:数据的通道,这一点与java NIO一样,所有的数据流转都需要经过channel。         EventLoop:NioEventLoop 中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务。

        I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发。

        非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。

        NioEventLoopGroup:主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。

        每个EventLoopGroup里包括一个或多个EventLoop,每个EventLoop中维护一个Selector实例。

        ChannelFuture:Netty 框架中所有的 I/O 操作都为异步的,因此我们需要 ChannelFuture 的 addListener()注册一个 ChannelFutureListener 监听事件,当操作执行成功或者失败时,监听就会自动触发返回结果。         ChannelHandler:充当了所有处理入站和出站数据的逻辑容器。ChannelHandler 主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。         ChannelPipeline:为 ChannelHandler 链提供了容器,当 channel 创建时,就会被自动分配到它专属的 ChannelPipeline,这个关联是永久性的。

上一篇:tomcat启动时设定环境变量
下一篇:没有了
网友评论