跨网访问云 Oracle 连接假死问题笔记

2 阅读4分钟

适用场景:

  • 学校 / 政务系统
  • 应用部署在内网服务器
  • Oracle 数据库部署在云上,通过跨网络访问
  • 低并发(寒假、夜间)时偶发接口卡死、16 分钟无响应

一、问题现象(我们遇到的真实情况)

  • 应用服务进程正常,没有崩溃
  • 访问 Oracle 的接口长时间无响应(10~20 分钟)
  • 最终连接池耗尽,系统“假死”
  • 同一系统访问 内网 DM 数据库完全正常
  • 重启应用后短时间内恢复
  • 问题多发生在 寒假 / 夜间低流量时段

二、核心结论(一句话定性)

这是“内网系统跨网络访问云 Oracle,在低流量、长空闲场景下产生 TCP 半开 / 黑洞连接,导致 JDBC 读操作无限等待”的典型工程问题。

  • 不是业务代码 Bug
  • 不是 Oracle 性能问题
  • 不是并发太高
  • 网络拓扑 + 长连接机制 + 缺少读超时 共同导致

三、为什么只有 Oracle 出问题,DM 没问题

1. 网络路径不同

  • DM:

    • 应用服务器 → 内网 → 内网 DM
    • 无跨网、无 NAT、无云边界
  • Oracle:

    • 应用服务器 → 政务出口 → 防火墙 / NAT → 云网络 → 云 Oracle

2. 结论

  • 内网数据库:连接要么活,要么立刻断
  • 跨网数据库:可能出现“看起来还在,实际上已不可用”的连接

四、问题的本质:什么是“僵尸连接”

1. 连接的真实状态

  • Java / 连接池认为:连接还活着
  • 操作系统认为:TCP 仍是 ESTABLISHED
  • 中间网络设备:已不再可靠转发业务数据
  • Oracle 可能根本收不到请求

这种状态称为:

TCP 半开 / 黑洞连接(Zombie Connection)

2. 最危险的点

  • JDBC 默认 read()无限等待
  • 线程被卡住
  • 连接无法归还连接池
  • 多个请求后导致整个系统假死

五、为什么低流量(寒假)更容易出问题

  • 连接长期空闲(idle)
  • 防火墙 / NAT 会对长时间无流量的 TCP 会话进行“静默老化”
  • 不发送 FIN / RST
  • 下一次使用该连接时才暴露问题

人少不是原因,长时间不用才是触发条件


六、已经采取过的方案:TCP keepalive(有效但不充分)

1. 内核参数配置

net.ipv4.tcp_keepalive_time = 300

net.ipv4.tcp_keepalive_intvl = 60

net.ipv4.tcp_keepalive_probes = 5

2. 作用

  • 约 10 分钟内清理“真正已经断掉的 TCP 连接”
  • 对网络断连、对端消失等问题有效

3. 局限性

  • 只能检测 TCP 层是否活着
  • 无法发现“TCP 活着,但 Oracle 应用层不返回数据”的情况

七、为什么必须设置 JDBC 读超时(关键结论)

1. JDBC ReadTimeout 的含义

限制一次数据库读操作最多等待多久

  • 不是 SQL 总执行时间
  • 而是:多久内必须收到 Oracle 返回的任何数据

2. 不设置的后果

  • 网络黑洞 / Oracle 无响应时
  • 线程会无限等待(可达 10~20 分钟)
  • 连接池逐步被拖死

3. 工程铁律

任何跨网络访问的数据库连接,必须设置读超时,否则一定会在某个时间点出事故。


八、最终采用的解决方案(推荐方案)

1. JDBC URL 设置读超时(必须)

jdbc:oracle:thin:@//host:1521/orcl?oracle.jdbc.ReadTimeout=30000

含义:

  • 30 秒内无任何返回 → 本次请求失败
  • 连接被回收并重建
  • 系统不会被拖死

2. 调整连接池参数(低流量友好)

hikari:

max-pool-size: 20

min-idle: 2

max-lifetime: 840000 # 14 分钟

原则:

  • 少养连接
  • 快速淘汰老连接
  • 避免僵尸连接长期存在

3. TCP keepalive 作为底层补充(保留)

  • 清理真正的死连接
  • 作为网络层兜底
  • 不能替代 JDBC ReadTimeout

九、30 秒失败意味着什么(重要认知)

  • 只会在以下场景发生:

    • 网络异常
    • Oracle 无响应
    • SQL 被锁死
  • 后果:

    • 当前请求失败
    • 连接释放
    • 系统整体仍可用

允许请求失败,不允许系统假死


十、最终总结

本问题的根因是:

内网系统跨网络访问云 Oracle,在低流量场景下产生僵尸连接,JDBC 默认无限读等待导致线程和连接池被拖死。

通过 TCP keepalive + JDBC ReadTimeout + 连接池参数治理,可有效规避该类问题。