Netty权威指南总结(二)

news2024/12/26 14:20:46

三、Netty代码相关:

(四) EventLoop与EventLoopGroup:

Netty的Nio线程是NioEventLoop。

1. Reactor线程模型:

Reactor模型的三个角色:

  • Reactor:把IO事件分配给对应的Handler处理,功能像是调度器。

  • Acceptor【饿渴赛破特儿】:处理客户端连接事件。

  • Handler:处理具体的任务。

Reactor模型的运行机制:(四个步骤)

  • 连接注册:Channel建立后,注册到Reactor线程的Selector中。

  • 事件轮询:轮询Selector选择器中已注册的所有Channel的IO事件。

  • 事件分发:将准备就绪的 IO 事件分配相应的处理线程。

  • 任务处理:Reactor线程还负责任务队列中的非 IO 任务,每个Worker线程从各自维护的任务队列中,取出任务异步执行。

Reactor单线程模型:

  • Reactor单线程模型:是指所有的IO操作都在同一个NIO线程上面完成。

  • NIO线程的职责如下(4个):

  • 作为NIO的服务端,接受客户端的TCP链接;

  • 作为NIO的客户端,向服务端发起TCP链接;

  • 读取通信对端的请求或应答消息;

  • 向通信对端发送消息请求或应答消息。

  • 由于Reactor模式使用的是异步非阻塞IO,所有的IO操作不会导致阻塞,理论上一个线程可以独立处理所有IO相关的操作。

  • 小容量应用场景可以使用单线程模型,但是高负载、大并发应用场景不合适。主要原因如下:

  • 一个NIO线程同时处理上千链路,性能无法支持。

  • NIO线程负载过重之后,处理速度变慢,会导致大量客户端链接超时。

  • 可靠性问题,得不到保障。

Reactor多线程模型:

  • 它与Reactor单线程模型的最大区别:有一组NIO线程来处理IO操作,即Reactor线程池。

  • 主要特点:

  • 有专门用于监听服务端,接受客户端TCP链接请求的NIO线程(Acceptor线程)。

  • 有专门负责网络IO操作的线程池(Reactor Pool):读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,油这些NIO线程负责消息的取读、解码、编码和发送。

  • 一个NIO线程可以同时处理多条链路,但是一个链路只对应一个NIO线程,防止发生并发操作问题

  • 绝大多数场景,Reactor多线程模型可以满足性能需求。但是在个别特殊场景中,一个NIO线程负责监听和处理所有的客户端链接(Acceptor线程)可能存在性能问题,即单独一个Acceptor线程可能存在性能不足。

主从Reactor多线程模型:

  • 为了解决Reactor多线程模型中,一个Acceptor性能不足,而诞生的模型。

  • 特点:

  • 服务端用于接受客户端连接的是一个独立的NIO线程池

  • Acceptor接收到客户端TCP连接请求,并处理完成后(可能包含接入认证等),将新创建的SocketChannel注册到IO线程池( Reactor Pool)的某个IO线程上,由它负责SocketChannel的读写和编解码工作。

  • Acceptor线程池(NIO线程池)仅仅用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到后端subReactor线程池的IO线程上,由IO线程负责后续的IO操作。

  • 主从Reactor多线程模型,可以解决一个服务端监听线程(Acceptor线程)性能不足的问题。因此,在Netty的官方Demo中,推荐使用该线程模型。

2. Netty的线程模型:

Netty可以同时支持单线程、多线程和主从多线程模型。用户可以通过启动参数进行配置。

Netty服务端启动时,创建了两个NioEventLoopGroup,它实际是两个独立的线程池。一个用于接收客户端的TCP连接,另一个用于处理IO相关的读写操作,或者执行系统Task、定时任务Task等

用于接收客户端请求线程池的职责如下(Acceptor线程池)

  • 接收客户端TCP连接,初始化Channel参数。

  • 将链路状态变更事件,通知给ChannelPipeline。

用于处理IO操作线程池的职责如下(Reactor线程池)

  • 异步读取通信对端的数据报,发送读事件到ChannelPipeline。

  • 异步发送消息到通信对端,调用ChannelPipeline的消息发送接口。

  • 执行系统调用Task。

  • 执行定时任务Task,例如,链路空闲状态监测定时任务。

3. EventLoop原理:

概念:

  • EventLoop不是Netty独有的概念,它是一种事件等待和处理的程序模型,可以解决多线程资源消耗高的问题。例如Node.js也采用了EventLoop的运行机制。

  • 在Netty中,EventLoop是Reactor线程模型的事件处理引擎,每个EventLoop线程都维护一个Selector选择器和任务队列taskQueue。它主要负责处理 I/O 事件、普通任务和定时任务。

  • NioEventLoop无锁串行化的设计不仅使系统吞吐量达到最大化,而且降低了用户开发业务逻辑的难度,不需要花太多精力关心线程安全问题。

EventLoop通用的运行模式:

每当事件发生时,应用程序都会将产生的事件,放入事件队列当中,然后EventLoop会轮询,从队列中取出事件执行,或者将事件分发给相应的事件监听者执行。事件执行的方式通常分为立即执行、延后执行、定期执行几种。

NioEventLoop循环处理的流程:

NioEventLoop每次循环的处理流程都包含:事件轮询Select、事件处理ProcessSelectedKeys、任务处理RunAllTasks这个几个步骤,是典型的Reactor线程模型的运行机制。而且,Netty提供了一个参数 ioRatio,可以调整 IO 事件处理和任务处理的时间比例。

下面我们将着重从事件处理和任务处理两个核心部分出发,详细介绍Netty EventLoop 的实现原理。

事件处理机制 -> 采用无锁串行化的设计思路:

Netty的整体架构图如下:

  • BossEventLoopGroup和WorkerEventLoopGroup包含一个或者多个NioEventLoop。

  • BossEventLoopGroup负责监听客户端的Accept事件,当事件触发时,将事件注册至WorkerEventLoopGroup中的一个NioEventLoop上。

  • 每新建一个Channel, 只选择一个NioEventLoop与其绑定。所以说Channel生命周期的所有事件处理都是线程独立的,不同的NioEventLoop线程之间不会发生任何交集。

  • NioEventLoop完成数据读取后,会调用绑定的ChannelPipeline进行事件传播,ChannelPipeline也是线程安全的,数据会被传递到ChannelPipeline的第一个ChannelHandler中。数据处理完成后,将加工完成的数据再传递给下一个ChannelHandler,整个过程是串行化执行,不会发生线程上下文切换的问题。

任务处理机制:

  • NioEventLoop不仅负责处理IO事件,还负责执行任务队列中的任务。任务队列遵循FIFO规则,可以保证任务执行的公平性。

  • NioEventLoop处理的任务类型(三类):

  • 普通任务:通过NioEventLoop.execute()向任务队列taskQueue中添加任务。

  • 例如,Netty在写数据时,会封装WriteAndFlushTask提交给taskQueue。

  • taskQueue的实现类是"多生产者单消费者"队列MpscChunkedArrayQueue,在多线程并发添加任务时,可以保证线程安全。

  • 定时任务:通过调用 NioEventLoop.schedule() 向定时任务队列 scheduledTaskQueue 添加一个定时任务,用于周期性执行该任务。

  • 例如,心跳消息发送等。

  • 定时任务队列scheduledTaskQueue采用优先队列PriorityQueue实现。

  • 尾部队列:tailTasks相比于普通任务队列优先级较低,在每次执行完taskQueue 中任务后,会去获取尾部队列中任务执行。

  • 尾部任务并不常用,主要用于做一些收尾工作,例如统计事件循环的执行时间、监控信息上报等。

  • 任务处理在runAllTasks(long timeoutNanos)中完成,具体实现步骤如下(6步):

  • 1. fetchFromScheduledTaskQueue():将定时任务从scheduledTaskQueue中取出,聚合放入普通任务队列taskQueue中,只有定时任务的截止时间小于当前时间才可以被合并。

  • 2. 从普通任务队列taskQueue中取出任务。

  • 3. 计算任务执行的最大超时时间。

  • 4. safeExecute():安全执行任务,实际直接调用的 Runnable 的 run() 方法。

  • 5. 每执行 64 个任务进行超时时间的检查,如果执行时间大于最大超时时间,则立即停止执行任务,避免影响下一轮的 I/O 事件的处理。

  • 6. 最后获取尾部队列中的任务执行。

4. NioEventLoop继承关系类图:

5. EventLoop最佳实践:

  • 网络连接建立过程中三次握手、安全认证的过程会消耗不少时间。这里建议采用Boss和Worker两个EventLoopGroup,有助于分担Reactor线程的压力。

  • 由于Reactor线程模式适合处理耗时短的任务场景,对于耗时较长的ChannelHandler可以考虑维护一个业务线程池,将编解码后的数据封装成Task进行异步处理,避免 ChannelHandler阻塞而造成EventLoop不可用。或者将数据放入内存队列中解耦,业务线程异步处理队列中的数据(Hippo就是这么做的)。

  • 如果业务逻辑执行时间较短,建议直接在ChannelHandler中执行。例如编解码操作,这样可以避免过度设计而造成架构的复杂性。

  • 不宜设计过多的ChannelHandler。对于系统性能和可维护性都会存在问题,在设计业务架构的时候,需要明确业务分层和Netty分层之间的界限。不要一味地将业务逻辑都添加到ChannelHandler中。

(五) ChannelFuture与Promise:

1. ChannelFuture:

  • ChannelFuture的由来

  • 由于Netty的Future都是与异步IO操作相关的,因此,命名为ChannelFuture,代表它与Channel操作相关。

  • ChannelFuture的两种状态

  • uncompleted:开始一个IO操作时,ChannelFuture被创建,此时它处于uncompleted状态,表示非失败、非成功、非取消。

  • completed:IO操作完成后,ChannelFuture将会被设置成completed,它的结果有如下三种可能:操作成功、操作失败、操作被取消。

  • ChannelFuture的API

  • 它可以用于获取操作结果、添加事件监听器、取消IO操作、同步等待等。

  • Netty强烈建议直接通过添加监听器的方式获取IO操作结果,或者进行后续的相关操作

  • 原因:当我们进行异步IO操作是,完成的时间是无法预测的,如果不设置超时时间,会导致调用线程长时间被阻塞,甚至挂死。

  • 不要再ChannelHandler中调用ChannelFuture的await(),这会导致死锁

  • 原因:发起IO操作之后,IO线程负责通知发起IO操作的用户线程,如果IO线程和用户线程是同一个线程,就会导致IO线程等待自己通知操作完成,这就导致了死锁。

  • 异步IO操作有两类超时

  • TCP层面的IO超时。

  • 业务逻辑层面的操作超时,但是通常情况下业务逻辑超时时间应大于IO超时时间,它们两者是包含的关系。

  • ChannelFuture超时并不代表IO超时,ChannelFuture超时后,需要考虑是设置IO超时还是ChannelFuture超时。

2. ChannelFuture继承关系图:

3. Promise:

  • Promise由来

  • Promise是可写的Future,Future自身并没有写操作相关接口,Netty通过Promise对Future进行扩展,用于设置IO操作的结果。

  • Promise的举例使用

  • Netty发起IO操作时,会创建一个新的Promise对象,例如,调用ChannelHandlerContext的write(Object object)时,会创建一个新的ChannelPromise。当IO操作发送异常或者完成时,设置Promise的结果。

4. Promise的继承关系图:

由于IO操作种类非常多,因此对Promise子类也非常繁多。

四、Netty高级特性:

(一) Netty的架构:

1. Netty逻辑架构:

Netty采用三层网络架构进行设计和开发。

Reactor通信调度层:

  • Reactor通信调度层主要包括

  • Reactor线程NioEventLoop及其父类、NioSocketChannel/NioServerSocketChannel及其父类、ByteBuf及其衍生出来的各种Buffer、Unsafe以及衍生出的各种内部类。

  • Reactor通信调度层主要职责:监听网络的读写和连接操作

  • 负责将网络层的数据读取到内存缓冲区中,然后触发各种网络事件,例如连接创建、连接激活、读事件、写事件等,将这些事件触发到Pipeline中,由Pipeline管理的职责链来进行后续的处理。

职责链ChannelPipeline:

  • ChannelPipeline负责事件在职责链中的有序传播,同时负责动态地编排职责链

  • 职责链可以选择监听和处理自己关心的事件,它可以拦截处理和向后/向前传播事件。

业务逻辑编排层:

  • 业务逻辑编排层的分类(通常有两类)

  • 纯粹的业务逻辑编排。

  • 其他的应用层协议插件,用于特定协议相关的会话和链路管理。例如,CMPP协议,用于管理和中国移动短信系统的对接。

(二) Netty的高性能:

0. 高性能的体现(汇总):

  • 采用异步非阻塞的IO类库,基于Reactor模式实现,解决了传统同步阻塞IO模式下,一个服务端无法平滑地处理线性增长的客户端的问题。

  • TCP接收和发送的缓冲区使用直接内存代替堆内存,避免了内存复制,提升了IO读取和写入的性能。

  • 支持内存池的方式循环利用ByteBuf,避免了频繁创建和销毁ByteBuf带来的性能损耗。

  • 可配置的IO线程数、TCP参数等,为不同的用户场景提供定制化的调优参数,满足不同的性能场景。

  • 零拷贝。

  • 无锁化设计

  • 关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的CPU资源消耗问题。

  • 采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器或者锁。(环形数组缓冲区为什么能无锁化并发编程??20221121)

  • 高性能的序列化框架。

  • 高效的并发编程。

  • 通过引用计数器及时地申请释放不再被引用的对象,细粒度的内存管理降低了GC的频率,减少了频繁GC带来的时延增大和CPU损耗。

1. 异步非阻塞通信:

Netty的IO线程(NioEventLoop)聚合了多路复用器(Selector),可以同时并发处理成百上千的客户端SocketChannel。

Netty采用异步通信模式,一个IO线程可以并发处理N个客户端链接和读写操作,这个从根本上解决了传统同步阻塞IO“一链接一线程”模型架构的性能、弹性伸缩能力和可靠性的问题。

2. 高效的Reactor线程模型:

《见 (四) EventLoop与EventLoopGroup》

3. 直接内存与内存池:

《见 (一) ByteBuf和相关辅助类

4. 灵活的TCP参数配置能力:

对性能影响比较大的TCP配置项:

  • SO_RCVBUF和SO_SNDBUF:通常建议值为128KB或者256KB。

  • SO_TCPNODELAY:NAGLE算法,通过将缓冲区内的小封包,自动相连组成较大的封包,阻止大量小封包的发送阻塞网络,从而提高网络应用效率。但是,对于时延敏感的应用场景需要关闭该优化算法。

  • 软中断:如果Linux内核版本支持RPS(2.6.35以上版本),开启RPS后可以实现软中断,提升网络吞吐量。RPS根据数据包的源地址,目的地址以及目的和源端口,计算出一个hash值,然后根据这个hash值来选择软中断运行的CPU。从上层来看,也就是说将每个连接和CPU绑定,并通过这个hash值,来均衡软中断在多个CPU上,提升网络并行处理性能。

5. 零拷贝(3种):

Netty的零拷贝主要体现:

  • 读写socket的零拷贝:ByteBuf使用堆外直接内存进行socket读写,不需要进行字节缓冲区的二次拷贝。Netty默认使用Direct Buffer。

  • 文件传输的零拷贝:直接把文件缓冲区的内容,发送到目标的Channel中,不需要通过循环拷贝的方式。Netty文件传输类 DefaultFileRegion.transferTo() 将文件发送到目标Channel中。

  • 使用CompositeByteBuf的零拷贝:它对外,将多个ByteBuf封装成一个ByteBuf,对外提供统一封装后的ByteBuf接口。在添加ByteBuf时,不需要内存拷贝,即零拷贝。

6. 无锁化的串行设计:

在IO线程内部进行串行操作,避免多线程竞争导致性能下降。通过调整NIO线程池的线程参数,同时启动多个串行化的线程并行运行,提高性能。

Netty的NioEventLoop读取到消息之后,直接调用 ChannelPipeline.fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到用户的Handler,期间不进行线程切换。

7. 高性能的序列化框架:

Netty 默认提供了对Google ProtoBuf的支持。

8. 高效的并发编程:

volatile的大量、正确使用;CAS和原子类的广泛使用;线程安全容器的使用;通过读写锁提升并发性能。

(三) Netty的可靠性:

0. 可靠性的体现(汇总):

  • 网络通信类故障的解决

  • Netty支持配置客户端超时的时间,NIO类库没有提供现成的链路超时接口,Netty自己封装的。

  • 通信对端强制关闭连接(Netty在IO异常之后,会调用关闭连接),TCP是全双工,通信双方都需要关闭和释放socket句柄,才不会发生句柄的泄漏。

  • 链路关闭:Netty在read操作返回-1后,调用方法关闭句柄,释放资源。

  • 定制IO故障处理:Netty支持用户自定义处理IO异常,Netty具体实现的接口:ChannelHandlerAdapter.exceptionCaught()。

  • 链路有效性检测:Netty采用链路空闲时心跳检测机制,保证长链接的链路有效性。

  • Reactor线程的保护

  • 例如,Netty策略规避了epoll bug

  • 内存保护机制

  • Netty提供了内存池和对象池,提升内存的利用率。

  • 可设置的内存容量上限,包括:ByteBuf、线程池线程数等。

  • 流量整形

  • 全局流量整形、链路级流量整形。

  • 优雅停机

  • Netty5.0版本开始优雅退出功能,做得更加完善。

1. 网络通信类故障:

1.1 客户端超时:

NIO在非阻塞模式下,会直接返回连接结果。如果没有连接成功,也没有发生IO异常,则需要将SocketChannel注册到Selector上监听连接结果。所以,异步连接超时,无法在API层面直接设置,而是需要通过定时器来主动监测。并且NIO类库并没提供现成的链接超时接口,需要NIO框架或者用户自己封装实现。

Netty支持配置连接超时时间,Netty在发起链接时,会根据超时时间创建ScheduledFuture,将它挂载到Reactor线程上,用于定时监测是否发生链接超时。创建完链接超时的定时任务后,会有NioEventLoop负责执行。如果已经连接超时,但是服务端仍然没有返回TCP握手应答,则关闭链接。

1.2 通信对端强制关闭连接:

客户端与服务端正常通信过程中,如果发生网络闪断、对方进程突然宕机或者其他非正常关闭链路事件时,TCP链路就会发生异常。由于TCP是全双工的,通信双方都需要关闭和释放Socket句柄才不会发生句柄的泄漏

Netty在IO异常之后,会调用关闭连接。

1.3 链路关闭:

NIO编程的一种误区:认为只要是对方关闭连接,就会发生IO异常,捕获IO异常之后再关闭连接即可。实际上,连接的合法关闭不会发生IO异常,它是一种正常场景,如果遗漏了该场景的判断和处理就会导致连接句柄泄漏

如果SocketChannel被设置为非阻塞,则它的read操作可能返回三个值:

  • 大于0:表示读取到了字节数。

  • 等于0:没有读取到消息,可能TCP处于Keep-Alive状态,接收到的是TCP握手消息。

  • -1:连接已经被对方关闭。

Netty通过判断Channel read操作的返回值进行不同的逻辑处理,如果返回-1,说明链路已经关闭,会调用closeOnRead() 关闭句柄,释放资源。

1.4 定制IO故障:

在大多数场景下,当底层网络发生故障时,应该由底层的NIO框架负责释放资源,处理异常等。上层的业务应用不需要关心底层的处理细节。但是,在一些特殊的场景下,用户可能需要感知这些异常,并针对这些异常进行定制处理,例如:

  • 客户端的断连重连机制。

  • 消息的缓存重发。

  • 接口日志中详细记录故障细节。

  • 运维相关功能,例如告警、触发邮件/短信等

Netty的I/O异常处理策略:在发生IO异常时,底层的资源由它负责释放,同时将异常堆栈信息以事件的形式通知给上层用户,用户对IO异常进行定制。这种处理机制既保证了异常处理的安全性,也向上层提供了灵活的定制能力。

Netty具体实现的接口:ChannelHandlerAdapter.exceptionCaught()。

2. 链路的有效性检测:

2.1 为什么要周期性检测链路?

当网络发生单通、连接被防火墙Hang住、长时间GC或者通信线程发生非预期异常时,会导致链路不可用,且不易被及时发现。特别是异常发生在凌晨业务低谷期间,当早晨业务高峰期到来时,由于链路不可用,导致瞬间的大批量业务失败或者超时,这将对系统的可靠性产生重大的威胁。

从技术层面看,要解决链路的可靠性问题,必须周期性的对链路进行有效性检测。目前最流行和通用的做法就是心跳检测

2.2 心跳检测机制:

  • 心跳检测目的

  • 确认当前链路可用、对方存活、并且能够正常接收和发送消息。

  • 心跳检测机制的三个层面

  • TCP层面的心跳检测:即TCP的Keep-Alive机制,它的作用域是整个TCP协议栈。

  • 协议层的心跳检测:主要存在于长连接协议中。例如SMPP协议。

  • 应用层的心跳检测:它主要由各业务产品通过约定方式定时给对方发送心跳消息实现。

  • 心跳检测机制的分类

  • Ping-Pong型心跳:由通信一方定时发送Ping消息,对方接收到Ping消息之后,立即返回Pong应答消息给对方,属于“请求-响应型”心跳。

  • Ping-Ping型心跳:不区分心跳请求和应答,由通信双方按照约定,定时向对方发送心跳Ping消息,它属于“双向”心跳。

  • 心跳检测的策略:

  • 心跳超时:连续N次心跳检测都没有收到对方的Pong应答消息或者Ping请求消息,则认为链路已经发生逻辑失效,这被称作心跳超时。

  • 心跳失败:读取和发送心跳消息时,如果直接发生了IO异常,说明链路己经失效,这被称为心跳失败。

  • Tips:无论发生心跳超时还是心跳失败,都需要关闭链路,由客户端发起重连操作,保证链路能够恢复正常。

  • 心跳检测原理示意图:

2.3 Netty的心跳检测机制:

  • Netty的心跳检测的实现

  • 利用链路空闲检测机制

  • Netty空闲检测机制的分类

  • 读空闲:链路持续时间T,没有读取到任何消息,触发超时Handler,进行链路检测。

  • 写空闲:链路持续时间T,没有发送任何消息,~~。

  • 读写空闲:链路持续时间T,没有接收或者发送任何消息,~~。Netty的默认读写空闲机制是发生超时异常时,关闭连接。但是,我们自定义超时逻辑,支持不同的用户场景。

  • Netty框架,自己实现的Handler,用来实现心跳检测:

  • IdleStateHandler:可以处理读、写、读写空闲事件的超时,在超时之后,会触发用户 handler 的 userEventTriggered()。

  • ReadTimeoutHandler:在指定的时间内没有数据被读取,则抛出一个异常,并且断开通道的链接。

  • WriteTimeoutHandler:在指定时间内写操作没有完成,则直接抛出一个异常,并且断开通道的链接。Tips:IdleStateHandler检测的是指定事件没有发生写操作。

  • 链路空闲时,并没有关闭链路,而是触发IdleStateEvent事件。用户可以订阅IdleStateEvent事件,自定义逻辑处理。例如关闭链路、客户端发起重新连接、告警和打印日志等。

3. Reactor线程的保护:

Reactor线程是IO操作的核心,NIO框架的发动机,一旦出现故障,将会导致挂载在其上面的多路用复用器和多个链路无法正常工作。因此它的可靠性要求非常高。

3.1 规避NIO BUG(epoll bug):

通常情况下,死循环是可检测、可预防,但是无法完全避免的。Reactor线程通常处理的都是IO相关的操作,因此我们重点关注IO层面的死循环。

JDK NIO类库最著名的就是epoll bug,它会导致Selector空轮询,IO线程CPU100%,严重影响系统的安全性和可靠性。

Netty提供了一种检测机制判断线程是否可能陷入空轮询,具体的实现方式如下:

  • 每次执行Select操作之前记录当前时间currentTimeNanos。

  • time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos,如果事件轮询的持续时间大于等于 timeoutMillis,那么说明是正常的,否则表明阻塞时间并未达到预期,可能触发了空轮询的Bug。

  • Netty引入了计数变量 selectCnt。在正常情况下,selectCnt 会重置,否则会对selectCnt自增计数。当 selectCnt达到SELECTOR_AUTO_REBUILD_THRESHOLD(默认512) 阈值时,会触发重建 Selector 对象。

Netty 采用这种方法巧妙地规避了JDK Bug。异常的Selector中所有的SelectionKey会重新注册到新建的Selector 上,重建完成之后异常的Selector就可以废弃了。

相关代码解释:https://blog.csdn.net/hxyascx/article/details/114284075

4. 内存保护:

NIO通信的内存保护主要集中在如下几点:

  • 链路总数的控制:每条链路都包含接收和发送缓冲区,链路个数太多容易导致内存溢出。

  • 单个缓冲区的上限控制:防止非法长度或者消息过大导致内存溢出。

  • 缓冲区内存释放:防止因为缓冲区使用不当导致的内存泄露。

  • NIO消息发送队列的长度上限控制。

4.1 缓冲区的内存泄漏保护:

为了提升内存的利用率,Netty提供了内存池和对象池。但是,基于缓存池实现以后需要对内存的申请和释放进行严格的管理,否则很容易导致内存泄漏。引入内存池机制,对象的生命周期,将由内存池负责管理。

对于Netty的用户而言,使用者的技术水平差异很大,一些对JVM内存模型和内存泄漏机制不了解的用户,可能只记得中请内存,忘记主动释放内存,特别是JAVA程序员。为了防止因为用户遗漏导致内存泄漏,Netty在Pipeline的尾Handler中自动对内存进行释放,TailHandler的内存回收代码如下:

4.2 缓冲区溢出保护:

对消息进行解码时,需要创建缓冲区。缓冲区的创建方式通常有两种:

  • 容量预分配:在实际读写过程中如果不够再扩展。

  • 根据协议消息长度创建缓冲区。

在实际的商用环境中,如果遇到畸形码流攻击、协议消息编码异常、消息丢包等问题时,可能会解析到一个超长的长度字段。Netty提供了编解码框架,因此对于解码缓冲区的上限保护就显得非常重要。

5. 流量整形:

大多数的商用系统都有多个网元或者部件组成,例如参与短信互动,会沙及手机、基站、短信中心、短信网关、SPCP等网元,不同网元或者部件的处理性能不同。为了防止因为浪涌业务或者下谢网元性能低,导致下游网元被压垮,有时候需要系统提供流量整形功能。

流量整形(traffic shaping)的定义:它是一种主动调整流量输出速率的措施。一个典型应用是基于下游网络结点的TP指标来控制本地流量的输出。

流量整形与流量监管的主要区别:

  • 流量整形对流量监管中需要丢弃的报文进行缓存,通常是将它们放入缓冲区或队列内,也称流量整形(Traffic Shaping,简称TS)。当令牌桶有足够的令牌时,再均匀的向外发送这些被缓存的报文。

  • 整形可能会增加延迟,而监管几乎不引入额外的延迟。

作为高性能的NIO框架,Netty的流量整形有两个作用:

  • 防止由于上下游网元性能不均衡导致下游网元被压垮,业务流程中断。

  • 防止由于通信模块接收消息过快,后端业务线程处理不及时导致的“撑死”问题。

5.1 全局流量整形:

全局流量整形的作用范围是进程级的,无论你创建了多少个Channel,它的作用域针对所有的Channel。

用户可以通过参数设置:报文的接收速率、报文的发送速率、整形周期。

Netty流量整形的原理:对每次读取到ByteBuf的可写字节数进行计算,获取当前的服文流量,然后与流量整形调值对比。如果已经达到或者超过了倒值。则计算等待时间delay,将当前的ByteBuf放到定时任务Task中缓存,由定时任务线程池在廷迟delay之后继续处理该ByteBuf。

流量整形的阈值limit越大。流量整形的精度越高,流量整形功能是可靠性的一种保障,它无法做到100%的精确。这个跟后端的编解码以及缓神区的处理策略相关,此处不再赞述。

流量整形与流控的最大区别:流控会拒绝消息,流量整形不拒绝和丢弃消息,无论接收量多大,它总能以近似恒定的速度下发消息,跟变压器的原理和功能类似。

5.2 链路级流量整形:

除了全局流量整形,Netty也支持链路级的流量整形,ChannelTrafficShapingHandler接口。

单链路流量整形与全局流量整形的最大区别:它以单个链路为作用城,可以对不同的链路设置不同的整形策略,它的实现原理与全局流量整形类似。

Netty支持用户自定义流量整形策略,通过继承 AbstractTrafficShapingHandler.doAccounting() 可以定制整形策略。

6. 优雅停机接口:

Java的优推停机通常通过注册JDK的ShutdownHook来实现,当系统接收到是出指令后,首先标记系统处于理出状态,不再接收新的消息。然后将积压的消息处理完,最后调用资源回收接口将资源销毁,最后各线程退出执行。

通常优雅退出有个时问限制,例如30S,如果到达执行时间仍然没有完成退出前的操作,则由监控脚本直接kil -9 pid,强制退出。

(四) Netty的可定制性与可拓展性:

1. Netty可定制性的提现:

  • 责任链模式:ChannelPipeline基于责任链模式开发,便于业务逻辑的拦截、定制和扩展。

  • 基于接口的开发:关键的类库都提供了接口或者抽象类,如果Netty自身的实现无法满足用户的需求,可以由用户自定义实现相关接口。

  • 提供了大量工厂类,通过重载这些工厂类可以按需创建出用户实现的对象。

  • 提供了大量的系统参数供用户按需设置,增强系统的场景定制性

2. Netty可扩展性的提现:

基于Netty框架,可以方便地进行应用层协议定制不需要修改Netty的源码,直接基于Netty的二进制类库即可,实现协议的扩展和定制。例如:HTTP协议栈、Thrift协议栈、FTP协议栈等。

例如基于Netty的HTTP协议、Dubbo协议、RocketMQ内部私有协议等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/403782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】C++核心编程(一)---内存四区

C程序在执行时,将内存大方向划分为4个区域 代码区 存放函数体的二进制代码,由操作系统进行管理全局区 存放全局变量和静态变量以及常量(字符串常量、全局常量)栈区 由编译器自动分配释放,存放函数的参数值、局部变量等堆区 由程序员分配和释…

jenkins问题

目录 python 不是内部或外部命令,也不是可运行的程序 ‘cmd’ 不是内部或外部命令,也不是可运行的程序或批处理文件。 git 不是内部或外部命令,也不是可运行的程序或批处理文件。 pywintypes.com_error: (-2147024891, ‘拒绝访问。’, None,…

Qt实用技巧:Qt中浮点数的相等比较方式(包括单精度和双精度)

若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/129464152 红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

Spring——AOP切入点表达式和AOP通知类型

切入点:要进行增强的方法 切入点表达式:要进行增强的方法的描述式 第一种方法的本质是基于接口实现的动态代理(jdk) 第二种是基于cglib实现的动态代理 AOP切入点表达式 而需要加载多个切入点时,不可能每个切入点都写一个切入点表达式 例子 下面的代理描述的是匹配…

条件语句(分支语句)——“Python”

各位CSDN的uu们你们好呀,最近总是感觉特别特别忙,但是却又不知道到底干了些什么,好像啥也没有做,还忙得莫名其妙,言归正传,今天,小雅兰的内容还是Python呀,介绍一些顺序结构的知识点…

Hadoop学习:Yarn

1.YARN介绍 一个通用的资源管理系统和调度平台 YARN不分配磁盘,由HDFS分配 相当于一个分布式的操作系统平台,为上层MR等计算程序提供运算所需要的资源(内存、CPU等) 2.YARN三大组件 不要忘记AppMaster,他是程序内部…

Android 进程间通信机制(二) mmap 原理

一. 前言 Binder中一次拷贝的实现就是利用mmap(memory mapping)内存映射机制,我们来看看它的工作原理. 二. 参考文章 下面这几篇文章建议先好好阅读一下,都是总结的很好的文章, 每个人理解可能不一样 笔者也是看了好多博客文章和B站视频讲解, 然后加上自己的理解后 输出的一…

程序的编译和链接

程序的编译和链接程序的编译和链接程序的两种环境翻译环境详解编译和链接预处理编译汇编链接运行环境程序的编译和链接 程序的两种环境 在ANSI C的任何一种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 …

《数据分析-JiMuReport04》JiMuReport报表设计入门介绍-页面优化

报表设计 2 页面优化 如上图所示的报表,仅仅是展示数据,不过这样看起来似乎太草率了,所以再优化一下吧 保存报表后,在积木报表中就可以看到对应的报表文件 此时我们如果还需要编辑报表,就点击这个报表即可 2.1 居中…

如何设计企业节点的『工业互联网标识解析系统』

一、『星火 链网』体系架构 『星火 链网』以节点形式进行组织互联互通,其中包括三类节点:超级节点、骨干节点、业务节点。 其底层采用“1N”主从链群架构,支持同构和异构区块链接入主链。在全国重点区域部署『星火 链网』超级节点&#…

three.js的demo例子-STL加载对象组件

three.js的demo例子-STL加载对象组件 提示:demo示例中所涉及到的three.js安装插件方法这里就不单个说明了哈,有需要的网上有很多教程 文章目录three.js的demo例子-STL加载对象组件效果展示插件模型一、HTML部分二、script部分1.引入库2.初始化数据3.监听…

卷王都在偷偷准备金三银四了...

年终奖没发; 简历石沉大海; 发消息只读不回 打开某招聘,看了看岗位,这个厂还不错,可是要求好高,我啥都不会。 “哎,算了,我简历还没更新呢,我躺到6月份拿到年终奖再跑…

【动态规划】多重背包问题,分组背包问题

Halo,这里是Ppeua。平时主要更新C语言,C,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接 我会一直往里填充内容哒! &…

名创优品业绩狂飙,手握哪些王牌?

撰稿 | 多客 来源 | 贝多财经 2023年注定是名创优品(NYSE:MNSO、HKEX:9896)发展史上具有重要意义的一年,不仅是创立的10周年,也是全球品牌战略升级的开局之年。 2月28日,名创优品公布了2023财年第二财季未经审计财务报告。得益于全球化战略…

机器学习笔记之狄利克雷过程(六)预测任务求解

机器学习笔记之狄利克雷过程——预测任务求解引言回顾:基于狄利克雷过程的预测过程预测任务的求解过程引言 上一节引出了基于狄利克雷过程的预测任务,本节将对该预测任务进行求解。 回顾:基于狄利克雷过程的预测过程 在已知隐变量样本集合…

Windows 环境安装Scala详情

为了进一步学习Spark,必须先学习Scala 编程语言。首先开始Scala 环境搭建。温馨提示:本文是基于Windows 11 安装Scala 2.13.1 版本第一步:确保本机已经正确安装JDK1.8 环境第二步:Scala 官网下载我们所属scala版本文件。Scala 官网…

JavaScript(WebAPI)

目录 1.什么是Web API? 2.DOM和DOM树 3.获取元素 4.事件 5.操作元素 获取/修改元素内容 1.innerText 2. innerHTML 获取/修改元素属性 获取/修改表单元素属性 获取/修改样式属性 1.修改内联样式 2.修改元素应用的CSS类名 6.操作节点 新增节点 删除节点 7.案例…

Vue3的composition API—setup函数, ref函数,reactive函数

1、Setup 函数 1.setup 是vue3中的一个配置项 2、setup是所有组件所需要的数据和方法都需要配置到setup中的 3、setup两种返回值: 若返回一个对象 若返回一个渲染函数 mian.js文件 注意:尽量不与Vue2混用 setup中无法访问vue2中的配置 不能是async函数…

Optional--Java8新特性最佳实践

Optional是在 Java8中引入的新特性之一。使用Optional类包装数据,可以避免经典的空检查和一些try-catch代码块。也能够通过链式方法调用,写出更流畅的函数式编程的代码。另一方面,滥用Optional也会导致性能低下和代码混乱。过往项目业务中有大…

【Linux】旋转锁 | 读写锁

在之前的线程学习中,用到的锁都是挂起等待锁,如果申请不到锁,那就会在锁中等待; 自旋锁则不大相似 文章目录1.自旋锁1.1 概念1.2 接口1.2.1 pthread_spin_init/destroy1.2.2 pthread_spin_lock1.2.3 pthread_spin_unlock2.读写锁…