refactor: 将 global_priority 和 rate_multiplier 改为按 API 格式配置

- 新增 global_priority_by_format 字段,支持按 API 格式设置全局优先级
- 移除已废弃的 rate_multiplier 字段,统一使用 rate_multipliers
- 移除已废弃的 timeout 字段(providers 和 provider_endpoints 表)
- 更新调度器以支持按格式的优先级排序
- 同步更新前后端类型定义和 API 接口
- 添加数据库迁移脚本,自动迁移现有数据
This commit is contained in:
fawney19
2026-01-16 17:53:27 +08:00
parent 6c98816f9f
commit d5d74339dd
18 changed files with 247 additions and 136 deletions

View File

@@ -670,7 +670,7 @@ class CacheAwareScheduler:
)
# 3. 应用优先级模式排序
candidates = self._apply_priority_mode_sort(candidates, affinity_key)
candidates = self._apply_priority_mode_sort(candidates, affinity_key, target_format.value)
# 更新指标
self._metrics["total_candidates"] += len(candidates)
@@ -693,7 +693,7 @@ class CacheAwareScheduler:
)
elif self.scheduling_mode == self.SCHEDULING_MODE_LOAD_BALANCE:
# 负载均衡模式:忽略缓存,同优先级内随机轮换
candidates = self._apply_load_balance(candidates)
candidates = self._apply_load_balance(candidates, target_format.value)
for candidate in candidates:
candidate.is_cached = False
else:
@@ -1188,50 +1188,57 @@ class CacheAwareScheduler:
logger.debug(f"[CacheAwareScheduler] 切换调度模式为: {self.scheduling_mode}")
def _apply_priority_mode_sort(
self, candidates: List[ProviderCandidate], affinity_key: Optional[str] = None
self, candidates: List[ProviderCandidate], affinity_key: Optional[str] = None,
api_format: Optional[str] = None
) -> List[ProviderCandidate]:
"""
根据优先级模式对候选列表排序(数字越小越优先)
- provider: 提供商优先模式,保持原有顺序(按 Provider.provider_priority -> Key.internal_priority 排序,已由查询保证)
Key.internal_priority 表示 Endpoint 内部优先级,同优先级内通过哈希分散负载均衡
- global_key: 全局 Key 优先模式,按 Key.global_priority 升序排序(数字小的优先)
global_priority 的优先NULL 的排后面
global_priority 内通过哈希分散实现负载均衡
- global_key: 全局 Key 优先模式,按 Key.global_priority_by_format 升序排序(数字小的优先)
优先级的优先NULL 的排后面
优先级内通过哈希分散实现负载均衡
"""
if not candidates:
return candidates
if self.priority_mode == self.PRIORITY_MODE_GLOBAL_KEY:
# 全局 Key 优先模式:按 global_priority 分组,同组内哈希分散负载均衡
return self._sort_by_global_priority_with_hash(candidates, affinity_key)
return self._sort_by_global_priority_with_hash(candidates, affinity_key, api_format)
# 提供商优先模式保持原有顺序provider_priority 排序已经由查询保证)
return candidates
def _sort_by_global_priority_with_hash(
self, candidates: List[ProviderCandidate], affinity_key: Optional[str] = None
self, candidates: List[ProviderCandidate], affinity_key: Optional[str] = None,
api_format: Optional[str] = None
) -> List[ProviderCandidate]:
"""
按 global_priority 分组排序,同优先级内通过哈希分散实现负载均衡
按 global_priority_by_format 分组排序,同优先级内通过哈希分散实现负载均衡
排序逻辑:
1. 按 global_priority 分组数字小的优先NULL 排后面)
2. 同 global_priority 组内,使用 affinity_key 哈希分散
1. 按 global_priority_by_format[api_format] 分组数字小的优先NULL 排后面)
2. 同优先级组内,使用 affinity_key 哈希分散
3. 确保同一用户请求稳定选择同一个 Key缓存亲和性
"""
import hashlib
from collections import defaultdict
# 按 global_priority 分组
def get_priority(candidate: ProviderCandidate) -> int:
"""获取候选的优先级"""
if not candidate.key:
return 999999
priority_by_format = candidate.key.global_priority_by_format or {}
if api_format and api_format in priority_by_format:
return priority_by_format[api_format]
return 999999 # NULL 排在后面
# 按优先级分组
priority_groups: Dict[int, List[ProviderCandidate]] = defaultdict(list)
for candidate in candidates:
global_priority = (
candidate.key.global_priority
if candidate.key and candidate.key.global_priority is not None
else 999999 # NULL 排在后面
)
priority_groups[global_priority].append(candidate)
priority = get_priority(candidate)
priority_groups[priority].append(candidate)
result = []
for priority in sorted(priority_groups.keys()): # 数字小的优先级高
@@ -1263,13 +1270,13 @@ class CacheAwareScheduler:
return result
def _apply_load_balance(
self, candidates: List[ProviderCandidate]
self, candidates: List[ProviderCandidate], api_format: Optional[str] = None
) -> List[ProviderCandidate]:
"""
负载均衡模式:同优先级内随机轮换
排序逻辑:
1. 按优先级分组provider_priority, internal_priority 或 global_priority
1. 按优先级分组provider_priority, internal_priority 或 global_priority_by_format
2. 同优先级组内随机打乱
3. 不考虑缓存亲和性
"""
@@ -1282,14 +1289,14 @@ class CacheAwareScheduler:
# 根据优先级模式选择分组方式
if self.priority_mode == self.PRIORITY_MODE_GLOBAL_KEY:
# 全局 Key 优先模式:按 global_priority 分组
# 全局 Key 优先模式:按格式特定优先级分组
for candidate in candidates:
global_priority = (
candidate.key.global_priority
if candidate.key and candidate.key.global_priority is not None
else 999999
)
priority_groups[(global_priority,)].append(candidate)
priority = 999999
if candidate.key:
priority_by_format = candidate.key.global_priority_by_format or {}
if api_format and api_format in priority_by_format:
priority = priority_by_format[api_format]
priority_groups[(priority,)].append(candidate)
else:
# 提供商优先模式:按 (provider_priority, internal_priority) 分组
for candidate in candidates:

View File

@@ -27,22 +27,15 @@ class ProviderCacheService:
@staticmethod
def compute_rate_multiplier(
rate_multiplier: Optional[float],
rate_multipliers: Optional[dict],
api_format: Optional[str] = None,
) -> float:
"""
计算 rate_multiplier 的纯函数(无数据库/缓存依赖)
优先返回指定 API 格式的倍率,如果没有则返回默认倍率
规则:
- 如果指定了 api_format 且 rate_multipliers 存在:
- 如果 rate_multipliers[api_format] 存在,返回它
- 否则返回 1.0rate_multipliers 存在但该格式未配置)
- 否则返回 rate_multiplier 或 1.0
返回指定 API 格式的倍率,如果没有则返回 1.0
Args:
rate_multiplier: 默认倍率
rate_multipliers: 按 API 格式的倍率配置字典
api_format: API 格式(可选),如 "CLAUDE""OPENAI"
@@ -53,12 +46,7 @@ class ProviderCacheService:
format_upper = api_format.upper()
if format_upper in rate_multipliers:
return float(rate_multipliers[format_upper])
else:
# rate_multipliers 存在但该格式未配置,使用默认值 1.0
return 1.0
else:
# rate_multipliers 不存在或未指定 api_format回退到默认倍率
return rate_multiplier or 1.0
return 1.0
@staticmethod
async def get_provider_api_key_rate_multiplier(
@@ -92,7 +80,7 @@ class ProviderCacheService:
# 2. 缓存未命中,查询数据库
provider_key = (
db.query(ProviderAPIKey.rate_multiplier, ProviderAPIKey.rate_multipliers)
db.query(ProviderAPIKey.rate_multipliers)
.filter(ProviderAPIKey.id == provider_api_key_id)
.first()
)
@@ -100,7 +88,7 @@ class ProviderCacheService:
# 3. 计算倍率并写入缓存
if provider_key:
rate_multiplier = ProviderCacheService.compute_rate_multiplier(
provider_key.rate_multiplier, provider_key.rate_multipliers, api_format
provider_key.rate_multipliers, api_format
)
await CacheService.set(

View File

@@ -1567,7 +1567,7 @@ class UsageService:
from src.services.cache.provider_cache import ProviderCacheService
provider_key = (
db.query(ProviderAPIKey.rate_multiplier, ProviderAPIKey.rate_multipliers)
db.query(ProviderAPIKey.rate_multipliers)
.filter(ProviderAPIKey.id == provider_api_key_id)
.first()
)
@@ -1576,7 +1576,7 @@ class UsageService:
return None
return ProviderCacheService.compute_rate_multiplier(
provider_key.rate_multiplier, provider_key.rate_multipliers, api_format
provider_key.rate_multipliers, api_format
)
@classmethod