Netty架构概述及其原理说明

为什么要用Netty架构

前面一篇文章写了java原生支持的NIO模型也是可以实现网络数据传输的,为什么还有Netty出现呢?

  1. NIO的类库和 API 繁杂,使用麻烦:需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
  2. 要熟悉 Java 多线程编程,因为 NIO 编程涉及到 Reactor 模式,你必须对多线程和网络编程非常熟悉
  3. 开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流
    的处理等等。
  4. NIO中有bug,至今未解决,比如Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%

Netty的优点

  1. 适用于各种传输类型的统一 API 阻塞和非阻塞 Socket
  2. 高性能、吞吐量更高:延迟更低;减少资源消耗
  3. 发现的 Bug 被及时修复

Netty版本

netty 版本分为 netty3.x 和 netty4.x、netty5.x,因为 Netty5 出现重大 bug,已经被官网废弃了,目前推荐使用的是 Netty4.x 的稳定版本

常见数据请求处理模式

在此之前,先介绍几个常用的数据请求的模式。 目前比较常见的模式有传统阻塞 I/O 服务模型,即一个请求一个线程,这个不再说。Reactor 模式:根据Reactor的数量又分为几种实现。

单 Reactor 单线程

原理图:

Netty架构概述及其原理说明

说明:

实现应用程序通过一个阻塞对象监听多路连接请求,Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发,如果是连接请求则由 Acceptor 通过 Accept 处理连接请求,如果不是,则由Handler 来响应

优缺点

优点:模型简单,没有多线程、进程通信、竞争的问题,全部都在一个线程中完成
缺点:性能问题,只有一个线程,无法完全发挥多核 CPU 的性能,线程意外终止,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障

单 Reactor 多线程

原理图

Netty架构概述及其原理说明

说明

前面跟上面单线程一样,也是select监控请求,然后收到请求,将其转发,如果是连接请求,则交给accept处理,如果不是,则交给handler处理,不同的是。这里handler只负责响应事件,不做具体的业务处理, 通过 read 读取数据后,会分发给后面的 worker 线程池的某个线程处理业务,worker 线程池会分配独立线程完成真正的业务,并将结果返回给 handler,然后通过 send 将结果返回给 client

优缺点

这里充分发挥了CPU多线程的优势,也可以同时处理很多业务,但是 reactor需要 处理所有的事件的监听和响应,并且是单线程,这里在高并发会出现瓶颈。

主从 Reactor 多线程

原理图

Netty架构概述及其原理说明

说明

跟上面不用的是,新增 MainReactor, MainReactor可以连接多个SubReactor,这就解决了一个Reactor性能瓶颈的问题。

  1. Reactor 主线程 MainReactor 对象通过 select 监听连接事件, 收到事件后,通过 Acceptor 处理连接事件,当 Acceptor 处理连接事件后,MainReactor 将连接分配给 SubReactor,
  2. subreactor 将连接加入到连接队列进行监听,并创建 handler 进行各种事件处理
  3. 当有新事件发生时, subreactor 就会调用对应的 handler 处理
  4. handler 通过 read 读取数据,分发给后面的 worker 线程处理
  5. worker 线程池分配独立的 worker 线程进行业务处理,并返回结果给client
优缺点

基本以上性能问题解决了,就是变成比较麻烦!

Netty

上面说了说了三种常用模式,Netty主要是基于主从 Reactors 多线程模型做了一定的改进。

原理图

Netty架构概述及其原理说明

说明
  1. Netty 抽象出两组线程池 BossGroup 专门负责接收客户端的连接, WorkerGroup 专门负责网络的读写
  2. BossGroup 和 WorkerGroup 类型都是 NioEventLoopGroup
  3. NioEventLoopGroup 相当于一个事件循环组, 这个组中含有多个事件循环 ,每一个事件循环是 NioEventLoop
  4. NioEventLoop 表示一个不断循环的执行处理任务的线程, 每个 NioEventLoop 都有一个 selector , 用于监听绑
    定在其上的 socket 的网络通讯
  5. NioEventLoopGroup 可以有多个线程, 即可以含有多个 NioEventLoop
  6. 每个 Boss NioEventLoop 循环执行的步骤有 3 步
    1、 轮询 accept 事件
    2、 处理 accept 事件 , 与 client 建立连接 , 生成 NioScocketChannel , 并将其注册到某个 worker NIOEventLoop 上
    的 selector
    3、 处理任务队列的任务 , 即 runAllTasks
  7. 每个 Worker NIOEventLoop 循环执行的步骤
    1、 轮询 read, write 事件
    2、 处理 i/o 事件, 即 read , write 事件,在对应 NioScocketChannel 处理
    3、处理任务队列的任务 , 即 runAllTasks
  8. 每个Worker NIOEventLoop 处理业务时,会使用pipeline(管道), pipeline 中包含了 channel , 即通过pipeline
    可以获取到对应通道, 管道中维护了很多的 处理器

简单来说就是BossGroup 负责请求接受,并将其注册到WorkerGroup ,然后WorkerGroup 中的select监听事件,有事件了就甩给下面的管道,管道里面有多线程可以同时处理很多业务,处理完返回就完事了、

下面的连接是一个用Netty写的案例,供参考:https://github.com/Coderxiangyang/NettyExercise/tree/master/NettySimple

其他解释
  1. Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负责网络读写操作。
  2. NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个 selector,用于监听绑定
    在其上的 socket 网络通道。
  3. NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由 IO 线程 NioEventLoop
    负责
    1、 NioEventLoopGroup 下包含多个 NioEventLoop
    2、 每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue
    3、 每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel
    4、 每个 NioChannel 只会绑定在唯一的 NioEventLoop 上
    5、 每个 NioChannel 都绑定有一个自己的 ChannelPipeline

Netty异步模型

Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得IO 操作结果,
Netty 的异步模型是建立在 future 和 callback 的之上的。callback 就是回调。重点说 Future,它的核心思想是:假设一个方法 fun,计算过程可能非常耗时,等待 fun 返回显然不合适。那么可以在调用 fun 的时候,立马返回一个 Future,后续可以通过 Future 去监控方法 fun 的处理过程(即 : Future-Listener 机制)

Future-Listener 机制)

当 Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,
注册监听函数来执行完成后的操作。
常见操作:
1、通过 isDone 方法来判断当前操作是否完成;
2、 通过 isSuccess 方法来判断已完成的当前操作是否成功;
3、 通过 getCause 方法来获取已完成的当前操作失败的原因;
4、 通过 isCancelled 方法来判断已完成的当前操作是否被取消;
5、 通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果
Future 对象已完成,则通知指定的监听器