RPC 框架的可靠性设计
Posted Netty之家
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RPC 框架的可靠性设计相关的知识,希望对你有一定的参考价值。
1. 背景
1.1 分布式调用引入的故障
在传统的单体架构中,业务服务调用都是本地方法调用,不会涉及到网络通信、协议栈、消息序列化和反序列化等,当使用 RPC 框架将业务由单体架构改造成分布式系统之后,本地方法调用将演变成跨进程的远程调用,会引入一些新的故障点,如下所示:
新引入的潜在故障点包括:
1.消息的序列化和反序列化故障,例如,不支持的数据类型。
2.路由故障:包括服务的订阅、发布故障,服务实例故障之后没有及时刷新路由表,导致 RPC 调用仍然路由到故障节点。
3.网络通信故障,包括网络闪断、网络单通、丢包、客户端浪涌接入等。
1.2 第三方服务依赖
RPC 服务通常会依赖第三方服务,包括数据库服务、文件存储服务、缓存服务、消息队列服务等,这种第三方依赖同时也引入了潜在的故障:
1.网络通信类故障, 如果采用 BIO 调用第三方服务,很有可能被阻塞。
2.“雪崩效用”导致的级联故障,例如服务端处理慢导致客户端线程被阻塞。
3.第三方不可用导致 RPC 调用失败。
典型的第三方依赖示例如下:
2. 通信层的可靠性设计
2.1 链路有效性检测
当网络发生单通、连接被防火墙 Hang 住、长时间 GC 或者通信线程发生非预期异常时,会导致链路不可用且不易被及时发现。特别是异常发生在凌晨业务低谷期间,当早晨业务高峰期到来时,由于链路不可用会导致瞬间的大批量业务失败或者超时,这将对系统的可靠性产生重大的威胁。
从技术层面看,要解决链路的可靠性问题,必须周期性的对链路进行有效性检测。目前最流行和通用的做法就是心跳检测。
心跳检测机制分为三个层面:
1.TCP 层面的心跳检测,即 TCP 的 Keep-Alive 机制,它的作用域是整个 TCP 协议栈。
2. 协议层的心跳检测,主要存在于长连接协议中。例如 MQTT 协议。
3. 应用层的心跳检测,它主要由各业务产品通过约定方式定时给对方发送心跳消息实现。
心跳检测的目的就是确认当前链路可用,对方活着并且能够正常接收和发送消息。做为高可靠的 NIO 框架,Netty 也提供了心跳检测机制,下面我们一起熟悉下心跳的检测原理。
心跳检测的原理示意图如下:
不同的协议,心跳检测机制也存在差异,归纳起来主要分为两类:
1.Ping-Pong 型心跳:由通信一方定时发送 Ping 消息,对方接收到 Ping 消息之后,立即返回 Pong 应答消息给对方,属于请求 - 响应型心跳。
2.Ping-Ping 型心跳:不区分心跳请求和应答,由通信双方按照约定定时向对方发送心跳 Ping 消息,它属于双向心跳。
心跳检测策略如下:
1.连续 N 次心跳检测都没有收到对方的 Pong 应答消息或者 Ping 请求消息,则认为链路已经发生逻辑失效,这被称作心跳超时。
2.读取和发送心跳消息的时候如何直接发生了 IO 异常,说明链路已经失效,这被称为心跳失败。
无论发生心跳超时还是心跳失败,都需要关闭链路,由客户端发起重连操作,保证链路能够恢复正常。
Netty 的心跳检测实际上是利用了链路空闲检测机制实现的,它的空闲检测机制分为三种:
1.读空闲,链路持续时间 t 没有读取到任何消息。
2.写空闲,链路持续时间 t 没有发送任何消息。
3.读写空闲,链路持续时间 t 没有接收或者发送任何消息。
Netty 的默认读写空闲机制是发生超时异常,关闭连接,但是,我们可以定制它的超时实现机制,以便支持不同的用户场景,链路空闲接口定义如下:
protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}
链路空闲的时候并没有关闭链路,而是触发 IdleStateEvent 事件,用户订阅 IdleStateEvent 事件,用于自定义逻辑处理,例如关闭链路、客户端发起重新连接、告警和打印日志等。利用 Netty 提供的链路空闲检测机制,可以非常灵活的实现链路空闲时的有效性检测。
请大家点击左下角 【阅读原文】 移步至我的infoQ技术专栏 阅读全文。
以上是关于RPC 框架的可靠性设计的主要内容,如果未能解决你的问题,请参考以下文章