问题:RPC 和多个服务端远程调用,每次都进行 TCP 连接,会对 RPC 性能影响大。实际中,要对连接进行管理和保持。
解决办法:用心跳包、断线重连实现,结合系统 tcp-keepalive 机制。
一、 连接管理(1. 长连接和短连接 2. TCP 层 keep-alive 3. 应用层 keep-alive 4. 应用层心跳还是 Keep-Alive)
二、SOFARPC 如何实现
1.基于系统 tcp-keepalive 机制实现
2.基于 Netty IdleStateHandler 心跳实现
3. SOFARPC 连接管理断开重连实现
一、 连接管理
1. 长连接和短连接
短连接:客户端向服务端发起连接请求。连接建立后,发送数据,接收返回,触发连接断开,下次重复。
长连接:则是在建立连接后,发送数据,接收数据,不主动断开,主动通过心跳等机制来维持连接可用,下次无需重连。
场景:长发送频繁,点对点通讯。TCP握手需要时间,跨城,或长距离时,处理速度会降很多。
PS:网络设备防火墙,可能导致连接断开。需要管理长连接。
2. TCP 层 keep-alive
2.1 TCP 的 keep-alive 是什么
让 TCP 连接“活着”,或让对方无响应TCP 连接断开,解决痛点场景是:
(1)两个机器之间有防火墙,防火墙自动断开长期无活动 TCP 连接。
(2)客户端。断电重启,卡死等等,导致 TCP 连接无法释放。
导致:如热数据需传递,连接已断开,应用程序没感知,在无效数据链路层面发送业务数据,发送失败。
解决办法:tcp-keepalive 连接无活动一段时间,发送空 ack,不会被防火墙关闭。
2.2 TCP 的 keep-alive 的默认值
tcp-keepalive,需要自行开启,三个参数生效,决定其行为。
net.ipv4.tcp_keepalive_time = 7200 TCP 保活打开情况,发送心跳的周期,默认值为7200s(2h)
net.ipv4.tcp_keepalive_probes = 9 没有接收到对方确认,继续发送保活探测包次数,默认值为 9(次)
net.ipv4.tcp_keepalive_intvl = 75 没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s。
2.3 如何使用
Java 的 Netty 为例,服务端和客户端设置即可。
ChannelOption.SO_KEEPALIVE, true 即打开,bolt默认打开。
.childOption( ChannelOption.SO_KEEPALIVE, Boolean.parseBoolean( System.getProperty (Configs.TCP_SO_KEEPALIVE,"true") ) );
Java 只能设置 SO_KEEPALIVE ;TCP_KEEPCNT,TCP_KEEPIDLE,TCP_KEEPINTVL 等参数配置,依赖于 sysctl 配置,系统进行读取。
2.4 检查
用 `netstat -no|grep keepalive` 命令来查看当前哪些 tcp 连接开启了 tcp keepalive.
3. 应用层 keep-alive
应用层 keep-alive 方案,叫心跳包,跟 tcp-keepalive 类似,间隔一定时间发送心跳数据,检测对方是否连接,属于应用程序协议一部分。
3.1 心跳是什么
实现和 tcp keep-alive一样,为什么要有应用层心跳?
tcp keep-alive默认2h,系统级别一旦更改,影响所有服务器上开启 keep alive应用。socks proxy 让 tcp keep-alive 失效, socks 协议只管转发 TCP 层具体数据包,不转发TCP 协议内实现细节包(也做不到)。
用 socks 代理,tcp keep-alive 失效,所以要自己心跳包。 socks proxy 只是一个例子,有各种原因让 tcp keep-alive 失效。
3.2 如何使用
基于 netty 开发的简单。分析 rpc 中连接管理时候介绍。
4. 应用层心跳还是 Keep-Alive
默认 keepalive 周期2h
4.1 系统 keep-alive 优势:
上层应用只需要处理数据收发、连接异常通知;
TCP 协议层面保活探测机制,系统内核自动替上层应用做好;
内核层面计时器相比上层应用,更为高效 ;
数据包更紧凑;
4.2 应用 keep-alive 优势:
(1)关闭 TCP keepalive,完全使用业务层面心跳保活机制;
(2)应用的心跳包更灵活,可控制检测间隔,方式等;
(3)适用于 TCP 和 UDP ,切换 TCP 和 UDP 时,上层的心跳包功能都适用;
心跳包附带:定时在服务端和客户端之间同步(如帧数同步);
所以大多数情况下,业务心跳 + TCP keepalive 互相补充。
二、SOFARPC 如何实现
1. SOFABOLT 基于系统 tcp-keepalive 机制实现
直接打开 KeepAlive 选项即可
客户端
RpcConnectionFactory 创建 RPC 连接,生成用户触发事件,init() 方法初始化 Bootstrap通过option()方法给每条连接设置 TCP属性,ChannelOption.SO_KEEPALIVE 表示是否开启 TCP 底层心跳机制,默认打开 SO_KEEPALIVE 选项。
服务端
RpcServer 服务端启动类 ServerBootstrap 初始化通过 option() 方法给每条连接设置 TCP底层相关的属性,默认设置 ChannelOption.SO_KEEPALIVE 选项为 true,表示 RPC 连接开启 TCP 底层心跳机制。
2. SOFABOLT 基于 Netty IdleStateHandler 心跳实现
向 Netty 中注册一个处理 Idle 事件的监听器。注册时传入 idle 产生事件,读 还是写 IDLE,还是都有,多久没有读写则认为是 IDLE 等。
客户端
(1)SOFABOLT 心跳检测客户端默认基于 IdleStateHandler(15000ms, 150000 ms, 0) 即 15 秒没有读或者写操作,注册给 Netty
(2)调用HeartbeatHandler 的 userEventTriggered()方法触发 RpcHeartbeatTrigger 发送心跳消息。
(3)RpcHeartbeatTrigger 心跳检测判断成功标准为是否接收到服务端回复成功响应,如果心跳失败次数超过最大心跳次数(默认为 3 )则关闭连接
服务端
(1)SOFABOLT 心跳检测服务端默认基于 IdleStateHandler(0,0, 90000 ms) 即 90 秒没有读或者写操作为空闲,调用ServerIdleHandler的userEventTriggered() 方法触发关闭连接。
(2)SOFABOLT 心跳检测由客户端在没有对 TCP 有读或者写操作后触发定时发送心跳消息,服务端接收到响应。客户端 15 秒/服务端 90 秒心跳检测,服务端不会运行到 90 秒仍旧没有任何读写操作的,只有客户端下线或抛异常时等待 90 秒过后服务端主动关闭与客户端连接。如果是 tcp-keepalive 需要等到 90秒之后,在此期间则为读写异常。
服务端一旦产生 IDLE,客户端不可用,直接断连。
3. SOFARPC 连接管理断开重连实现
每次 RPC 调用过程都校验是否有可用连接,不需要断链与重连,没有则新建。但一些场景是需要断链和保持长连接:
自动断连:通过 LVS VIP 或者 F5 建立多个连接,网络设备负载均衡机制,可能某些连接固定映射到了某几台后端的 RS 上,需要自动断连然后重连,靠随机性实现终负载均衡。需配合重连使用。
重连:客户端发起建连后由服务端通过双工通信发起请求到客户端,没有重连无法实现。
连接管理是客户端逻辑,启动好,连接管理开启异步线程。
(1)SOFARPC 连接管理 ConnectionHolder 客户端列表 aliveConnections(维护存活) 和 retryConnections(失败待重试),RPC 启动守护线程以默认 10 秒的间隔检查存活和失败待重试的客户端列表的可用连接:
存活列表不可用则需要放到待重试列表 retryConnections ;遍历retryConnections 连接命中重连周期则重连;重连成功放到存活列表 ,多次失败丢弃。
核心代码在连接管理器的方法中:com.alipay.sofa.rpc.client.AllConnectConnectionHolder#doReconnect