脚本怎样排查连接泄露问题

wen 实用脚本 27

《连接泄漏深度排查指南:从脚本原理到实战修复的完整方案》

目录导读

  1. 连接泄漏的本质与危害 – 理解问题的根源
  2. 典型场景与高危代码模式 – 哪些代码最容易引发泄漏
  3. 脚本排查五步法 – 可落地的操作流程
  4. 常用工具与监控指标 – 从日志到网络抓包
  5. 修复案例:从检测到闭环 – 真实场景的排雷过程
  6. 常见问答 – 开发团队最关心的5个问题

连接泄漏的本质与危害

连接泄漏指应用程序在使用数据库、HTTP、Redis、消息队列等资源时,未正确关闭连接,导致连接池被耗尽。泄漏的典型特征:系统运行一段时间后,响应变慢,连接超时,甚至OOM。

脚本怎样排查连接泄露问题

核心影响:

  • 资源枯竭:数据库连接数满,新请求被阻塞
  • 雪崩效应:一个服务泄漏,拖垮下游依赖
  • 成本上升:云厂商按连接数计费时,泄漏直接产生浪费

排查难点:

  • 泄漏往往是间歇性的,普通监控难以捕捉
  • 代码调用链复杂时,无法快速定位哪个模块未关闭连接

典型场景与高危代码模式

场景类型 危险代码模式 直接后果
数据库操作 未在finally/using块中关闭Connection 连接池耗尽
HTTP请求 未设置超时时间 + 未释放Response 连接句柄泄露
Redis操作 使用完Jedis未归还到池 客户端连接数暴涨
多个资源嵌套 外层异常时未关闭内层Connection 隐蔽泄漏

示例对比:

// 危险模式
var connection = new SqlConnection(connStr);
connection.Open();
// 使用... 如果中间抛异常,连接永远不关闭
// 正确模式
using var connection = new SqlConnection(connStr);
connection.Open();
// 无论是否异常,using块结束时自动关闭

脚本排查五步法

Step 1:确认泄漏存在(全局监控)

  • 执行命令查看连接数:
    netstat -an | grep 3306 | wc -l  (Linux)
    ss -s  (查看套接字统计)
  • 检查应用日志:Connection pool exhausted / Timeout waiting for idle object

Step 2:粗定位泄漏模块(脚本化快照)

编写临时脚本,定时采集连接信息:

import psutil, time, datetime
while True:
    connections = psutil.net_connections()
    # 筛选特定端口连接
    targets = [c for c in connections if c.laddr.port in [3306,6379]]
    print(f"{datetime.datetime.now()} - 连接数: {len(targets)}")
    # 打印本地端口分布
    for c in targets[:10]:
        print(f"  {c.laddr} -> {c.raddr} 状态:{c.status}")
    time.sleep(60)

运行30分钟,观察连接数是否持续增长不回落。

Step 3:关联代码调用栈(结合APM)

  • 使用开源的SkyWalking、Pinpoint或商业工具
  • 查看耗时最长的SQL或Redis操作
  • 重点关注:执行时间超过1分钟但未关闭的连接

Step 4:压力复现 + 抓包验证

  • 编写测试脚本模拟高并发请求
  • 同时抓包:tcpdump port 3306 -w leak.pcap
  • 使用Wireshark查看TCP挥手序列:正常连接应有FIN/ACK,泄漏连接只建立不释放

Step 5:日志注入精确追踪

在关键代码处添加临时日志:

def get_connection():
    import traceback
    logging.info(f"获取连接 - 调用栈: {''.join(traceback.format_stack()[:5])}")
    return pool.get_connection()

运行后导出发放的连接ID,与关闭的连接ID做集合差。


常用工具与监控指标

命令行工具集

工具 用途 命令示例
ss/lsof 查看进程打开句柄 lsof -i :3306 -nP
strace 追踪系统调用 strace -p PID -e trace=network
/proc文件系统 实时查看进程连接 cat /proc/PID/net/tcp

关键监控指标(Prometheus + Grafana)

  • process_open_fds – 文件描述符总数
  • db_connections_active – 活跃连接数
  • pool_wait_seconds – 连接等待时间
  • connection_close_failures – 关闭失败计数

脚本化检查清单(直接可执行的Shell)

#!/bin/bash
echo "=== 系统级连接检查 ==="
ss -tan state established | wc -l
echo "=== 应用进程连接详情 ==="
ps aux | grep -E "java|python|node" | awk '{print $2}' | xargs -I{} sh -c 'lsof -p {} -i TCP | wc -l'
echo "=== 数据库连接池状态 ==="
# 需要根据实际中间件调整,
# redis-cli info clients | grep connected_clients

修复案例:从检测到闭环

现象:某Java服务每2小时重启一次,重启后连接数从50逐步增长到500后崩溃。

排查过程

  1. 使用jstack抓线程栈,发现大量线程阻塞在getConnection()
  2. 按上述五步法编写脚本,发现连接数在每分钟平稳增长
  3. 配合APM日志,锁定某HTTP调用HttpClient.execute()后未关闭Response
  4. 代码审查发现:网络异常时未走到finally块

修复方案

// 修复前
HttpResponse response = httpClient.execute(request);
// 使用response后未关闭
// 修复后
try (CloseableHttpResponse response = httpClient.execute(request)) {
    // 自动close
}

效果:连接数稳定在50以内,服务连续运行15天未重启。


常见问答

Q1:如何快速区分正常连接和泄漏连接? A:连续观察30分钟,正常连接数应在波动的某个稳定范围(如30-50),泄漏连接会持续线性增长,且不会随请求结束而回落。

Q2:脚本排查时需要注意哪些陷阱? A:① 有些连接池会保持最小空闲连接(如10个),不算泄漏;② 长连接业务(如WebSocket)需单独判断;③ 监控脚本本身不要产生新连接,建议使用socket的本地端口观察。

Q3:使用ORM框架后是否还需要关注连接泄漏? A:是的,ORM只负责释放自己创建的连接,但开发者可能直接通过DataSource.getConnection()获取原生连接,分布式事务、多数据源场景极易遗漏关闭。

Q4:连接泄漏和线程泄漏如何区分? A:连接泄漏表现为连接数持续增长,线程泄漏表现为线程数持续增长,两者常伴随出现,因为未关闭的连接会阻塞线程,通过jstack/proc/PID/status查看线程数可辅助判断。

Q5:有没有统一的脚本模板可复用? A:可以直接使用下面的Python脚本框架,根据实际中间件调整过滤条件:

def check_leak(port_list, interval=60):
    import time, psutil
    baseline = set()
    while True:
        now = set()
        for conn in psutil.net_connections():
            if conn.laddr.port in port_list and conn.raddr:
                now.add((conn.laddr.port, conn.raddr))
        new_leaks = now - baseline
        if new_leaks:
            print(f"发现新增连接:{new_leaks}")
        baseline = now
        time.sleep(interval)

排查连接泄漏本质上是资源管理与代码健壮性的博弈,把脚本排查作为日常巡检工具,配合完善的异常处理和资源释放机制,才能从根本上杜绝这类问题,建议团队在CI/CD流程中加入连接数检测用例,让泄漏问题在测试阶段就被发现。

抱歉,评论功能暂时关闭!