fix(gateway): 对齐号池调度预设行为 (#332)

- 保持 sticky session 优先于分配模式排序,并支持 sticky_session_ttl_seconds=0 禁用粘滞绑定
- 移除旧版 free_team_first 预设入口,统一使用 free_first / team_first 新模式
- 对齐 plus_first 对 Plus/Pro 账号的优先级处理
- 调整负载均衡排序种子,避免同一分钟内固定命中同一 Key
- multi-score 预设下记录延迟样本,并补充号池调度/配置/运行时测试
- 收敛 request candidate extra_data 构造入参,修复 clippy too_many_arguments
This commit is contained in:
Entropy.Xu
2026-04-25 16:41:51 +08:00
committed by GitHub
parent b8205b5a09
commit bc97e383d3
3 changed files with 210 additions and 16 deletions

View File

@@ -266,7 +266,6 @@ pub(crate) fn admin_provider_pool_config_from_config_value(
sticky_session_ttl_seconds: pool_advanced
.get("sticky_session_ttl_seconds")
.and_then(json_u64)
.filter(|value| *value > 0)
.unwrap_or(3600),
latency_window_seconds: pool_advanced
.get("latency_window_seconds")
@@ -389,6 +388,18 @@ mod tests {
assert_eq!(config.stream_timeout_cooldown_seconds, 180);
}
#[test]
fn parses_zero_sticky_session_ttl_to_disable_sticky_sessions() {
let config = admin_provider_pool_config_from_config_value(Some(&json!({
"pool_advanced": {
"sticky_session_ttl_seconds": 0
}
})))
.expect("pool config should parse");
assert_eq!(config.sticky_session_ttl_seconds, 0);
}
#[test]
fn parses_pool_config_from_generic_config_value() {
let config = admin_provider_pool_config_from_config_value(Some(&json!({

View File

@@ -52,7 +52,7 @@ fn should_touch_lru(pool_config: &AdminProviderPoolConfig) -> bool {
}
fn should_record_latency(pool_config: &AdminProviderPoolConfig) -> bool {
enabled_pool_presets(pool_config).any(|preset| preset.eq_ignore_ascii_case("latency_first"))
enabled_pool_presets(pool_config).any(|preset| !preset.eq_ignore_ascii_case("lru"))
}
fn oauth_cache_key(key_id: &str) -> String {
@@ -589,11 +589,18 @@ mod tests {
fn sample_pool_config() -> AdminProviderPoolConfig {
AdminProviderPoolConfig {
scheduling_presets: vec![AdminProviderPoolSchedulingPreset {
preset: "latency_first".to_string(),
enabled: true,
mode: None,
}],
scheduling_presets: vec![
AdminProviderPoolSchedulingPreset {
preset: "cache_affinity".to_string(),
enabled: true,
mode: None,
},
AdminProviderPoolSchedulingPreset {
preset: "latency_first".to_string(),
enabled: true,
mode: None,
},
],
unschedulable_rules: Vec::new(),
lru_enabled: true,
skip_exhausted_accounts: false,
@@ -720,6 +727,44 @@ mod tests {
assert!(runtime.lru_score_by_key.contains_key("key-1"));
}
#[tokio::test]
async fn success_feedback_does_not_write_sticky_when_ttl_is_zero() {
let Some(redis) = start_managed_redis_or_skip().await else {
return;
};
let app = build_runner_app(redis.redis_url(), "pool_runtime_no_sticky_without_affinity");
let runner = app.redis_kv_runner().expect("redis runner should exist");
let mut pool_config = sample_pool_config();
pool_config.sticky_session_ttl_seconds = 0;
let key_ids = vec!["key-1".to_string()];
record_admin_provider_pool_success(
&runner,
"provider-1",
"key-1",
&pool_config,
Some("session-1"),
120,
Some(80),
)
.await;
let runtime = read_admin_provider_pool_runtime_state(
&runner,
"provider-1",
&key_ids,
&pool_config,
Some("session-1"),
)
.await;
assert_eq!(runtime.total_sticky_sessions, 0);
assert_eq!(runtime.sticky_bound_key_id, None);
assert_eq!(runtime.sticky_sessions_by_key.get("key-1"), None);
assert_eq!(runtime.cost_window_usage_by_key.get("key-1"), Some(&120));
assert!(runtime.lru_score_by_key.contains_key("key-1"));
}
#[tokio::test]
async fn error_feedback_respects_retry_after_for_rate_limits() {
let Some(redis) = start_managed_redis_or_skip().await else {