refactor(proxy): remove retry logic and add enabled check for failover

This commit is contained in:
YoVinchen
2025-12-31 14:06:29 +08:00
parent a2aa969096
commit c69d36d457
4 changed files with 35 additions and 94 deletions
+6 -5
View File
@@ -184,17 +184,18 @@ pub async fn reset_circuit_breaker(
.await?;
// 3. 检查是否应该切回优先级更高的供应商(从 proxy_config 表读取)
let auto_failover_enabled = match db.get_proxy_config_for_app(&app_type).await {
Ok(config) => config.auto_failover_enabled,
// 只有当该应用已被代理接管(enabled=true)且开启了自动故障转移时才执行
let (app_enabled, auto_failover_enabled) = match db.get_proxy_config_for_app(&app_type).await {
Ok(config) => (config.enabled, config.auto_failover_enabled),
Err(e) => {
log::error!(
"[{app_type}] Failed to read proxy_config for auto_failover_enabled: {e}, defaulting to disabled"
"[{app_type}] Failed to read proxy_config: {e}, defaulting to disabled"
);
false
(false, false)
}
};
if auto_failover_enabled && state.proxy_service.is_running().await {
if app_enabled && auto_failover_enabled && state.proxy_service.is_running().await {
// 获取当前供应商 ID
let current_id = db
.get_current_provider(&app_type)
+19
View File
@@ -81,6 +81,25 @@ impl FailoverSwitchManager {
provider_id: &str,
provider_name: &str,
) -> Result<bool, AppError> {
// 检查该应用是否已被代理接管(enabled=true
// 只有被接管的应用才允许执行故障转移切换
let app_enabled = match self.db.get_proxy_config_for_app(app_type).await {
Ok(config) => config.enabled,
Err(e) => {
log::warn!(
"[Failover] 无法读取 {app_type} 配置: {e},跳过切换"
);
return Ok(false);
}
};
if !app_enabled {
log::info!(
"[Failover] {app_type} 未被代理接管(enabled=false),跳过切换"
);
return Ok(false);
}
log::info!("[Failover] 开始切换供应商: {app_type} -> {provider_name} ({provider_id})");
// 1. 更新数据库 is_current
+3 -80
View File
@@ -1,6 +1,6 @@
//! 请求转发器
//!
//! 负责将请求转发到上游Provider,支持重试和故障转移
//! 负责将请求转发到上游Provider,支持故障转移
use super::{
error::*,
@@ -31,8 +31,6 @@ pub struct RequestForwarder {
client: Client,
/// 共享的 ProviderRouter(持有熔断器状态)
router: Arc<ProviderRouter>,
/// 单个 Provider 内的最大重试次数
max_retries: u8,
status: Arc<RwLock<ProxyStatus>>,
current_providers: Arc<RwLock<std::collections::HashMap<String, (String, String)>>>,
/// 故障转移切换管理器
@@ -48,7 +46,6 @@ impl RequestForwarder {
pub fn new(
router: Arc<ProviderRouter>,
non_streaming_timeout: u64,
max_retries: u8,
status: Arc<RwLock<ProxyStatus>>,
current_providers: Arc<RwLock<std::collections::HashMap<String, (String, String)>>>,
failover_manager: Arc<FailoverSwitchManager>,
@@ -77,7 +74,6 @@ impl RequestForwarder {
Self {
client,
router,
max_retries,
status,
current_providers,
failover_manager,
@@ -86,59 +82,6 @@ impl RequestForwarder {
}
}
/// 对单个 Provider 执行请求(带重试)
///
/// 在同一个 Provider 上最多重试 max_retries 次,使用指数退避
async fn forward_with_provider_retry(
&self,
provider: &Provider,
endpoint: &str,
body: &Value,
headers: &axum::http::HeaderMap,
adapter: &dyn ProviderAdapter,
) -> Result<Response, ProxyError> {
let mut last_error = None;
for attempt in 0..=self.max_retries {
if attempt > 0 {
// 指数退避:100ms, 200ms, 400ms, ...
let delay_ms = 100 * 2u64.pow(attempt as u32 - 1);
log::info!(
"[{}] 重试第 {}/{} 次(等待 {}ms",
adapter.name(),
attempt,
self.max_retries,
delay_ms
);
tokio::time::sleep(Duration::from_millis(delay_ms)).await;
}
match self
.forward(provider, endpoint, body, headers, adapter)
.await
{
Ok(response) => return Ok(response),
Err(e) => {
// 只有“同一 Provider 内可重试”的错误才继续重试
if !self.should_retry_same_provider(&e) {
return Err(e);
}
log::debug!(
"[{}] Provider {} 第 {} 次请求失败: {}",
adapter.name(),
provider.name,
attempt + 1,
e
);
last_error = Some(e);
}
}
}
Err(last_error.unwrap_or(ProxyError::MaxRetriesExceeded))
}
/// 转发请求(带故障转移)
///
/// # Arguments
@@ -224,9 +167,9 @@ impl RequestForwarder {
let start = Instant::now();
// 转发请求(带单 Provider 内重试
// 转发请求(每个 Provider 只尝试一次,重试由客户端控制
match self
.forward_with_provider_retry(provider, endpoint, &body, &headers, adapter.as_ref())
.forward(provider, endpoint, &body, &headers, adapter.as_ref())
.await
{
Ok(response) => {
@@ -551,25 +494,6 @@ impl RequestForwarder {
}
}
/// 分类ProxyError
///
/// 决定哪些错误应该触发故障转移到下一个 Provider
///
/// 设计原则:既然用户配置了多个供应商,就应该让所有供应商都尝试一遍。
/// 只有明确是客户端中断的情况才不重试。
fn should_retry_same_provider(&self, error: &ProxyError) -> bool {
match error {
// 网络类错误:短暂抖动时同一 Provider 内重试有意义
ProxyError::Timeout(_) => true,
ProxyError::ForwardFailed(_) => true,
// 上游 HTTP 错误:只对“可能瞬态”的状态码做同 Provider 重试(其余交给 failover
ProxyError::UpstreamError { status, .. } => {
*status == 408 || *status == 429 || *status >= 500
}
_ => false,
}
}
fn categorize_proxy_error(&self, error: &ProxyError) -> ErrorCategory {
match error {
// 网络和上游错误:都应该尝试下一个供应商
@@ -585,7 +509,6 @@ impl RequestForwarder {
ProxyError::TransformError(_) => ErrorCategory::Retryable,
ProxyError::AuthError(_) => ErrorCategory::Retryable,
ProxyError::StreamIdleTimeout(_) => ErrorCategory::Retryable,
ProxyError::MaxRetriesExceeded => ErrorCategory::Retryable,
// 无可用供应商:所有供应商都试过了,无法重试
ProxyError::NoAvailableProvider => ErrorCategory::NonRetryable,
// 其他错误(数据库/内部错误等):不是换供应商能解决的问题
+7 -9
View File
@@ -150,31 +150,29 @@ impl RequestContext {
/// 使用共享的 ProviderRouter,确保熔断器状态跨请求保持
///
/// 配置生效规则:
/// - 故障转移开启:超时配置和重试次数正常生效(0 表示禁用超时/不重试直接故障转移
/// - 故障转移关闭:超时配置和重试次数不生效(全部传入 0
/// - 故障转移开启:超时配置正常生效(0 表示禁用超时)
/// - 故障转移关闭:超时配置不生效(全部传入 0)
pub fn create_forwarder(&self, state: &ProxyState) -> RequestForwarder {
let (non_streaming_timeout, max_retries, first_byte_timeout, idle_timeout) =
let (non_streaming_timeout, first_byte_timeout, idle_timeout) =
if self.app_config.auto_failover_enabled {
// 故障转移开启:使用配置的值(0 = 禁用超时 / 不重试直接故障转移
// 故障转移开启:使用配置的值(0 = 禁用超时)
(
self.app_config.non_streaming_timeout as u64,
self.app_config.max_retries as u8,
self.app_config.streaming_first_byte_timeout as u64,
self.app_config.streaming_idle_timeout as u64,
)
} else {
// 故障转移关闭:不启用超时和重试配置
// 故障转移关闭:不启用超时配置
log::info!(
"[{}] Failover disabled, timeout and retry configs are bypassed",
"[{}] Failover disabled, timeout configs are bypassed",
self.tag
);
(0, 0, 0, 0)
(0, 0, 0)
};
RequestForwarder::new(
state.provider_router.clone(),
non_streaming_timeout,
max_retries,
state.status.clone(),
state.current_providers.clone(),
state.failover_manager.clone(),