netty可靠性

 Netty的可靠性

首先,咱们要从Netty的主要用途来分析它的可靠性,Netty目前的主流用法有三种:编程

1) 构建RPC调用的基础通讯组件,提供跨节点的远程服务调用能力;缓存

2) NIO通讯框架,用于跨节点的数据交换;安全

3) 其它应用协议栈的基础通讯组件,例如HTTP协议以及其它基于Netty开发的应用层协议栈。网络

以阿里的分布式服务框架Dubbo为例,Netty是Dubbo RPC框架的核心。它的服务调用示例图以下:框架

图1-1 Dubbo的节点角色说明图运维

其中,服务提供者和服务调用者之间能够经过Dubbo协议进行RPC调用,消息的收发默认经过Netty完成。异步

经过对Netty主流应用场景的分析,咱们发现Netty面临的可靠性问题大体分为三类:分布式

1) 传统的网络I/O故障,例如网络闪断、防火墙Hang住链接、网络超时等;oop

2) NIO特有的故障,例如NIO类库特有的BUG、读写半包处理异常、Reactor线程跑飞等等;测试

3) 编解码相关的异常。

在大多数的业务应用场景中,一旦由于某些故障致使Netty不能正常工做,业务每每会陷入瘫痪。因此,从业务诉求来看,对Netty框架的可靠性要求是很是的高。做为当前业界最流行的一款NIO框架,Netty在不一样行业和领域都获得了普遍的应用,它的高可靠性已经获得了成百上千的生产系统检验。

Netty是如何支持系统高可靠性的?下面,咱们就从几个不一样维度出发一探究竟。

2. Netty高可靠性之道

2.1. 网络通讯类故障

2.1.1. 客户端链接超时

在传统的同步阻塞编程模式下,客户端Socket发起网络链接,每每须要指定链接超时时间,这样作的目的主要有两个:

1) 在同步阻塞I/O模型中,链接操做是同步阻塞的,若是不设置超时时间,客户端I/O线程可能会被长时间阻塞,这会致使系统可用I/O线程数的减小;

2) 业务层须要:大多数系统都会对业务流程执行时间有限制,例如WEB交互类的响应时间要小于3S。客户端设置链接超时时间是为了实现业务层的超时。

JDK原生的Socket链接接口定义以下:

图2-1 JDK Socket链接超时接口

对于NIO的SocketChannel,在非阻塞模式下,它会直接返回链接结果,若是没有链接成功,也没有发生IO异常,则须要将SocketChannel注册到Selector上监听链接结果。因此,异步链接的超时没法在API层面直接设置,而是须要经过定时器来主动监测。

下面咱们首先看下JDK NIO类库的SocketChannel链接接口定义:

图2-2 JDK NIO 类库SocketChannel链接接口

从上面的接口定义能够看出,NIO类库并无现成的链接超时接口供用户直接使用,若是要在NIO编程中支持链接超时,每每须要NIO框架或者用户本身封装实现。

下面咱们看下Netty是如何支持链接超时的,首先,在建立NIO客户端的时候,能够配置链接超时参数:

图2-3 Netty客户端建立支持设置链接超时参数

设置完链接超时以后,Netty在发起链接的时候,会根据超时时间建立ScheduledFuture挂载在Reactor线程上,用于定时监测是否发生链接超时,相关代码以下:

图2-4 根据链接超时建立超时监测定时任务

建立链接超时定时任务以后,会由NioEventLoop负责执行。若是已经链接超时,可是服务端仍然没有返回TCP握手应答,则关闭链接,代码如上图所示。

若是在超时期限内处理完成链接操做,则取消链接超时定时任务,相关代码以下:

图2-5 取消链接超时定时任务

Netty的客户端链接超时参数与其它经常使用的TCP参数一块儿配置,使用起来很是方便,上层用户不用关心底层的超时实现机制。这既知足了用户的个性化需求,又实现了故障的分层隔离。

2.1.2. 通讯对端强制关闭链接

在客户端和服务端正常通讯过程当中,若是发生网络闪断、对方进程忽然宕机或者其它非正常关闭链路事件时,TCP链路就会发生异常。因为TCP是全双工的,通讯双方都须要关闭和释放Socket句柄才不会发生句柄的泄漏。

在实际的NIO编程过程当中,咱们常常会发现因为句柄没有被及时关闭致使的功能和可靠性问题。究其缘由总结以下:

1) IO的读写等操做并不是仅仅集中在Reactor线程内部,用户上层的一些定制行为可能会致使IO操做的外逸,例如业务自定义心跳机制。这些定制行为加大了统一异常处理的难度,IO操做愈加散,故障发生的几率就越大;

2) 一些异常分支没有考虑到,因为外部环境诱因致使程序进入这些分支,就会引发故障。

下面咱们经过故障模拟,看Netty是如何处理对端链路强制关闭异常的。首先启动Netty服务端和客户端,TCP链路创建成功以后,双方维持该链路,查看链路状态,结果以下:

图2-6 Netty服务端和客户端TCP链路状态正常

强制关闭客户端,模拟客户端宕机,服务端控制台打印以下异常:

图2-7 模拟TCP链路故障

从堆栈信息能够判断,服务端已经监控到客户端强制关闭了链接,下面咱们看下服务端是否已经释放了链接句柄,再次执行netstat命令,执行结果以下:

图2-8 查看故障链路状态

从执行结果能够看出,服务端已经关闭了和客户端的TCP链接,句柄资源正常释放。由此能够得出结论,Netty底层已经自动对该故障进行了处理。

下面咱们一块儿看下Netty是如何感知到链路关闭异常并进行正确处理的,查看AbstractByteBuf的writeBytes方法,它负责将指定Channel的缓冲区数据写入到ByteBuf中,详细代码以下:

图2-9 AbstractByteBuf的writeBytes方法

在调用SocketChannel的read方法时发生了IOException,代码以下:

图2-10 读取缓冲区数据发生IO异常

为了保证IO异常被统一处理,该异常向上抛,由AbstractNioByteChannel进行统一异常处理,代码以下:

图2-11 链路异常退出异常处理

为了可以对异常策略进行统一,也为了方便维护,防止处理不当致使的句柄泄漏等问题,句柄的关闭,统一调用AbstractChannel的close方法,代码以下:

图2-12 统一的Socket句柄关闭接口

2.1.3. 正常的链接关闭

对于短链接协议,例如HTTP协议,通讯双方数据交互完成以后,一般按照双方的约定由服务端关闭链接,客户端得到TCP链接关闭请求以后,关闭自身的Socket链接,双方正式断开链接。

在实际的NIO编程过程当中,常常存在一种误区:认为只要是对方关闭链接,就会发生IO异常,捕获IO异常以后再关闭链接便可。实际上,链接的合法关闭不会发生IO异常,它是一种正常场景,若是遗漏了该场景的判断和处理就会致使链接句柄泄漏。

下面咱们一块儿模拟故障,看Netty是如何处理的。测试场景设计以下:改造下Netty客户端,双发链路创建成功以后,等待120S,客户端正常关闭链路。看服务端是否可以感知并释放句柄资源。

首先启动Netty客户端和服务端,双方TCP链路链接正常:

图2-13 TCP链接状态正常

120S以后,客户端关闭链接,进程退出,为了可以看到整个处理过程,咱们在服务端的Reactor线程处设置断点,先不作处理,此时链路状态以下:

图2-14 TCP链接句柄等待释放

从上图能够看出,此时服务端并无关闭Socket链接,链路处于CLOSE_WAIT状态,放开代码让服务端执行完,结果以下:

图2-15 TCP链接句柄正常释放

下面咱们一块儿看下服务端是如何判断出客户端关闭链接的,当链接被对方合法关闭后,被关闭的SocketChannel会处于就绪状态,SocketChannel的read操做返回值为-1,说明链接已经被关闭,代码以下:

图2-16 须要对读取的字节数进行判断

若是SocketChannel被设置为非阻塞,则它的read操做可能返回三个值:

1) 大于0,表示读取到了字节数;

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

3) -1,链接已经被对方合法关闭。

经过调试,咱们发现,NIO类库的返回值确实为-1:

图2-17 链路正常关闭,返回值为-1

得知链接关闭以后,Netty将关闭操做位设置为true,关闭句柄,代码以下:

图2-18 链接正常关闭,释放资源

2.1.4. 故障定制

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

1) 客户端的断连重连机制;

2) 消息的缓存重发;

3) 接口日志中详细记录故障细节;

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

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

具体接口定义以及默认实现以下:

图2-19 故障定制接口

用户能够覆盖该接口,进行个性化的异常定制。例如发起重连等。

相关文章
相关标签/搜索