https://pyc-ycy.blog.csdn.net/article/details/121875863
Netty 专栏——Netty及其核心组件
一、Netty概述
1、原生NIO的缺点
- 1)NIO的类库和API库繁杂,使用麻烦,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
- 2)需要具备其他额外的技能:Java多线程和Java网络编程,因为NIO涉及到Reactor模式
- 3)开发工作量和难度非常大:客户端断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等
- 4)JDK NIO的bug:Epoll Bug,会导致Selector空轮询,最终耗尽CPU
2、Netty
Netty 有JBOSS 提供的一个Java开源框架,提供异步的、基于事件驱动的网络应用程序框架,用以开发高性能、高可靠的网络 IO 程序
。
二、Netty 高性能架构设计
1、线程模型
目前存在的线程模型有:传统阻塞I/O服务模型,reactor模式
;根据Reactor 的数量和处理资源线程池的数量的不同,有三种典型的实现:单Reactor单线程、单Reactor多线程和主从Reactor多线程
。Netty的线程模式主要基于主从Reactor多线程模型
做了改进,其中主从Reactor多线程模型有多个Reactor
。
反应器模式、分发者模式(dispatch)、通知者模式(notifier)
- 1)
基于I/O复用模型
:多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接;当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞态返回,开始业务处理。 - 2)
基于线程池复用线程资源
:不必为每个连接创建线程,将连接完成后的业务处理分配给线程进行处理,一个线程可以处理多个连接的业务。
2、Reactor 模式的核心组成
1)Reactor:reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反映
。
2)Handlers:处理程序执行 I/O 事件要完成的实际事件,执行非阻塞操作
。
3、单Reactor单线程
不能充分利用多核CPU的性能,并且无法应对高并发,容易阻塞,此外缺乏可靠性,一旦线程意外终止或死循环,会导致整个通讯模块不可用,不能接收和处理消息,只能用于客户量不大,且业务处理非常快的场景下
。
4、单Reactor多线程
Reactor 通过select 监控客户端的连接请求,用dispatch进行分发,Acceptor 通过 accept 处理连接请求,然后创建一个Handler对象处理完成连接后的各种事件,如果是业务处理请求,Reactor 会调用对应的handler,而handler 只负责响应事件,不做具体的业务处理,具体的业务处理又worker线程池中的线程进行,worker线程会在处理完成后的结果返回给handler,handler 再返回给客户端。可以充分利用多核CPU的处理能力,但多线程数据共享和访问比较复杂,并且Reactor 只有一个线程,面对高并发容易成为应用的性能瓶颈
5、主从 Reactor 多线程
Rector 子线程和线程池可以有多份。MainReactor和Acceptor共同完成与客户端的连接,连接完成后会被分配给SubReactor,SubReactor将连接加入到连接队列进行监听,并创建相应的handler进行各种事件处理,handler 同样只负责响应,具体业务处理还是由Worker线程池的线程进行处理,worker线程返回处理结果给handler,handler再返回给客户端。
优点:父线程与子线程的数据交互简单职责明确,父线程只需接收新连接,子线程完成后续的业务处理
缺点:编程复杂的较高
6、Netty 的线程模型
- 1)Netty抽象出两组线程池,BossGroup 负责接收客户端的连接,WorkerGroup专门负责网络的读写
- 2)BossGroup和WorkerGroup 类型都是 NIOEventLoopGroup
- 3)NIOEventLoopGroup 相当于事件循环组,含有多个事件循环,每一个事件循环是 NIOEventLoop
- 4)NIOEventLoop表示一个不断循环的执行处理任务的线程,每个NIOEventLoop都有一个selector,用于监听绑定在其上的Socket的网络通讯
- 5)NIOEventLoopGroup 可以有多个线程,可以含有多个NIOEventLoop
- 6)每个Boss NIOEventLoop 执行步骤有三步:①轮询accept事件、②处理accept事件与client建立连接,生成NIOSocketChannel,并将其注册worker NIOEventLoop上的selector、
③处理任务队列的任务,即 runAllTasks - 7)每个 worker NIOEventLoop 循环执行的步骤:① 轮询read、write事件 ② 处理 I/O 事件,在NIOSocketChannel处理 ③ 处理任务队列的任务
- 8)每个 worker NIOEventLoop 处理业务时,会使用 pipeline,pipeline 中包含了 channel,即通过 pipeline 可以获得对应的通道,管道中维护了很多的处理器
二、核心组件
1、Bootstrap 和 ServerBootstrap
Bootstrap或ServerBootstrap 起到一个引导作用
,分别用于客户端和服务端,通常通过Bootstrap实例对整个Netty程序进行一些相关配置,如设置BossGroup线程组和workerGroup线程组、channel类型和处理器等。
- 1)group():在服务端的group方法为 group(parentGroup, childGroup),在客户端的方法为: group(eventGroup) ,
用于设置线程组
- 2)channel() :
设置通道类型
,服务端通常传参为 NioServerSocketChannel,客户端为 NioSocketChannel - 3)option():
给 channel 添加配置
- 4)childOption() :
用于给接收到的通道添加配置
- 5)childHandler():
用于设置业务处理类,通常是自定义处理类
- 6)bind():
用于服务端,设置监听端口
- 7)connect():
用于客户端,设置服务端的主机和端口,用于连接服务器
2、Channel
netty 网络通信组件
,用于执行网络 I/O 操作,通过其可以获取当前网络连接的状态、配置参数,channel 提供异步的网络 I/O 操作——意味着任何 I/O 调用都会立即返回且不保证调用结束时所请求的 I/O 操作已完成,通常 channel 的 I/O 操作调用会返回一个 channelFuture。channel 支持关联 I/O 操作与对应的处理程序。不同协议、不同阻塞类型的连接有不同的 channel 类型与之对应,常用的 channel 类型见下面
- 1)NioSocketChannel:
异步的客户端 TCP Socket 连接
- 2)NioServerSocketChannel:
异步的服务端 TCP Socket 连接
- 3)NioDatagramChannel:
异步的 UDP 连接
- 4)NioSctpChannel:
异步的客户端 Sctp 连接
- 5)NioSctpServerChannel:
异步的服务端 Sctp 连接
,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。
3、Selector
Netty 基于 Selector 实现 I/O 多路复用
,通过一个 selector 线程可以监听多个连接的 Channel 事件。当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动轮询这些注册的 Channel 是否有已就绪的 I/O 事件,从而实现一个线程高效地管理多个 Channel
4、ChannelHandler
ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作并将其转发到 ChannelPipeline 中的一个处理程序
;Channel Handler 本身没有提供很多方法,因为该接口的许多方法需要实现,方便使用期间进行继承和重写,其常用实现类如下:
- 1)ChannelInboundHandler:用于处理
入站 I/O 事件
- 2)ChannelOutboundHandler:用于处理
出战 I/O 事件
- 3)ChannelInboundHandlerAdapter:用于处理
入站 I/O 事件,可用于自定义处理类进行继承
- 4)ChannelOutboundHandlerAdapter:用于处理
出战 I/O 事件,同样可用于自定义处理类进行继承
- 5)ChannelDuplexHandler:用于处理入站和出战事件
5、Pipeline 和 ChannelPipeline
- 1)
ChannelPipeline 是一个 Handler 的集合,负责处理和拦截 inbound 或 outbound 的事件和操作,相当于一个贯穿 Netty 的链
;ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 channel 中各个 ChannelHandler 如何相互交互。在 Netty 中每个 Channel 都有且仅有一个 ChannelPipline 与之对应
:
一个channel中包含了一个 ChannelPipeline,而 ChannelPipeline 中维护了一个 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中关联了一个 ChannelHandler。入站就是从 head 到 tail 的方向
,反之就是出战的方向,两个方向都会各自传递一个 handler 并且互不干扰。
6、ChannelHandlerContext
保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象,同时也绑定了对应的 pipeline 和 channel 的信息,方便对 Channel Handler 进行调用
- 1)ChannelFuture close() :关闭通道
- 2)ChannelOutboundInvoker flush, 刷新
- 3)ChannelFuture writeAndFlush(Object msg):将数据写到 ChannelPipeline 中当前的 ChannelHandler 的下一个 ChannelHandler 开始处理
7、ChannelOption
Netty 在创建 Channel 实例后,一般都需要设置 ChannelOption 参数,常用的参数如下
:
对应 TCP/IP 协议 listen 函数中的 backlog 参数
,用于初始化服务器可连接队列的大小。因为服务器是顺序处理客户端的连接请求的,同一时间点只能处理一个请求,后续请求会在一个请求队列中进行等待,而这个队列可以容纳多少个请求就是由 backlog 参数设置的。
7.2、ChannelOption.SO_KEEPALIVE
设置连接保持活跃状态。
8、EventLoopGroup 和 NioEventLoopGroup
NioEventLoopGroup 是 EventLoopGroup 的实现类
。EventLoopGroup 是一组 EventLoop 的抽象,每个 EventLoop 都有一个 Selector 实例
。
EventLoopGroup 提供 next 接口——用于从组里面按照一定规则获取其中一个 EventLoop 来处理任务。在服务端的 BossGroup 和 WorkerGroup 就是 EventLoopGroup 的实例对象。
通常一个服务端口对应一个 Selector 和一个 EventLoop 线程。BossEventLoop 负责接收客户端连接并将 SocketChannel 交给 WorkerEventLoop 进行具体 I/O 处理