Netty模型篇一:Netty 线程模型架构 & 工作原理 解读

Posted 一个小码农的进阶之旅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty模型篇一:Netty 线程模型架构 & 工作原理 解读相关的知识,希望对你有一定的参考价值。

1 Netty基本介绍

1.1 回顾一下 Java 中的 I/O模型:BIO、NIO、AIO

  • I/O 模型简单的理解:就是 用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能。

  • Java共支持3种网络编程模型/IO模式:BIONIOAIO

  • Java BIO同步并阻塞(传统阻塞型),服务器实现模式为 一个连接对应一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。

  • Java NIO同步非阻塞,服务器实现模式为 一个线程处理多个请求(连接)即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理

  • Java AIO(NIO.2)异步非阻塞,AIO 引入 异步通道 的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是:先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

BIO、NIO、AIO适用场景分析

  • BIO方式适用于 连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。
  • NIO方式适用于 连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯等。编程比较复杂,JDK1.4 开始支持。
  • AIO方式适用于 连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7 开始支持。

1.2 什么是Netty?为什么需要Netty?

什么是Netty

  • Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github上的独立项目。

  • Netty 是一个 异步的基于事件驱动网络应用框架,用以快速开发高性能、高可靠性的 网络 IO 程序。相当于简化和流程化了 NIO 的开发过程。

  • Netty主要针对在 TCP协议下,面向Clients端的高并发应用,或者Peer-to-Peer场景下的 大量数据持续传输 的应用。

  • Netty本质是一个 NIO框架,适用于 服务器通讯 相关的多种应用场景。

  • 要透彻理解Netty , 需要先学习 NIO , 这样我们才能阅读 Netty 的源码。

原生NIO存在的问题

  • NIO 的类库和 API 繁杂,使用麻烦:需要熟练掌握 SelectorServerSocketChannelSocketChannelByteBuffer 等。
  • 需要具备其他的额外技能:要熟悉 Java 多线程编程,因为 NIO 编程涉及到 Reactor模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的 NIO 程序。
  • 开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等。
  • JDK NIO 的 Bug:例如臭名昭著的 Epoll Bug它会导致 Selector 空轮询,最终导致 CPU 100%。直到 JDK 1.7 版本该问题仍旧存在,没有被根本解决。

这也就是问什么还需要Netty的原因

Netty 对 JDK 自带的 NIO 的 API 进行了封装,解决了上述问题。

  • 设计优雅:适用于各种传输类型的统一 API 阻塞和非阻塞 Socket;基于灵活且可扩展的事件模型,可以清晰地分离关注点;高度可定制的线程模型 - 单线程,一个或多个线程池.
  • 使用方便:详细记录的 Javadoc,用户指南和示例;没有其他依赖项,JDK 5(Netty3.x)或 6(Netty 4.x)就足够了。
  • 高性能、吞吐量更高:延迟更低;减少资源消耗;最小化不必要的内存复制。
  • 安全:完整的 SSL/TLS 和 StartTLS 支持。
  • 社区活跃、不断更新:社区活跃,版本迭代周期短,发现的 Bug 可以被及时修复,同时,更多的新功能会被加入

1.3 Netty的应用场景

互联网行业:在 分布式系统中,各个节点之间需要 远程服务调用高性能的 RPC 框架必不可少,Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用。

典型的应用有:阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信,Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信。

游戏行业

  • 无论是手游服务端还是大型的网络游戏,Java 语言得到了越来越广泛的应用;
  • Netty 作为高性能的基础通信组件,提供了 TCP/UDP 和 HTTP 协议栈,方便定制和开发私有协议栈,账号登录服务器;
  • 地图服务器之间可以方便的通过 Netty 进行高性能的通信。

2 传统阻塞 I/O 服务模型 & Reactor 模式

目前存在的线程模型有:

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

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

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

Netty 主要基于 主从 Reactor 多线程模型做了一定的 改进,其中主从Reactor 多线程模型 有多个 Reactor

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

工作原理图:黄色的框表示对象, 蓝色的框表示线程,白色的框表示方法(API)

模型特点

  • 采用阻塞IO模式获取输入的数据
  • 每个连接都需要独立的线程完成数据的输入,业务处理,数据返回

问题分析

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

2.2 Reactor 模式

建议大家先学习一下NIO:https://blog.csdn.net/qq_36389060/category_11777885.html

针对传统阻塞 I/O 服务模型的 2 个缺点,解决方案:

  • 基于 I/O 复用模型:多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理
  • 基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。

I/O 复用结合线程池,就是Reactor 模式基本设计思想

  • Reactor 对应的叫法:
    • 反应器模式
    • 分发者模式(Dispatcher)
    • 通知者模式(notifier)
  • Reactor 模式
    • 一个或多个请求,同时传递给 服务处理器(基于事件驱动)
    • 服务器端程序处理传入的多个请求,并将它们同步分派到相应的处理线程, 因此Reactor模式也叫Dispatcher模式;
    • Reactor 模式使用 IO复用 监听事件,收到事件后,分发给某个线程(进程),这点就是网络服务器高并发处理关键

Reactor 模式中 核心组成

  • Reactor:Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。 它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人;
  • Handlers:处理程序执行 I/O 事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际官员。Reactor 通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。

Reactor 模式分类

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

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

2.2.1 单 Reactor 单线程

方案说明

  • Select 是前面 I/O 复用模型介绍的标准网络编程 API,可以实现应用程序通过一个阻塞对象监听多路连接请求
  • Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发
  • 如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个Handler 对象处理连接完成后的后续业务处理
  • 如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应
  • Handler 会完成 Read ==》 业务处理 ==》Send 的完整业务流程

方案优缺点分析

服务器端用 一个线程 通过 多路复用 搞定所有的 IO 操作(包括连接,读、写等),编码简单,清晰明了,但是如果客户端连接数量较多,将无法支撑,前面的NIO案例就属于这种模型。

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

使用场景:客户端的数量有限,业务处理非常快速,比如 Redis在业务处理的时间复杂度 O(1) 的情况。

2.2.2 单 Reactor 多线程

方案说明

  • Reactor 对象通过select 监控客户端请求事件, 收到事件后,通过dispatch进行分发
  • 如果建立连接请求, 则右Acceptor 通过accept 处理连接请求, 然后创建一个Handler对象处理完成连接后的各种事件
  • 如果不是连接请求,则由reactor分发调用连接对应的handler 来处理
  • handler 只负责响应事件,不做具体的业务处理,通过read 读取数据后,会分发给后面的worker线程池的某个线程处理业务
  • worker 线程池 会分配独立线程完成真正的业务,并将结果返回给handler
  • handler收到响应后,通过send 将结果返回给 client

方案优缺点分析

  • 优点:可以充分的利用多核cpu 的处理能力。
  • 缺点:多线程数据共享和访问比较复杂, reactor 处理所有的事件的监听和响应,在单线程运行, 在高并发场景容易出现性能瓶颈。

2.2.3 主从 Reactor 多线程

方案说明

针对单 Reactor 多线程模型中,Reactor 在单线程中运行,高并发场景下容易成为性能瓶颈,可以让Reactor 在多线程中运行。

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

方案优缺点说明

  • 优点
    • 父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线程完成后续的业务处理。
    • 父线程与子线程的数据交互简单,Reactor 主线程只需要把新连接传给子线程,子线程无需返回数据。
  • 缺点:编程复杂度较高

结合实例:这种模型在许多项目中广泛使用,包括 nginx 主从 Reactor 多进程模型,Memcached 主从多线程,Netty 主从多线程模型的支持

3 Netty 线程模型

Netty 主要基于 主从 Reactors 多线程模型 做了一定的 改进其中主从 Reactor 多线程模型有多个 Reactor

Netty 线程模型简要概述

  • BossGroup 线程维护Selector , 只关注Accecpt;
  • 当接收到Accept事件,获取到对应的SocketChannel, 封装成 NioscoketChannel并注册到 Worker 线程(事件循环),并进行维护;
  • 当Worker线程监听到selector 中通道发生自己感兴趣的事件后,就进行处理(就由handler), 注意 handler 已经加入到通道

Netty 线程模型详细说明

  • Nett y抽象出两组线程池BossGroupWorkerGroup
    • BossGroup 专门负责接收客户端的连接
    • WorkerGroup 专门负责网络的读写
  • BossGroupWorkerGroup 类型都是 NioEventLoopGroup,NioEventLoopGroup 相当于一个 事件循环组,这个组中 含有多个事件循环 ,每一个事件循环是 NioEventLoop
  • NioEventLoop 表示一个不断循环的执行处理任务的线程, 每个NioEventLoop 都有一个selector , 用于监听绑定在其上的socket的网络通讯
  • NioEventLoopGroup 可以有多个线程, 即可以含有多个NioEventLoop
  • 每个Boss Group 中的 NioEventLoop 循环执行的步骤:
    • 1)轮询accept 事件
    • 2)处理accept 事件,与client建立连接 , 生成 NioScocketChannel并将其注册 Worker Group 上的某个 NIOEventLoop 上的 selector
    • 3)处理任务队列的任务,即 runAllTasks
  • 每个 Worker Group 中的 NIOEventLoop 循环执行的步骤:
    • 1)轮询 read/write 事件
    • 2)处理 I/O 事件, 即 read/write 事件,在对应的 NioScocketChannel 上处理
    • 3)处理任务队列的任务 , 即 runAllTasks
  • 每个Worker NIOEventLoop 处理业务时,会使用 pipeline(管道)。pipline中包含了 channel,即通过pipline可以获取到对应的 channel,并且pipline维护了很多的 handler(处理器)来对我们的数据进行一系列的处理。
  • handler(处理器) 有Netty内置的,我们也可以自己定义。

转netty线程模型

一切从ServerBootstrap开始

ServerBootstrap 负责初始话netty服务器,并且开始监听端口的socket请求。

Java代码  技术分享
  1. bootstrap bootstrap = new ServerBootstrap(  
  2.     new NioServerSocketChannelFactory(  
  3.         Executors.newCachedThreadPool(),//boss线程池  
  4.         Executors.newCachedThreadPool()//worker线程池  
  5.     )  
  6. );  
  7. bootstrap.setPipelineFactory(new HttpChannelPipelineFactory());  
  8. bootstrap.setOption("child.tcpNoDelay", true);  
  9. bootstrap.setOption("child.keepAlive", true);  
  10. bootstrap.bind(new InetSocketAddress(httpPort));//端口开始监听  

 

ServerBootstrap 用一个ServerSocketChannelFactory 来实例化。ServerSocketChannelFactory 有两种选择,一种是NioServerSocketChannelFactory,一种是OioServerSocketChannelFactory。 前者使用NIO,后则使用普通的阻塞式IO。它们都需要两个线程池实例作为参数来初始化,一个是boss线程池,一个是worker线程池。

ServerBootstrap.bind(int)负责绑定端口,当这个方法执行后,ServerBootstrap就可以接受指定端口上的socket连接了。一个ServerBootstrap可以绑定多个端口。

 

boss线程和worker线程

可 以这么说,ServerBootstrap监听的一个端口对应一个boss线程,它们一一对应。比如你需要netty监听80和443端口,那么就会有两 个boss线程分别负责处理来自两个端口的socket请求。在boss线程接受了socket连接求后,会产生一个channel(一个打开的 socket对应一个打开的channel),并把这个channel交给ServerBootstrap初始化时指定的 ServerSocketChannelFactory来处理,boss线程则继续处理socket的请求。

ServerSocketChannelFactory则会从worker线程池中找出一个worker线程来继续处理这个请求。
如 果是OioServerSocketChannelFactory的话,那个这个channel上所有的socket消息消息,从开始到 channel(socket)关闭,都只由这个特定的worker来处理,也就是说一个打开的socket对应一个指定的worker线程,这个 worker线程在socket没有关闭的情况下,也只能为这个socket处理消息,无法服务器他socket。

如果是NioServerSocketChannelFactory的话则不然,每个worker可以服务不同的socket或者说channel,worker线程和channel不再有一一对应的关系。
显然,NioServerSocketChannelFactory只需要少量活动的worker线程及能很好的处理众多的channel,而OioServerSocketChannelFactory则需要与打开channel等量的worker线程来服务。

线 程是一种资源,所以当netty服务器需要处理长连接的时候,最好选择NioServerSocketChannelFactory,这样可以避免创建大 量的worker线程。在用作http服务器的时候,也最好选择NioServerSocketChannelFactory,因为现代浏览器都会使用 http keepalive功能(可以让浏览器的不同http请求共享一个信道),这也是一种长连接。

worker线程的生命周期(life circle)

当 某个channel有消息到达或者有消息需要写入socket的时候,worker线程就会从线程池中取出一个。在worker线程中,消息会经过设定好 的ChannelPipeline处理。ChannelPipeline就是一堆有顺序的filter,它分为两部分:UpstreamHandler和 DownStreamHandler。本文着重介绍netty的线程模型,所以关于pipeline的内容从简说明。

技术分享

客户端送入的消息会首先由许多UpstreamHandler依次处理,处理得到的数据送入应用的业务逻辑handler,通常用SimpleChannelUpstreamHandler来实现这一部分。

 

Java代码  技术分享
  1. public class SimpleChannelUpstreamHandler{  
  2.     public void messageReceived(ChannelHandlerContext c,MessageEvent e) throws Exception{  
  3.         //业务逻辑开始掺入  
  4.     }  
  5. }  

 

 

对 于Nio当messageReceived()方法执行后,如果没有产生异常,worker线程就执行完毕了,它会被线程池回收。业务逻辑hanlder 会通过一些方法,把返回的数据交给指定好顺序的DownStreamHandler处理,处理后的数据如果需要,会被写入channel,进而通过绑定的 socket发送给客户端。这个过程是由另外一个线程池中的worker线程来完成的。

对于Oio来说,从始到终,都是由一个指定的worker来处理。

减少worker线程的处理占用时间

worker 线程是由netty内部管理,统一调配的一种资源,所以最好应该尽快的把让worker线程执行完毕,返回给线程池回收利用。worker线程的大部分时 间消耗在在ChannelPipeline的各种handler中,而在这些handler中,一般是负责应用程序业务逻辑掺入的那个handler最占 时间,它通常是排在最后的UpstreamHandler。所以通过把这部分处理内容交给另外一个线程来处理,可以有效的减少worker线程的周期循环 时间。一般有两种方法:

messageReceived()方法中开启一个新的线程来处理业务逻辑

Java代码  技术分享
  1. public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception{  
  2.      ...  
  3.      new Thread(...).start();  
  4. }  

 

在messageReceived()中开启一个新线程来继续处理业务逻辑,而worker线程在执行完messageReceived()就会结束了。更加优雅的方法是另外构造一个线程池来提交业务逻辑处理任务。

利用netty框架自带的ExecutionHandler

基本使用方法:

Java代码  技术分享
  1. public class DatabaseGatewayPipelineFactory implements ChannelPipelineFactory {  
  2.   
  3.     private final ExecutionHandler executionHandler;  
  4.   
  5.     public DatabaseGatewayPipelineFactory(ExecutionHandler executionHandler) {  
  6.         this.executionHandler = executionHandler;  
  7.     }  
  8.   
  9.     public ChannelPipeline getPipeline() {  
  10.         return Channels.pipeline(  
  11.                 new DatabaseGatewayProtocolEncoder(),  
  12.                 new DatabaseGatewayProtocolDecoder(),  
  13.                 executionHandler, // 多个pipeline之间必须共享同一个ExecutionHandler  
  14.                 new DatabaseQueryingHandler());//业务逻辑handler,IO密集  
  15.     }  
  16. }  

 

把 共享的ExecutionHandler实例放在业务逻辑handler之前即可,注意ExecutionHandler一定要在不同的pipeline 之间共享。它的作用是自动从ExecutionHandler自己管理的一个线程池中拿出一个线程来处理排在它后面的业务逻辑handler。而 worker线程在经过ExecutionHandler后就结束了,它会被ChannelFactory的worker线程池所回收。

它的构造方法是ExecutionHandler(Executor executor) ,很显然executor就是ExecutionHandler内部管理的线程池了。netty额外给我们提供了两种线程池:
MemoryAwareThreadPoolExecutor和OrderedMemoryAwareThreadPoolExecutor,它们都在org.jboss.netty.handler.execution 包下。
MemoryAwareThreadPoolExecutor 确保jvm不会因为过多的线程而导致内存溢出错误,OrderedMemoryAwareThreadPoolExecutor是前一个线程池的子类,除 了保证没有内存溢出之外,还可以保证channel event的处理次序。具体可以查看API文档,上面有详细说明。

 

 

 

 

Netty服务端启动步骤:
代码:

Java代码  技术分享
  1. // 构造一个服务端Bootstrap实例,并通过构造方法指定一个ChannelFactory实现  
  2. // 其中后两个参数分别是BOSS和WORK的线程池  
  3. Bootstrap serverBootstrap = new ServerBootstrap(  
  4.         new NioServerSocketChannelFactory(  
  5.                 Executors.newCachedThreadPool(),   
  6.                 Executors.newCachedThreadPool()));  
  7.   
  8. // 注册用户自己实现的ChannelPipelineFactory  
  9. serverBootstrap.setPipelineFactory(this.pipelineFactory);  
  10.   
  11. // 调用bind等待客户端来连接  
  12. ((ServerBootstrap) serverBootstrap).bind(socketAddress);  



Netty提供NIO与BIO两种模式,我们主要关心NIO的模式:
NIO处理方式:
1.Netty用一个BOSS线程去处理客户端的接入,创建Channel
2.从WORK线程池(WORK线程数量默认为cpu cores的2倍)拿出一个WORK线程交给BOSS创建好的Channel实例(Channel实例持有java网络对象)
3.WORK线程进行数据读入(读到ChannelBuffer)
4.接着触发相应的事件传递给ChannelPipeline进行业务处理(ChannelPipeline中包含一系列用户自定义的ChannelHandler组成的链)

有 一点要注意的是,执行整个ChannelHandler链这个过程是串行的,如果业务逻辑(比如DB操作)比较耗时,会导致WORK线程长时间被占用得不 到释放,最终影响整个服务端的并发处理能力,所以一般我们通过ExecutionHandler线程池来异步处理ChannelHandler调用链,使 得WORK线程经过ExecutionHandler时得到释放。
要解决这个问题增加下面代码即可:

Java代码  技术分享
  1. ExecutionHandler executionHandler =  
  2.         new ExecutionHandler(  
  3.                 new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576));  
  4. public ChannelPipeline getPipeline() {  
  5.         return Channels.pipeline(  
  6.                                 new DatabaseGatewayProtocolEncoder(),  
  7.                                 new DatabaseGatewayProtocolDecoder(),  
  8.                                 executionHandler, // Must be shared  
  9.                                 new DatabaseQueryingHandler());  
  10. }  



Netty为ExecutionHandler提供了两种可选的线程池模型:
1) MemoryAwareThreadPoolExecutor
通过对线程池内存的使用控制,可控制Executor中待处理任务的上限(超过上限时,后续进来的任务将被阻塞),并可控制单个Channel待处理任务的上限,防止内存溢出错误;
2) OrderedMemoryAwareThreadPoolExecutor
是 1)的子类。除了MemoryAwareThreadPoolExecutor 的功能之外,它还可以保证同一Channel中处理的事件流的顺序性,这主要是控制事件在异步处理模式下可能出现的错误的事件顺序,但它并不保证同一 Channel中的事件都在一个线程中执行,也没必要保证这个。
我们看下OrderedMemoryAwareThreadPoolExecutor中的注释:

Thread X: --- Channel A (Event A1) --.   .-- Channel B (Event B2) --- Channel B (Event B3) --->
                                                                    \ /
                                                                    X
                                                                    / \
Thread Y: --- Channel B (Event B1) --‘   ‘-- Channel A (Event A2) --- Channel A (Event A3) --->
处理同一个Channel的事件,是串行的方式执行的,但是同一个Channel的多个事件,可能会分布到线程中池中的多个线程去处理,不同的Channel事件可以并发处理,互相并不影响

再来看看MemoryAwareThreadPoolExecutor中的注释
Thread X: --- Channel A (Event 2) --- Channel A (Event 1) --------------------------->
Thread Y: --- Channel A (Event 3) --- Channel B (Event 2) --- Channel B (Event 3) --->
Thread Z: --- Channel B (Event 1) --- Channel B (Event 4) --- Channel A (Event 4) --->
同 一个Channel的事件,并不保证处理顺序,可能一个线程先处理了Channel A (Event 3),然后另一个线程才处理Channel A (Event 2),如果业务不要求保证事件的处理顺序,我认为还是尽量使用MemoryAwareThreadPoolExecutor比较好

Netty采用标准的SEDA(Staged Event-Driven Architecture) 架构
SEDA的核心思想是把一个请求处理过程分成几个Stage,不同资源消耗的Stag使用不同数量的线程来处理,Stag间使用事件驱动的异步通信模式。更进一步,在每个Stage中可以动态配置自己的线程数,在超载时降级运行或拒绝服务。

Netty所设计的事件类型,代表了网络交互的各个阶段,每个阶段发生时,会触发相应的事件并交给ChannelPipeline进行处理。事件处理都是通过Channels类中的静态方法调用开始的。

Channels中事件流转静态方法:
1. fireChannelOpen
2. fireChannelBound
3. fireChannelConnected
4. fireMessageReceived
5. fireWriteCompleteLater
6. fireWriteComplete
7. fireChannelInterestChangedLater
8. fireChannelDisconnectedLater
9. fireChannelDisconnected
10. fireChannelUnboundLater
11. fireChannelUnbound
12. fireChannelClosedLater
13. fireChannelClosed
14. fireExceptionCaughtLater
15. fireExceptionCaught
16. fireChildChannelStateChanged


Netty将网络事件分为两种类型:
1.Upstresam:上行,主要是由网络底层反馈给Netty的,比如messageReceived、channelConnected
2.Downstream:下行,框架自己发起的,比如bind、write、connect等

Netty的ChannelHandler 分为3种类型:
1.只处理Upstream事件:实现ChannelUpstreamHandler接口
2.只处理Downstream事件:实现ChannelDownstreamHandler接口
3.同时处理Upstream和Downstream事件:同时实现ChannelUpstreamHandler和ChannelDownstreamHandler接口
ChannelPipeline 维持所有ChannelHandler的有序链表,当有Upstresam或Downstream网络事件发生时,调用匹配事件类型的 ChannelHandler来处理。ChannelHandler自身可以控制是否要流转到调用链中的下一个 ChannelHandler(ctx.sendUpstream(e)或者ctx.sendDownstream(e)),这一样有一个好处,比如业务 数据Decoder出现非法数据时不必继续流转到下一个ChannelHandler

下面是我胡乱的画的一个图:

技术分享

 

以上是关于Netty模型篇一:Netty 线程模型架构 & 工作原理 解读的主要内容,如果未能解决你的问题,请参考以下文章

分布式理论,架构设计 Netty

Netty源码解析Netty核心源码和高并发高性能架构设计精髓

Netty系列之Netty线程模型

彻底搞懂 netty 线程模型

转netty线程模型

Netty基础招式——ChannelHandler的最佳实践