网页长时间静置后,重新发送请求为何第一次一定会超时?

TCP 会话保持技术

问题起因

在项目开发过程中,我曾遇到过一个难以解决的数据库连接池问题。无论如何配置,始终存在连接异常或被关闭的情况。直到最近偶然阅读了一篇关于负载均衡与会话保持的文章,里面详细介绍了各大云服务商对 HTTP 连接和 TCP 连接的限制。我这才意识到:MySQL 连接池底层依赖的是 TCP 连接,自然也会受到 TCP 空闲连接超时机制的影响。

那么会不会是因为连接在长时间空闲后被云服务器强制断开,导致连接池中的部分连接失效,进而引发连接异常?

其实早在之前排查问题时我就很疑惑:我的数据库服务器上,wait_timeoutinteractive_timeout 都已经设置为了 28800 秒(8 小时),理论上不应该自动断开连接。但问题依旧频繁出现。

直到看到了这篇文章才意识到:即使没有主动购买负载均衡服务,云服务商通常也会在内部做默认的连接管理。一般在各自的服务商的手册中会提到各种连接的默认配置

TCP 连接超时时间默认为 900 秒,即在没有数据传输的情况下,900 秒后将主动断开连接。

由此推断:并不是数据库本身断开连接,而是 TCP 层在云服务器层面超时回收了连接。


解决方法

找到了问题根源后,我们便可以有针对性地采取多种方式来避免 TCP 连接被过早断开。

方法一:缩短连接池连接生命周期(maxLifetime

通过设置 HikariCP 的 maxLifetime 参数,使连接在被云服务断开前就主动关闭并重建,从而避免使用已被云服务器关闭的连接

例如,设置连接最大存活时间为 850 秒(小于 900 秒):

hikari:
  max-lifetime: 850000

单位为毫秒,即 850 秒,略小于腾讯云的 900 秒超时阈值。


方法二:修改 TCP KeepAlive 探活参数

如果我们希望保持长时间的 TCP 活跃连接,操作系统层面提供了 TCP KeepAlive 机制。我们可以通过修改内核参数,使系统在连接空闲时周期性发送探测包,从而避免被认为是“死连接”。

1. 查看当前 TCP 探活参数:

# 查看 TCP KeepAlive 各参数
sysctl net.ipv4.tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_intvl
sysctl net.ipv4.tcp_keepalive_probes

输出示例:

$ sysctl net.ipv4.tcp_keepalive_time
net.ipv4.tcp_keepalive_time = 7200	# 默认 2 小时才开始探活
$ sysctl net.ipv4.tcp_keepalive_intvl
net.ipv4.tcp_keepalive_intvl = 75	# 探活间隔 75 秒
$ sysctl net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_probes = 9	# 最多尝试 9 次探活失败才判定断开

2. 修改配置:

编辑系统配置文件:

sudo vim /etc/sysctl.conf

添加或修改以下配置项:

net.ipv4.tcp_keepalive_time = 600     # 10分钟后开始探活
net.ipv4.tcp_keepalive_intvl = 30     # 每30秒探测一次
net.ipv4.tcp_keepalive_probes = 5     # 最多尝试5次

保存后使配置生效:

sudo sysctl -p

修改这些参数可以显著提高 TCP 长连接的存活能力,但实际效果仍取决于各个云服务商的网络策略是否允许。


小问题解答

Q1:为什么即便设置了 HikariCP 的 keepaliveTime 参数,TCP 连接仍然可能被断开?

这是因为:

  • HikariCP 的 keepaliveTime 是应用层机制,主要用于在连接空闲时执行一次轻量级 SQL 查询(如 SELECT 1),以防数据库关闭连接。
  • 它并不能控制底层 TCP 层是否发送 KeepAlive 探测包。

也就是说,Hikari 保活只影响连接池和数据库之间的交互,并不能干预 TCP 层连接的存活状态。

因此,如果云服务器在 TCP 层判断连接空闲并主动断开,即便 Hikari 的连接仍“活着”,实质上底层 TCP 已经断开了,继续使用该连接将仍然抛出异常。


欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1701220998@qq.com
导航页 GitHub