Ai_GirlFriend/开发/2026年2月3日/PHP连接泄漏问题修复.md

386 lines
8.6 KiB
Markdown
Raw Normal View 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`
```batch
restart_php_service.bat
```
这个脚本会:
1. 检查当前 PHP 服务状态
2. 停止所有 PHP 服务进程
3. 等待端口释放
4. 启动新的 PHP 服务
### 方法 2手动重启
```bash
# 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
```
### 验证服务已重启
```bash
# 检查服务状态
netstat -ano | findstr :30100
# 应该只看到 LISTENING 状态,没有 CLOSE_WAIT
```
---
## 监控连接状态
### 使用监控脚本
双击运行 `monitor_php_connections.bat`
```batch
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**
- 访问https://nginx.org/en/download.html
- 下载稳定版Stable version
2. **下载 PHP非线程安全版本**
- 访问https://windows.php.net/download/
- 下载 NTS (Non Thread Safe) 版本
3. **配置 PHP-FPM**
创建 `php-cgi.bat`
```batch
@echo off
cd C:\php
php-cgi.exe -b 127.0.0.1:9000
```
4. **配置 Nginx**
编辑 `nginx.conf`
```nginx
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;
}
}
```
5. **启动服务**
```batch
# 启动 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 计划任务命令
```batch
schtasks /create /tn "重启PHP服务" /tr "C:\path\to\restart_php_service.bat" /sc hourly /mo 4
```
### 方案 3优化 Python 请求代码
`lover/deps.py` 中优化 HTTP 请求:
```python
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 调用的耗时:
```python
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 服务状态:
```python
@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 接口:
```bash
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
---
## 快速参考
### 检查连接状态
```bash
netstat -ano | findstr :30100
```
### 重启 PHP 服务
```bash
restart_php_service.bat
```
### 监控连接
```bash
monitor_php_connections.bat
```
### 停止所有服务
```bash
stop_all_services.bat
```
### 启动所有服务
```bash
start_all_services.bat
```
---
## 总结
1. **问题根源**PHP 内置服务器连接泄漏
2. **临时方案**:定期重启 PHP 服务
3. **长期方案**:使用 Nginx + PHP-FPM
4. **监控措施**:使用监控脚本定期检查
建议尽快切换到 Nginx + PHP-FPM彻底解决连接泄漏问题