Netty的线程模型

 2023-01-25
原文作者:mooseraod 原文地址:https://juejin.cn/post/7028858045882957837

Netty介绍

原生NIO存在的问题

  1. NIO的类库和API复杂,使用起来比较麻烦,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等API的使用。

  2. 需要具备一些额外的技能:Java多线程、Reactor模式、必须对多线程和网络编程非常熟悉、才能写出高质量的NIO程序。

  3. 开发工作量和难度非常大:例如客户端面临重连、网络闪断、半包读写、失败缓存、网络阻塞和异常流的处理等等。

  4. JDK NIO的BUG:Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。直到JDK1.7版本该问题依然存在,没有被根本解决。

    在NIO中通过Selector的轮询,查看当前通道是否有IO事件,根据JDK NIO API的描述,Selector的select()方法会一直阻塞,直到IO事件完成或超时,但是在Linux平台上有时会出问题,在某些场景下select()方法会直接返回,即使没有超时并且也没有IO事件到达,这就是著名的epoll bug,这是一个比较严重的bug,他会导致线程陷入死循环,会让CPU飙升到100%,极大地影响到系统的可靠性,到目前为止,JDK都没有完全解决这个问题。

概述

Netty是JBoss提供的一个Java开源框架。提供异步的,基于事件驱动的网络应用程序框架,用以快速地开发高性能,高可靠性的网络IO程序。Netty是一个基于NIO的网络编程框架,使用Netty可以帮你快速、简单的开发一个网络应用,相当于简化和流程化了NIO的开发过程。作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,知名的Elasticsearch、Dubbo框架内部都采用了Netty。

Netty的强大之处:零拷贝、可拓展的事件模型;支持TCP、UDP、HTTP、WebSocket等协议;提供安全传输、压缩、大文件传输、编解码支持等等。

具备如下优点:
  1. 设计优雅,提供阻塞和非阻塞的Socket;

  2. 提供灵活可拓展的事件模型;

  3. 提供高度可定制的线程模型;

  4. 具备更高的性能和更大的吞吐量,使用零拷贝技术最小化不必要的内存复制,减少资源的消耗;

  5. 提供安全传输特性;

  6. 支持多种主流协议;预置多种编解码功能,支持用户开发私有协议。

线程模型

概述

不同的线程模式,对程序的性能有很大影响,在学习Netty线程模式之前,首先了解下各个线程模式,最后看看Netty线程模型有哪些优越性。

目前存在的线程模型有:

  • 传统的阻塞I/O服务模型

  • Reactor模型:根据Reactor的数量和处理资源池线程的数量不同,有三种典型的实现

    • 单Reactor 单线程
    • 单Reactor 多线程
    • 主从Reator 多线程

传统阻塞的 I/O 服务模型

采用阻塞 I/O 模式获取输入的数据,每个连接都需要独立的线程完成数据的输入,业务处理和数据返回的工作。

202212302204319601.png

这种模式存在问题:

  • 当并发数很大的时候,会创建大量的线程,占用很大的系统资源。
  • 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成系统资源的浪费。

Reactor 模型

Reactor模式,通过一个或者多个输入同时传递给服务器处理器的模式,服务器端程序处理传入的多个请求,并将它们同步分派到相应的处理线程,因此Reactor模式也叫Dispatch模式。

Reactor模式使用IO复用监听事件,收到事件后,分发给某个线程(进程),这点就是网络服务器高并发处理的关键。

下面我们分别看下Reactor的三种经典模式:

单 Reactor 单线程

202212302204324582.png

  1. Selector 可以实现应用程序通过一个阻塞对象监听多路连接请求。

  2. Reactor 对象通过Selector监控客户端连接事件,收到事件之后通过 Dispatch进行分发。

  3. 建立连接,请求事件,则由Acceptor处理连接请求,然后创建一个Handler对象处理完成后的后续业务处理。

  4. Handler会完成Read->业务处理->Send 这样一个完成业务流程。

优点:

模型简单,没有多线程,进程通信,竞争等问题,全部都在一个线程中完成

缺点:
  1. 存在性能问题:只有一个线程,无法完全发挥多核CPU的性能,Handler在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈。
  2. 存在可靠性问题:线程意外终止或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。

单 Reactor 多线程

202212302204328593.png

  1. Reactor 对象通过 Selector 监控客户端请求事件,收到事件后,通过 Dispatch 进行分发。

  2. 如果建立连接请求,则右 Acceptor 通过 accept 处理连接请求。

  3. 如果不是连接请求,则由 Reactor 分发调用连接对应得Handler 处理。

  4. Handler 只负责响应事件,不做具体的业务处理(只做 read 和 send 操作),通过 read 读取数据后,会分发给后面的 Worker 线程池中的某个线程处理业务。

  5. Worker 线程池会分配独立线程完成真正的业务,并将结果返回给 Handler。

  6. Handler 收到响应后,通过 send 将结果返回给客户端。

优点:

可以充分利用多核CPU的处理能力

缺点:

多线程数据共享和访问比较复杂,Reactor 处理所有的事件的监听和响应,再单线程运行,再高并发场景容易出现性能瓶颈。

主从 Reactor 多线程

202212302204335074.png

  1. Reactor 主线程 MainReactor 对象通过 select() 监听客户端连接事件,收到事件后,通过 Acceptor 处理客户端连接事件。

  2. 当 Acceptor 处理完客户端连接事件之后(与客户端建立好 Socket 连接),MainReactor 将连接分配给 SubReactor。(即:MainReactor 只负责监听客户端连接请求,和客户端建立连接之后将连接交由SubReactor 监听后面的 IO事件。)

  3. SubReactor 将连接加入到自己的连接队列进行监听,并创建 Handler 对各种事件进行处理。

  4. 当连接上有新事件发生的时候,SubReactor 就会调用对应的 Handler 处理。

  5. Handler 通过 Read 从连接上读取请求数据,将请求数据分发给 Worker 线程池进行业务处理。

  6. Worker 线程池会分配独立线程来完成真正的业务处理,并将结果返回给 Handler。Handler 通过 Send 向客户端发送响应数据。

  7. 一个 MainReactor 可以对应多个 SubReactor,即一个 MainReactor 线程可以对应多个 SubReactor 线程。

优点:
  • MainReactor 线程与 SubReactor 线程的数据交互简单职责明确,MainReactor 线程只需要接收新连接,SubReactor 线程完成后续的业务处理。

  • MainReactor 线程与 SubReactor 线程的数据交互简单,MainReactor 线程只需要把新连接传给 SubReactor 线程,SubReactor 线程无需返回数据。

  • 多个 SubReactor 线程能够应对更高的并发请求

缺点:
  • 这种模式的缺点就是编程复杂度较高。但是由于其优点明显,再许多项目中被广泛应用,包括 Nginx,Memcached、Netty 等。
  • 这种模式也叫服务器的1+M+N 线程模式,即使用该模式开发的服务器包含一个(或多个, 1只表示相对较少)连接建立线程,+M 个IO线程+N 个业务处理线程。这是业界成熟的服务器程序设计模式。