问题描述
在处理高并发转录任务时,系统出现了数据库连接池溢出的问题。分析代码后发现,每个任务在处理过程中都会尝试获取数据库连接,但由于任务处理时间较长,导致连接无法及时释放,从而超出了连接池的限制,引发连接超时错误。
QueuePool limit of size 20 overflow 10 reached, connection timed out, timeout 30.00
代码分析
数据库连接相关代码
async def create_task(request_data: dict = None) -> str:
async with get_db() as db:
task_id = str(uuid.uuid4())
new_task = Task(
id=task_id,
status='pending',
request_data=request_data
)
db.add(new_task)
await db.commit()
return task_id
并发任务处理代码
async def process_transcribe_request(task_id: str, transcribe_data: TranscribeSchema, test_mode: bool):
Logger.debug(f"开始处理转录请求,任务ID: {task_id}")
try:
if not transcribe_data.callback_url:
transcribe_data.callback_url = "XXXXXXX"
Logger.debug(f"使用默认回调地址: {transcribe_data.callback_url}")
task = await get_task_status(task_id)
if task and task["status"] == "pending":
Logger.info(f"检测到待处理任务: {task_id}")
await check_and_process_task(task_id, transcribe_data, transcribe_data.callback_url, test_mode)
except Exception as e:
Logger.error(f"处理转录请求时发生错误: {str(e)}")
await update_task(task_id, "failed", str(e))
async def check_and_process_task(task_id: str, data: TranscribeSchema, callback_url: str, test_mode: bool):
while True:
task = await get_task_status(task_id)
Logger.debug(f"检查任务 {task_id} 状态: {task['status']}")
if task and task["status"] == "pending":
processing_tasks = await get_processing_tasks_count()
Logger.debug(f"当前正在处理的任务数量: {processing_tasks}")
if processing_tasks < 3:
if await start_processing_task(task_id):
Logger.debug(f"开始处理任务 {task_id}")
await _process_transcribe_task(task_id, data, callback_url, test_mode)
Logger.debug(f"任务 {task_id} 处理完成")
break
else:
Logger.debug(f"当前正在处理的任务数量达到上限,任务 {task_id} 处于待处理状态,等待30秒后再次检查")
await asyncio.sleep(30)
else:
Logger.debug(f"任务 {task_id} 状态为 {task['status']},退出检查循环")
break
问题分析
- SQLAlchemy 的连接池默认大小为 20,最大溢出为 10。
- 每个任务都会尝试获取数据库连接,处理时间较长导致连接无法及时释放。
- 并发请求超过连接池限制时,出现连接超时错误。
解决方案
1. 增加连接池配置
from sqlalchemy.pool import QueuePool
engine = create_engine(
DATABASE_URL,
poolclass=QueuePool,
pool_size=30, # 增加连接池大小
max_overflow=20, # 增加最大溢出连接数
pool_timeout=60, # 增加连接超时时间
pool_pre_ping=True # 启用连接健康检查
)
2. 优化数据库连接的使用
- 确保在完成数据库操作后及时关闭连接。
- 使用连接池复用而不是频繁创建新连接。
- 考虑使用批量操作减少连接占用时间。
3. 实现连接重试机制
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=4, max=10)
)
async def get_db_connection():
async with get_db() as db:
return db
4. 考虑使用队列控制并发任务数量
这部分代码已经实现了任务队列,但可以考虑根据系统资源调整并发数量。
5. 监控连接池状态
def monitor_pool_status():
"""监控数据库连接池状态"""
return {
"pool_size": engine.pool.size(),
"checkedin": engine.pool.checkedin(),
"overflow": engine.pool.overflow(),
"checkedout": engine.pool.checkedout(),
}