VWED_server/services/enhanced_scheduler/priority_queue_manager.py
2025-05-12 15:43:21 +08:00

258 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
优先级队列管理模块
实现动态阈值多级队列和平衡调度策略
"""
import asyncio
import logging
import heapq
from typing import Dict, List, Any, Optional, Tuple, Set
from datetime import datetime, timedelta
from collections import defaultdict
from utils.logger import get_logger
# 获取日志记录器
logger = get_logger("services.enhanced_scheduler.priority_queue_manager")
class PriorityQueueManager:
"""
优先级队列管理器
实现动态阈值多级队列和平衡调度策略
"""
def __init__(self,
queue_count: int = 3,
threshold_percentiles: List[float] = None,
worker_ratios: List[float] = None,
rebalance_interval: int = 300):
"""
初始化优先级队列管理器
Args:
queue_count: 队列数量
threshold_percentiles: 队列阈值百分比,如[0.1, 0.3, 1.0]表示前10%、接下来20%、剩余的70%
worker_ratios: 工作线程分配比例,如[0.6, 0.3, 0.1]
rebalance_interval: 重新平衡间隔(秒)
"""
self.queue_count = queue_count
self.threshold_percentiles = threshold_percentiles
self.worker_ratios = worker_ratios
self.rebalance_interval = rebalance_interval
# 验证配置
if len(self.threshold_percentiles) != queue_count:
raise ValueError(f"阈值百分比数量({len(self.threshold_percentiles)})必须等于队列数量({queue_count})")
if len(self.worker_ratios) != queue_count:
raise ValueError(f"工作线程比例数量({len(self.worker_ratios)})必须等于队列数量({queue_count})")
if sum(self.worker_ratios) != 1.0:
raise ValueError(f"工作线程比例总和必须等于1.0,当前为{sum(self.worker_ratios)}")
# 初始化多级队列
self.priority_queues = [asyncio.PriorityQueue() for _ in range(queue_count)]
# 优先级阈值,初始为默认值
self.priority_thresholds = [1, 5, 10]
# 优先级映射表 {task_id: priority}
self.priority_map = {}
# 最后一次重新平衡时间
self.last_rebalance_time = datetime.now()
# 优先级统计信息
self.priority_stats = []
logger.info(f"初始化优先级队列管理器: 队列数={queue_count}, 阈值百分比={self.threshold_percentiles}, "
f"工作线程比例={self.worker_ratios}")
async def enqueue(self, task_id: str, priority: int) -> int:
"""
将任务添加到适当的优先级队列
Args:
task_id: 任务ID
priority: 任务优先级
Returns:
int: 队列索引
"""
# 检查是否需要重新平衡
await self._check_rebalance()
# 确定任务应该进入哪个队列
queue_index = self._get_queue_index(priority)
# 保存优先级信息
self.priority_map[task_id] = priority
self.priority_stats.append(priority)
# 添加到队列(使用负优先级值)
await self.priority_queues[queue_index].put((-priority, task_id))
logger.info(f"任务 {task_id} (优先级 {priority}) 添加到队列 {queue_index}")
return queue_index
async def dequeue(self, worker_id: int, worker_count: int) -> Tuple[int, Any]:
"""
从适当的队列中获取任务
Args:
worker_id: 工作线程ID
worker_count: 工作线程总数
Returns:
Tuple[int, Any]: (队列索引, 任务数据)
"""
# 确定工作线程应该从哪个队列获取任务
queue_index = self._get_worker_queue(worker_id, worker_count)
# 检查指定队列是否有任务
if not self.priority_queues[queue_index].empty():
item = await self.priority_queues[queue_index].get()
return queue_index, item
# 如果指定队列为空,尝试从其他队列获取任务
for i in range(self.queue_count):
if not self.priority_queues[i].empty():
item = await self.priority_queues[i].get()
return i, item
# 所有队列都为空返回None
return -1, None
def task_done(self, queue_index: int) -> None:
"""
标记任务完成
Args:
queue_index: 队列索引
"""
if 0 <= queue_index < self.queue_count:
self.priority_queues[queue_index].task_done()
def remove_task(self, task_id: str) -> bool:
"""
从优先级映射表中移除任务
Args:
task_id: 任务ID
Returns:
bool: 是否成功移除
"""
if task_id in self.priority_map:
self.priority_map.pop(task_id)
return True
return False
async def _check_rebalance(self) -> None:
"""
检查是否需要重新平衡队列阈值
"""
now = datetime.now()
if (now - self.last_rebalance_time).total_seconds() > self.rebalance_interval:
await self._rebalance_thresholds()
self.last_rebalance_time = now
async def _rebalance_thresholds(self) -> None:
"""
重新平衡优先级阈值
基于历史优先级分布动态调整阈值
"""
if len(self.priority_stats) < 10:
# 数据不足,使用默认值
return
# 复制并排序优先级数据
sorted_priorities = sorted(self.priority_stats, reverse=True)
# 根据百分比计算新阈值
new_thresholds = []
for percentile in self.threshold_percentiles[:-1]: # 最后一个阈值不需要计算
index = min(int(len(sorted_priorities) * percentile), len(sorted_priorities) - 1)
new_thresholds.append(sorted_priorities[index])
# 添加最小阈值1确保所有任务都能分配到队列
new_thresholds.append(1)
# 更新阈值
if new_thresholds != self.priority_thresholds:
old_thresholds = self.priority_thresholds.copy()
self.priority_thresholds = new_thresholds
logger.info(f"优先级阈值更新: {old_thresholds} -> {new_thresholds}")
# 限制历史数据大小,保留最近的数据
if len(self.priority_stats) > 1000:
self.priority_stats = self.priority_stats[-1000:]
def _get_queue_index(self, priority: int) -> int:
"""
根据优先级确定队列索引
Args:
priority: 任务优先级
Returns:
int: 队列索引
"""
for i, threshold in enumerate(self.priority_thresholds):
if priority >= threshold:
return i
# 默认放入最低优先级队列
return self.queue_count - 1
def _get_worker_queue(self, worker_id: int, worker_count: int) -> int:
"""
根据工作线程ID确定应该从哪个队列获取任务
Args:
worker_id: 工作线程ID
worker_count: 工作线程总数
Returns:
int: 队列索引
"""
# 计算每个队列的工作线程数量
worker_counts = [int(ratio * worker_count) for ratio in self.worker_ratios]
# 补充剩余的工作线程到最后一个队列
remaining = worker_count - sum(worker_counts)
worker_counts[-1] += remaining
# 确定工作线程应该处理哪个队列
current_count = 0
for i, count in enumerate(worker_counts):
current_count += count
if worker_id < current_count:
return i
# 默认返回最低优先级队列
return self.queue_count - 1
def get_queue_sizes(self) -> List[int]:
"""
获取各队列大小
Returns:
List[int]: 队列大小列表
"""
return [queue.qsize() for queue in self.priority_queues]
def get_queue_status(self) -> Dict[str, Any]:
"""
获取队列状态信息
Returns:
Dict[str, Any]: 队列状态
"""
return {
"queue_count": self.queue_count,
"thresholds": self.priority_thresholds,
"queue_sizes": self.get_queue_sizes(),
"worker_ratios": self.worker_ratios,
"task_count": len(self.priority_map),
"last_rebalance": self.last_rebalance_time.strftime("%Y-%m-%d %H:%M:%S")
}