Java IO流(四)Netty理论[模型|核心组件]

news/2024/12/22 2:34:27/

概述

  • Netty是由JBOSS提供的一个Java开源框架,可从Github获取独立项目
  • Netty是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端(摘录官网)
  • Netty所谓的异步是针对用户使用Channel进行IO操作,会立即返回ChannelFuture。但IO操作的任务是提交给了Netty的NIO底层去进行处理,所以我们说Netty的异步事件驱动与Netty底层基于NIO(同步非阻塞)是不矛盾的
  • Netty主要针对在TCP传输协议下,面对Clients端高并发应用;本质属于NIO框架,适用于服务器通讯相关多种应用场景
    • 阿里分布式服务框架Dubbo的RPC框架使用Dubbo协议,而Dubbo协议默认使用Netty作为基础的通信组件,实现节点间内部通信
    • 大数据领域中Hadoop的RPC框架默认采用的就是Netty进行跨节点通信
  • Netty官网版本推荐:目前稳定版为Netty4.x

Netty模型

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

工作原理执行流程

  • Netty中抽象了两组线程池BossGroup和WorkerGroup,都属于NioEventLoopGroup,其中BossGroup负责接收客户端连接,WorkerGroup负责网络读写
    • NioEventLoopGroup属于事件循环组,这个组中包含多个事件循环,每个事件循环是NioEventLoop
    • NioEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上socket的网络通讯
  • BossGroup下每个NioEventLoop执行流程
    • 轮询accept事件
    • 处理accept事件,与client建立连接生成NIOSocketChannel,并将其注册到对应worker下NIOEventLoop上的selector
    • 处理任务队列任务,即runAllTasks
  • WorkerGroup下每个NioEventLoop执行流程
    • 轮询read,write事件
    • 在对应NIOSocketChannel中处理read,write I/O事件
    • 处理任务队列任务,即runAllTasks
  • 每个Worker NioEventLoop处理业务时都会交由pipeLine管道处理,pipeline中包含了channel,即通过pipeline可以获取对应channel通道,管道中维护了很多处理业务的Handler处理器

TaskQueue任务队列

BossGroup和WorkerGroup下每个NioEventLoop中都维护了一个TaskQueue,可以用来异步处理比较耗时的任务,TaskQueue中的Task有三种典型使用场景

  • 用户程序自定义普通任务,该任务会提交到TaskQueue
    public class NettyServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //读取客户端发送的数据//ctx:上下文对象,包含管道pipeline,通道等信息//用户程序自定义普通任务ctx.channel().eventLoop().execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(10 * 1000); //模拟耗时长的任务ctx.writeAndFlush(Unpooled.copiedBuffer("Hello, Client!", CharsetUtil.UTF_8)); //将数据写到缓存并刷新} catch (Exception e) {System.out.println("Exception Info: " + e.getMessage());}}});}
    }
  • 用户自定义定时任务,该任务提交到scheduleTaskQueue
            ctx.channel().eventLoop().schedule(() -> {try {Thread.sleep(5 * 1000);ctx.writeAndFlush(Unpooled.copiedBuffer("Hello, Client!", CharsetUtil.UTF_8)); } catch (Exception e) {System.out.println("Exception Info: " + e.getMessage());}},5, TimeUnit.SECONDS);
  • 非当前Reactor线程调用Channel的各种方法(常用于推送任务场景)

异步模型

  • 异步和同步相对,指的是当一个异步过程调用发出后,调用者不能立刻得到结果,实际处理这个调用的组件在完成后,会通过状态、通知和回调来通知调用者
  • Netty中的I/O操作是异步的,包括Bind、Write、Connect等会返回一个ChannelFuture
  • Netty的异步模型是建立在future和callback之上的,callback即回调,Future核心思想在于对于处理某些耗时的业务时会立刻返回一个Future(表示异步执行结果,其中通过监听方法检测任务执行情况),通过Future监控该业务处理的过程最终获得I/O操作结果(即Future-Listener机制)
    • Future-Listener机制
      • 当Future对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture来获取操作执行状态,注册监听函数来执行完成后的操作
      • 常见操作方法
        • isDone: 判断当前操作是否完成
        • isSuccess: 判断已完成的当前操作是否成功
        • getCause: 判断当前操作失败的原因
        • isCancelled: 判断已完成的当前操作是否被取消
        • addListener: 用来注册监听器,当前操作已完成,将会通知指定的监听器(如果Future对象已完成,则通知指定的监听器)
      • Future-Listener示例

        ChannelFuture cf = bootstrap.bind(6668).sync(); //绑定指定端口并同步处理
        cf.addListener(new ChannelFutureListener() { //为端口添加监听@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {if (cf.isSuccess()){System.out.println("监听端口成功!");} else{System.out.println("监听端口失败!");}}
        });

核心组件

Netty中根据各组件职责不同可以分为网络通信层、事件调度层、服务编排层

网络通信层核心组件

BootStrap|ServerBootStrap

BootStrap是Netty中客户端启动类,负责客户端启动并连接Netty服务端;ServerBootStrap是服务端启动引导类,负责服务端启动并监听端口;常用方法包括如下

  • group:服务端用来设置BossGroup和WorkerGroup,而对于客户端则是设置一个EventGroup
  • channel:用来设置服务器端的通道类型,如服务器端设置为NioServerSocketChannel类型
  • option:用来给ServerChannel添加配置,如设置线程队列连接个数
  • childOption:用来给接收通道添加配置,如设置活动连接状态
  • handler:该handler作用于BossGroup
  • childHandler:该handler作用于WorkerGroup,设置业务处理类,包括自定义的handler
  • bind:用于服务器端,设置对外暴露的端口号
  • connect:用于客户端,设置连接服务器的ip和端口等信息

Channel

提示:NioSocketChannel和NioServerSocketChannel分别对应客户端和服务端的Channel,两者的直接父类不一致,因此对外提供的功能也是不相同的。比如当发生read事时,NioServerSocketChannel 的主要逻辑就是建立新的连接,而NioSocketChannel则是读取传输的字节进行业务处理

  • Netty中的Channel是完成一系列网络通信的载体
  • Channel可以看成网络编程中的Socket,提供了执行网络异步IO操作API,包括read|write|bind|connect,通过ChannelFuture中已注册的监听器在IO操作成功、失败等回调通知调用结果,大大降低了直接使用Socket类的复杂性
    • Future|ChannelFuture:Netty中所有IO操作都是异步的,不能立刻得知消息是否正确处理,但可以通过Future和ChannelFuture注册监听事件,当操作成功或者失败自动触发注册的监听事件,常用方法如下

      • channel:返回当前正在进行的IO操作的通道
      • sync:等待异步操作执行完毕
  • 通过Channel可获取当前网络连接的通道的状态以及配置参数等
  • 不同协议、不同阻塞类型的连接都有不同的Channel类型与之对应,常用Channel类型如下
    • NioSocketChannel:异步客户端TCP Socket连接
    • NioServerSocketChannel:异步服务器端TCP Socket连接
    • NioDatagramChannel:异步的UDP连接

事件调度层核心组件

EventLoopGroup

就是一个线程池,负责接收I/O请求并分配线程执行处理请求,在Netty服务端中的bossGroup中的线程用来处理新连接的建立,当连接建立后分发给workerGroup,workerGroup中每个线程则都会和唯一的客户端Channel连接进行绑定并处理该Channel上的读、写事件

EventLoop

就相当于线程池中的一个线程

服务编排层核心组件

Channel&&ChannelPipeline&&ChannelHandler关系

ChannelPipeline

  • ChannelPipeline是Handler集合,它负责处理和拦截inbound或者outbound的事件和操作,即用于处理Channel的入站和出站操作
  • Netty中每个Channel都有一个ChannelPipeline与之对应,两者可以互相获取;而且Pipeline维护了一个channelHandlerContext组成的双向链表,并且channelHandlerContext关联一个ChannelHandler
    • ​​​​​如果事件是从客户端到服务端的,那么我们称这些事件为出站的,出站消息会被通过编码器编码转换成字节
    • 反之,服务端发送响应消息给到客户端称为入站,入站消息会被解码器从字节转换为另一种格式(比如java对象);
    • 出站和入站的数据都会通过pipeline中的一系列Handler进行处理
    • 注意:编码解码器handler在pipleline中的链表位置必须在业务handler之前,否则无法正确调用
  • 当Channel中发生指定事件时,该事件就会在ChannelPipeline中沿着双向链表进行传播并调用各个ChannelHandler中的指定方法完成相应的业务处理
  • ChannelPipeline常用方法如下
    • addFirst:把业务处理类Handler添加到链表中第一个位置
    • addLast:把业务处理类Handler添加到链表中最后一个位置

提示:Netty正是通过ChannelPipeline这种数据结构为用户提供了自定义业务逻辑的扩展点,用户只需要向ChannelPipeline中添加处理对应业务逻辑的ChannelHandler即可,当指定事件发生时,该ChannelHandler中的对应方法就会进行回调进而实现业务的处理

ChannelHandler

ChannelInboundHandler&&ChannelOutboundHandler分别用来处理读&&写事件
  • ChannelHandler是Netty 中业务处理的核心类,当有 IO 事件发生时,该事件会在ChannelPipeline中进行传播,并依次调用具体的ChannelHandler进行业务处理

ChannInboundHandler

  • 在ChannelInboundHandler中定义了一系列的回调方法,用户可以实现该接口并重写相应的方法来自定义的业务逻辑
    • channelRegistered: 当 Channel 绑定到了对应 Selector 上之后就会进行回调

    • channelActive: 当 Channel调用bind()完成端口绑定之后,channelActive() 方法会进行回调

    • channelRead0

      • 服务端channelRead0
        • 服务端Channel绑定到Selector上时监听的是Accept事件,当客户端有新连接接入时回调channelRead()方法并完成新连接的接入
      • 客户端channelRead0
        • 当服务端处理完Accept事件后会生成一个和客户端通信的Channel,该Channel也会注册到对应的Selector上并监听read事件,当客户端向该Channel中发送数据时就会触发read事件,调用channelRead()方法
    • exceptionCaught:当前ChannelHandler中各回调方法处理过程中若发生异常则回调该方法

ChannelHandlerContext

  • ChannelHandlerContext主要保存了Channel通道、ChannelHandler通道处理者相关信息,它们之间对应关系如下
    • ChannelPipeline与ChannelHandlerContext对应关系: 1:n
    • ChannelHandlerContext与ChannelHandler 对应关系: 1:1
  • ChannelHandlerContext 常用方法如下
    • channel(): 获取当前通道
    • pipeline(): 获取当前管道
    • handler(): 获取当前handler处理器
    • close(): 关闭当前通道
    • writeAndFlush(): 将数据写出到ChannelPipeline管道中

其他

Unpooled类

  • Unpooled是Netty提供的专门操作缓冲区的工具类
  • copiedBuffer:用来设置数据以及字符编码并返回一个ByteBuf对象
    • ByteBuf:Netty中提供的缓冲区,底层就是一个数组
    • ByteBuf不需要调用flip方法进行模式切换,底层通过readerIndex|writerIndex|capactiy将byteBuf分成三个区域
      • 0-readerIndex:表示已经读取的区域
      • readerIndex-writerIndex:可读的区域
      • writerIndex-capactiy:可写的区域

Netty的编码解码机制

Netty提供的编码和解码器包括如下

  • StringEncoder:对字符串数据进行编码
  • ObjectEncoder:对Java对象进行编码
  • StringDecoder:对字符串数据进行解码
  • ObjectDecoder:对Java对象进行解码

存在问题

Netty的对java对象进行解码编码的底层使用的是Java序列化技术,但其效率不高,且无法跨语言,序列化体积是二进制编码5倍多,性能太低

解决方案

可以使用Google的Protobuf,它是一种轻便高效的结构化数据存储格式,用于结构化数据串行化,很适合数据存储或RPC数据交换格式,而且它支持跨语言

常见问题

已经存在NIO,为何还要开发出Netty框架?

  • NIO类库和API繁杂,使用麻烦
  • NIO属于单Ractor单线程模式,需要熟悉掌握线程和网络编程,才可以编写高质量NIO程序
  • 开发工作量和难度大,如面临断连重连、失败缓存、网络拥塞等问题
  • NIO本身存在的Epoll bug,产生空轮询导致CPU爆满

而Netty则解决了如上问题,它基于NIO进行了封装优化,具有如下优点

  • 设计优雅:适用于各种传输类型的统一
  • 高性能、吞吐量更高,延迟更低,减少资源消耗
  • 安全:完整的SSL/TLS支持
  • 社区活跃、版本迭代周期短

http://www.ppmy.cn/news/1057099.html

相关文章

YOLOV7改进:更换WIOU,实现无损涨点

1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。

Maven 一键部署到 SSH 服务器

简介 利用 Maven Mojo 功能一键部署 jar 包或 war 包到远程服务器上。 配置 在 maven 的setting.xml 配置服务器 SSH 账号密码。虽然可以在工程的 pom.xml 直接配置&#xff0c;但那样不太安全。 <servers><server><id>iq</id><configuration&…

Java调用高德地图API根据经纬度获取区县

在开发中&#xff0c;有时候需要根据经纬度来获取对应的区县信息&#xff0c;这可以通过调用高德地图的API来实现。本篇文章将介绍如何使用Java代码调用高德地图API来根据经纬度获取区县信息。 前提条件 首先&#xff0c;你需要注册一个高德地图开发者账号&#xff0c;并获取…

自动驾驶SLAM技术第四章习题2

在g2o的基础上改成ceres优化&#xff0c;高博都写好了其他的部分, 后面改ceres就很简单了. 这块我用的是ceres的自动求导&#xff0c;很方便&#xff0c;就是转化为模板仿函数的时候有点麻烦&#xff0c; 代码部分如下 ceres_type.h : ceres优化核心库的头文件 这个文件写的内…

关于档案馆建设的一些标准性文件说明

第一章 总则 第一条 本条阐明了本标准的编制目的。 中国是一个历史悠久的文明古国&#xff0c;档案事业的发展源远流长。档案是人类活动的真实记录&#xff0c;是人们认识和把握客观规律的重要依据。借助档案&#xff0c;我们能够更好地了解过去、把握现在、预见未来。档案工…

基于Roop视频换脸

Roop 是一个强大的一键换脸工具&#xff0c;允许用户在视频中替换面部&#xff0c;只需要目标面部的一张图片&#xff0c;无需数据集&#xff0c;无需训练。 相对于之前的 Simswap 来说效果要好很多&#xff0c;不过需要注意的是没有授权不要商用。 文章目录 环境搭建使用方法…

数据结构(6)

2-3查找树 2-结点&#xff1a;含有一个键(及其对应的值)和两条链&#xff0c;左链接指向2-3树中的键都小于该结点&#xff0c;右链接指向的2-3树中的键都大于该结点。 3-结点&#xff1a;含有两个键(及其对应的值)和三条链&#xff0c;左链接指向的2-3树中的键都小于该结点&a…

【安装】MongoDB7安装MongoSH命令

MongoDB Shell Download | MongoDB 下载之后 解压 配置环境变量即可 以前使用 mongo命令 改为 mongosh 官方说明 安装mongosh MongoDB 中文手册 | MongoD Manual | 中文操作手册 | MongoDB 最新版 (whaleal.com) 安装 mongosh — MongoDB Shell