1. 问题背景
在使用Python的uvicorn库部署ASGI应用时,set_timeout_graceful_shutdown方法常用于实现优雅停机(Graceful Shutdown)。然而,开发者常遇到一个棘手问题:进程阻塞,即服务无法在预期时间内停止,导致资源无法释放或后续部署失败。
2. 问题现象
- 调用
set_timeout_graceful_shutdown后,服务未在超时时间内终止。 - 日志中显示“Waiting for connections to close”但长期无进展。
- 手动终止进程时触发SIGKILL信号,可能丢失部分请求数据。
3. 根本原因分析
该问题通常由以下因素导致:
- 长连接未关闭:WebSocket或HTTP长轮询连接未主动断开。
- 任务未完成:后台异步任务(如Celery作业)未处理完毕。
- 资源竞争:数据库连接池或文件锁未被释放。
- 信号处理冲突:与其他信号处理器(如SIGTERM)发生竞争。
4. 解决方案
4.1 强制终止长连接
# 在ASGI应用中主动关闭WebSocket连接
async def websocket_disconnect_handler():
for connection in active_websockets:
await connection.close(code=1001)
4.2 调整超时参数
通过uvicorn.Config增加graceful_timeout和timeout_keep_alive:
config = uvicorn.Config(
app,
graceful_timeout=15, # 默认10秒
timeout_keep_alive=5 # 防止Keep-Alive阻塞
)
4.3 使用进程监控工具
结合Supervisor或Gunicorn实现二次保护:
# Supervisor配置示例
[program:myapp]
stopwaitsecs=30
killasgroup=true
5. 高级调试技巧
| 工具 | 用途 |
|---|---|
| lsof -i :8000 | 检查未关闭的套接字 |
| strace -p PID | 跟踪进程系统调用 |
| asyncpg连接池日志 | 分析数据库连接泄漏 |
6. 最佳实践
建议在Kubernetes环境中结合preStop钩子和readiness探针:
# Kubernetes部署片段
lifecycle:
preStop:
exec:
command: ["sh", "-c", "kill -SIGTERM 1"]