tcp 长连接保活注意点

tcp 长连接一直是提升系统性能的重要手段. 不过在复杂网络或者请求很少的时候, 也会通过保活等方式维持连接的建立. 保活可以用来探测连接, 进而可能释放掉已经无效的连接. 在一些跨网以及含有硬防的网络环境中, 一些网络设置可能默认会 kill 掉长时间空闲的 tcp 会话, 这种情况下 tcp 连接的保活就显得更为重要. 本文则记录了 tcp 连接保活的几点事项.

保活的方式

通常包含系统层应用层两种保活方式.

系统层

系统层主要是 tcp keepalive 方式, 在 socket 层开启 SO_KEEPALIVE 选项, 主要有以下实现方式:

1. 框架实现      - 部分框架的网络库可能默认开启;
2. socket 自定义 - 可以自定义保活的时间间隔以及探活次数;
3. linux 系统    - 程序的 socket 需开启 SO_KEEPALIVE 选项, 开启后遵循系统设置;

备注: 较新版本的 golang, 默认就开启了 tcp keepalive, 且每 15s 保活一次. 更多见: golang-issue-48622.

应用层

应用层的保活建立在该连接可以定期请求回复的基础上, 比如很多语言将 select 1 作为 MySQL 的保活查询语句. 但是单个请求的执行时间很长, 则不能在应用层保活. 比如 select sleep(3000), 在 SQL 执行完之前, 同一连接是不会有其它数据交互的, 如果网络层有会话超时限制, 该连接就会被 kill.

问题汇总

硬防注意事项

硬防如果直接删掉 tcp 的会话, 对应的 client 或 server 段可能不会收到 FIN 报文, 其相应的 tcp 连接状态就不会变. 这也是我们可能会碰到下面场景的原因之一:

1. server 端  tcp 连接还是  ESTABLISHED, 但是 client 没有 tcp 连接;
2. client, server 端的 tcp 连接都是 ESTABLISHED,但是数据交互超时;

tcp keepalive 怎么实现

系统层保活是一种在不影响数据流内容的情况下探测对方的方式. 探测报文是一个空的数据包(或只包含一个字节). 它的序列号等于对方主机发送的 ACK 报文的最大序列号减 1. 因为这一序列号的数据段已经被成功接收, 所以不会对到达的报文段产生影响, 但探测返回的响应可以确定连接是否正常.

可以自行 tcpdump 抓包, 关注 TCP Keep-Alive 报文的详细格式.

tcp keepalive 可能不生效

这点可能经常出现在 web 环境中, 比如下面的结构:

  nginx(DC1)    ->  tomcat(DC1) -> nginx(DC2) -> tomcat(DC2)

如果在 nginx(DC1) 开启 tcp 的 keepalive(proxy_socket_keepalive) 特性, 在上述的流程中, tcp 的保活仅能在 DC1 的流程中. tomcat(DC1) 本身只转发 http 等应用层数据, 所以 DC2 的组件并不会收到 tcp 层的数据包. 如果是应用层开启保活, 比如 tomcat(DC1) 以下面的方式开启保活, DC2 的 nginx 就能正常收到保活请求:

CloseableHttpClient httpClient = HttpClients.createDefault()
HttpParams params = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(params, 3600000); //connection Timeout
HttpConnectionParams.setSoTimeout(params, 3600000); // Socket Time out
HttpConnectionParams.setSoKeepalive(params, true); //Enable Socket level keep alive time

tcp keepalive 哪边来实现

同样以上述流程为例, 系统层的保活既可以在 tomcat(DC1), 也可以在 tomcat(DC2) 层实现. 等同保活可以在 client 或 server 端实现. 分别说明如下:

1. client 端实现, 则由 client 端发空包到 server 端, server 端响应;
2. server 端实现, 则由 server 端发空包到 client 端, client 端响应;

通常 server 端实现更多, 哪边实现可以由具体的架构决定.

linux 系统参数

系统层主要受以下三个系统参数的影响:

net.ipv4.tcp_keepalive_time   - tcp 连接闲置的时长, 从该连接最后一个报文的时间算起, 默认 2 小时(7200 秒);
net.ipv4.tcp_keepalive_intvl  - tcp 探测包的发送间隔, 默认 75 秒;
net.ipv4.tcp_keepalive_probes - 如果对方不应答, 探测包的发送次数, 默认 9 次;

默认为 tcp 连接空闲超过 2 小时则开始发送探测包, 在负责网络环境中可以调低 net.ipv4.tcp_keepalive_time 的值.

参考

linux-keepalive-howto