首页  

netty 架构原理简介     所属分类 netty 浏览量 85
Netty 4.1 

主流版本 Netty4
Netty5 中使用了 ForkJoinPool,增加了代码的复杂度,但是对性能的改善却不明显,不推荐使用

Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
高性能之处主要来自于其 I/O 模型和线程处理模型,前者决定如何收发数据,后者决定如何处理数据。

基础概念
BIO、NIO和AIO这三个概念分别对应三种通讯模型:阻塞、非阻塞、非阻塞异步

BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理,线程开销大。
NIO:一个请求一个线程,客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到该连接有I/O请求时才启动一个线程进行处理;
AIO:一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理

I/O 多路复用
Nonblocking I/O
基于 Buffer    Channel 和 Buffer 
线程模型
事件驱动 

事件驱动模型

事件队列(event queue):接收事件的入口,存储待处理事件
分发器(event mediator):将不同的事件分发到不同的业务逻辑单元
事件通道(event channel):分发器与处理器之间的联系渠道
事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作


事件驱动优点
可扩展性好,分布式的异步架构,事件处理器之间高度解耦,可以方便扩展事件处理逻辑
高性能,基于队列暂存事件,能方便并行异步处理事件


Reactor 模型
根据 Reactor的数量 和 Hanndler线程数量的不同,Reactor 模型有 3 个变种:
单 Reactor 单线程
单 Reactor 多线程
主从 Reactor 多线程

Netty 线程模型
主从 Reactors 多线程模型

MainReactor 负责客户端的连接请求,并将请求转交给 SubReactor
SubReactor 负责相应通道的 IO 读写请求
非 IO 请求(具体逻辑处理)的任务则会直接写入队列,等待 worker threads 进行处理



EventLoopGroup bossGroup = new NioEventLoopGroup(); 
EventLoopGroup workerGroup = new NioEventLoopGroup(); 
ServerBootstrap server = new ServerBootstrap(); 
server.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) 


bossGroup 线程池则只是在 Bind 某个端口后,获得其中一个线程作为 MainReactor,专门处理端口的 Accept 事件,每个端口对应一个 Boss 线程。
workerGroup 线程池会被各个 SubReactor 和 Worker 线程充分利用。

Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。
调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
当 Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,注册监听函数来执行完成后的操作。
常见操作:
通过 isDone 方法来判断当前操作是否完成。
通过 isSuccess 方法来判断已完成的当前操作是否成功。
通过 getCause 方法来获取已完成的当前操作失败的原因。
通过 isCancelled 方法来判断已完成的当前操作是否被取消。
通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则理解通知指定的监听器。


serverBootstrap.bind(port).addListener(future -> {  
if (future.isSuccess()) {  
    System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");  
} else {  
    System.err.println("端口[" + port + "]绑定失败!");  
}  
}); 




Bootstrap ServerBootstrap

Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,
Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类。

Future、ChannelFuture
Netty 中所有的 IO 操作都是异步的 
通过 Future 和 ChannelFutures,注册监听 

Channel
网络通信  用于执行网络 I/O 操作 
当前网络连接的通道的状态 
网络连接的配置参数 (例如接收缓冲区大小)
提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。

一些常用的 Channel 类型:

NioSocketChannel,异步的客户端 TCP Socket 连接。
NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
NioDatagramChannel,异步的 UDP 连接。
NioSctpChannel,异步的客户端 Sctp 连接。
NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO
Selector

Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。
当向一个 Selector 中注册 Channel 后,
Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),
使用一个线程高效地管理多个 Channel 


NioEventLoop

NioEventLoop 中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:

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

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

两种任务的执行时间比由变量 ioRatio 控制,默认为 50,则表示允许非 IO 任务执行的时间与 IO 任务的执行时间相等。

NioEventLoopGroup

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

ChannelHandler
处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。


ChannelInboundHandler  处理入站 I/O 事件。
ChannelOutboundHandler 处理出站 I/O 操作。
适配器类
ChannelInboundHandlerAdapter  处理入站 I/O 事件
ChannelOutboundHandlerAdapter 处理出站 I/O 操作
ChannelDuplexHandler   处理入站和出站事件

ChannelHandlerContext
保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象

ChannelPipline
保存 ChannelHandlerContext 的 List,用于处理或拦截 Channel 的入站事件和出站操作。

ChannelPipeline 实现了一种高级形式的拦截过滤器模式,控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互。


I/O 事件由 ChannelInboundHandler 或 ChannelOutboundHandler 处理,并通过调用 ChannelHandlerContext 中定义的事件传播方法。

ChannelHandlerContext.fireChannelRead(Object)
ChannelOutboundInvoker.write(Object)


一个 Channel 包含一个 ChannelPipeline,ChannelPipeline 中维护了一个由 ChannelHandlerContext 组成的双向链表,
并且每个 ChannelHandlerContext 中又关联一个 ChannelHandler

入站事件和出站事件在一个双向链表中,
入站事件会从链表 head 往后传递到最后一个入站的 handler,
出站事件会从链表 tail 往前传递到最前一个出站的 handler,
两种类型的 handler 互不干扰。


Netty 工作原理架构

初始化并启动 Netty 服务端

public static void main(String[] args) {  
// 创建mainReactor  
NioEventLoopGroup boosGroup = new NioEventLoopGroup();  
// 创建工作线程组  
NioEventLoopGroup workerGroup = new NioEventLoopGroup();   
final ServerBootstrap serverBootstrap = new ServerBootstrap();  
serverBootstrap  
// 组装NioEventLoopGroup  
.group(boosGroup, workerGroup)  
// 设置channel类型为NIO类型  
.channel(NioServerSocketChannel.class)  
// 设置连接配置参数  
.option(ChannelOption.SO_BACKLOG, 1024)  
.childOption(ChannelOption.SO_KEEPALIVE, true)  
.childOption(ChannelOption.TCP_NODELAY, true)  
// 配置入站、出站事件handler  
.childHandler(new ChannelInitializer< NioSocketChannel>() {  
@Override  protected void initChannel(NioSocketChannel ch) {  
// 配置入站、出站事件channel  
ch.pipeline().addLast(...);  
ch.pipeline().addLast(...);  
}  
});   
// 绑定端口  
int port = 8080;  
serverBootstrap.bind(port).addListener(future -> {  
if (future.isSuccess()) {  
System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");  
} else {  
System.err.println("端口[" + port + "]绑定失败!");  
}  
});
} 


初始化创建 2 个 NioEventLoopGroup,
其中 boosGroup 用于 Accetpt 连接建立事件并分发请求,workerGroup 用于处理 I/O 读写事件和业务逻辑。
基于 ServerBootstrap(服务端启动引导类),配置 EventLoopGroup、Channel 类型,连接参数、配置入站、出站事件 handler。
绑定端口,开始工作。



Server 端包含 1 个 Boss NioEventLoopGroup 和 1 个 Worker NioEventLoopGroup。

NioEventLoopGroup 相当于 1 个事件循环组,这个组里包含多个事件循环 NioEventLoop,
每个 NioEventLoop 包含 1 个 Selector 和 1 个事件循环线程。

每个 Boss NioEventLoop 循环执行的任务包含 3 步:

轮询 Accept 事件。
处理 Accept I/O 事件,与 Client 建立连接,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker NioEventLoop 的 Selector 上。
处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用 eventloop.execute 或 schedule 执行的任务,或者其他线程提交到该 eventloop 的任务。

每个 Worker NioEventLoop 循环执行的任务包含 3 步:

轮询 Read、Write 事件。
处理 I/O 事件,即 Read、Write 事件,在 NioSocketChannel 可读、可写事件发生时进行处理。
处理任务队列中的任务,runAllTasks。

其中任务队列中的 Task 有 3 种典型使用场景。
1. 用户程序自定义的普通任务

ctx.channel().eventLoop().execute(new Runnable() {  
@Override  
public void run() 
{  //...  } }); 

2. 非当前 Reactor 线程调用 Channel 的各种方法

例如在推送系统的业务线程里面,根据用户的标识,找到对应的 Channel 引用,然后调用 Write 类方法向该用户推送消息,就会进入到这种场景。
最终的 Write 会提交到任务队列中后被异步消费。

3. 用户自定义定时任务

ctx.channel().eventLoop().schedule(new Runnable() {  
@Override  
public void run() {   } }, 60, TimeUnit.SECONDS);

上一篇     下一篇
netty http server 实例

netty 趣事

Netty ChannelPipeline Inbound Outbound

grafana prometheus 查询结果为空处理

netty-socketio 与 websocket

Flutter-MQChat 基于MQTT的现代聊天应用客户端