Ai_GirlFriend/开发/2026年2月3日/PHP连接泄漏问题修复.md
2026-02-04 18:47:56 +08:00

8.6 KiB
Raw Blame History

PHP 连接泄漏问题修复

问题描述

Python 后端调用 PHP 后端接口时出现超时错误:

HTTPConnectionPool(host='192.168.1.164', port=30100): Read timed out. (read timeout=5)

问题根源

通过 netstat -ano | findstr :30100 检查发现:

  • PHP 服务PID 23736 和 1416有 30+ 个 CLOSE_WAIT 连接
  • CLOSE_WAIT 状态表示:客户端已关闭连接,但服务器端未关闭
  • 这是典型的连接泄漏问题

为什么会出现 CLOSE_WAIT

  1. PHP 内置开发服务器的限制

    • php -S 是单线程服务器,设计用于开发测试
    • 在处理大量并发请求时容易出现连接泄漏
    • 长时间运行会导致资源耗尽
  2. 连接未正确关闭

    • 客户端Python发送请求后关闭连接
    • 服务器端PHP没有正确关闭 socket
    • 连接进入 CLOSE_WAIT 状态并一直保持
  3. 资源耗尽

    • 大量 CLOSE_WAIT 连接占用系统资源
    • 导致新请求无法处理或响应缓慢
    • 最终导致超时错误

临时解决方案:重启 PHP 服务

方法 1使用快速重启脚本推荐

双击运行 restart_php_service.bat

restart_php_service.bat

这个脚本会:

  1. 检查当前 PHP 服务状态
  2. 停止所有 PHP 服务进程
  3. 等待端口释放
  4. 启动新的 PHP 服务

方法 2手动重启

# 1. 查看当前 PHP 进程
netstat -ano | findstr :30100

# 2. 停止所有 PHP 进程(替换 PID
taskkill /F /PID 23736
taskkill /F /PID 1416

# 3. 等待 2 秒

# 4. 启动新的 PHP 服务
cd C:\Users\Administrator\Desktop\Project\AI_GirlFriend\xunifriend_RaeeC\public
php -S 192.168.1.164:30100

验证服务已重启

# 检查服务状态
netstat -ano | findstr :30100

# 应该只看到 LISTENING 状态,没有 CLOSE_WAIT

监控连接状态

使用监控脚本

双击运行 monitor_php_connections.bat

monitor_php_connections.bat

这个脚本会每 5 秒刷新一次,显示:

  • 所有连接状态
  • ESTABLISHED 连接数(正常活跃连接)
  • CLOSE_WAIT 连接数(连接泄漏)
  • TIME_WAIT 连接数(正常关闭中)

判断标准

  • 正常CLOSE_WAIT < 10 个
  • 注意CLOSE_WAIT 10-20 个(需要关注)
  • 警告CLOSE_WAIT > 20 个(建议立即重启)

长期解决方案

方案 1使用 Nginx + PHP-FPM推荐

PHP 内置服务器不适合生产环境,建议使用 Nginx + PHP-FPM。

安装步骤

  1. 下载 Nginx for Windows

  2. 下载 PHP非线程安全版本

  3. 配置 PHP-FPM

创建 php-cgi.bat

@echo off
cd C:\php
php-cgi.exe -b 127.0.0.1:9000
  1. 配置 Nginx

编辑 nginx.conf

server {
    listen 30100;
    server_name 192.168.1.164;
    root C:/Users/Administrator/Desktop/Project/AI_GirlFriend/xunifriend_RaeeC/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}
  1. 启动服务
# 启动 PHP-FPM
start php-cgi.bat

# 启动 Nginx
cd C:\nginx
start nginx.exe

优点

  • 支持多进程,性能更好
  • 连接管理更稳定
  • 适合生产环境
  • 不会出现连接泄漏

方案 2定期自动重启 PHP 服务

如果暂时无法切换到 Nginx可以设置定时任务自动重启 PHP 服务。

创建定时任务

  1. 打开"任务计划程序"Task Scheduler
  2. 创建基本任务
  3. 设置触发器:每 4 小时
  4. 操作:启动程序 restart_php_service.bat

或使用 Windows 计划任务命令

schtasks /create /tn "重启PHP服务" /tr "C:\path\to\restart_php_service.bat" /sc hourly /mo 4

方案 3优化 Python 请求代码

lover/deps.py 中优化 HTTP 请求:

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# 创建带重试和连接池的 session
def get_http_session():
    session = requests.Session()
    
    # 配置重试策略
    retry = Retry(
        total=3,
        backoff_factor=0.3,
        status_forcelist=[500, 502, 503, 504]
    )
    
    # 配置连接池
    adapter = HTTPAdapter(
        max_retries=retry,
        pool_connections=10,
        pool_maxsize=20,
        pool_block=False
    )
    
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    
    return session

# 使用 session
def _fetch_user_from_php(token: str) -> Optional[dict]:
    """通过 PHP/FastAdmin 接口获取用户信息。"""
    import logging
    logger = logging.getLogger(__name__)
    
    user_info_api = "http://192.168.1.164:30100/api/user_basic/get_user_basic"
    
    logger.info(f"用户中心调试 - 调用接口: {user_info_api}")
    
    try:
        session = get_http_session()
        resp = session.get(
            user_info_api,
            headers={
                "token": token,
                "Connection": "close"  # 明确关闭连接
            },
            timeout=10,  # 增加超时时间
        )
        logger.info(f"用户中心调试 - 响应状态码: {resp.status_code}")
        
        # 确保连接关闭
        resp.close()
        
    except requests.exceptions.Timeout:
        logger.error(f"用户中心调试 - 请求超时")
        raise HTTPException(
            status_code=status.HTTP_504_GATEWAY_TIMEOUT,
            detail="用户中心接口超时",
        )
    except Exception as exc:
        logger.error(f"用户中心调试 - 请求异常: {exc}")
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail="用户中心接口不可用",
        ) from exc
    
    # ... 其余代码

预防措施

1. 监控连接状态

定期运行 monitor_php_connections.bat 检查连接状态。

2. 设置告警

当 CLOSE_WAIT 连接数超过阈值时,发送告警通知。

3. 日志记录

在 Python 代码中记录每次 PHP 调用的耗时:

import time

start_time = time.time()
resp = requests.get(...)
elapsed_time = time.time() - start_time

logger.info(f"PHP 接口调用耗时: {elapsed_time:.2f}秒")

if elapsed_time > 3:
    logger.warning(f"PHP 接口响应缓慢: {elapsed_time:.2f}秒")

4. 健康检查

添加健康检查端点,定期检查 PHP 服务状态:

@app.get("/health/php")
async def check_php_health():
    try:
        resp = requests.get(
            "http://192.168.1.164:30100/api/health",
            timeout=2
        )
        return {
            "status": "healthy" if resp.status_code == 200 else "unhealthy",
            "response_time": resp.elapsed.total_seconds()
        }
    except:
        return {"status": "down"}

常见问题

Q1: 为什么会有两个 PHP 进程PID 23736 和 1416

A: 可能是之前启动了多次 PHP 服务,导致有多个进程在监听同一端口。建议:

  1. 停止所有 PHP 进程
  2. 只启动一个 PHP 服务

Q2: 重启后还是有 CLOSE_WAIT 怎么办?

A:

  1. 确认已停止所有旧的 PHP 进程
  2. 检查是否有其他程序占用端口
  3. 考虑更换端口或使用 Nginx

Q3: 如何判断是 PHP 问题还是 Python 问题?

A:

  1. 使用 curl 直接测试 PHP 接口:
    curl -X GET "http://192.168.1.164:30100/api/user_basic/get_user_basic" -H "token: YOUR_TOKEN"
    
  2. 如果 curl 正常,说明是 Python 客户端问题
  3. 如果 curl 也慢,说明是 PHP 服务器问题

Q4: 生产环境应该用什么?

A:

  • 不推荐php -S(仅用于开发)
  • 推荐Nginx + PHP-FPM
  • 备选Apache + mod_php

快速参考

检查连接状态

netstat -ano | findstr :30100

重启 PHP 服务

restart_php_service.bat

监控连接

monitor_php_connections.bat

停止所有服务

stop_all_services.bat

启动所有服务

start_all_services.bat

总结

  1. 问题根源PHP 内置服务器连接泄漏
  2. 临时方案:定期重启 PHP 服务
  3. 长期方案:使用 Nginx + PHP-FPM
  4. 监控措施:使用监控脚本定期检查

建议尽快切换到 Nginx + PHP-FPM彻底解决连接泄漏问题