Revert "feat(429): add configurable cooldown and retry strategies"

This reverts commit 6c3edc0956.
This commit is contained in:
IanShaw committed 2026-08-20 05:44:21 -07:00
1 parent 6c3edc0956
commit e62ec2c42f
12 files changed
+555 -693

No files matched your search

@@ -112,23 +112,15 @@ func (h *SettingHandler) GetRateLimit429CooldownSettings(c *gin.Context) {
}
response.Success(c, dto.RateLimit429CooldownSettings{
Enabled: settings.Enabled,
CooldownSeconds: settings.CooldownSeconds,
Strategy: settings.Strategy,
RetryIntervalMs: settings.RetryIntervalMs,
RetryMaxDurationSeconds: settings.RetryMaxDurationSeconds,
MaxAccountSwitches: settings.MaxAccountSwitches,
Enabled: settings.Enabled,
CooldownSeconds: settings.CooldownSeconds,
})
}
// UpdateRateLimit429CooldownSettingsRequest 更新429默认回避配置请求
type UpdateRateLimit429CooldownSettingsRequest struct {
Strategy string `json:"strategy"`
RetryIntervalMs int `json:"retry_interval_ms"`
RetryMaxDurationSeconds int `json:"retry_max_duration_seconds"`
MaxAccountSwitches int `json:"max_account_switches"`
Enabled bool `json:"enabled"`
CooldownSeconds int `json:"cooldown_seconds"`
Enabled bool `json:"enabled"`
CooldownSeconds int `json:"cooldown_seconds"`
}
// UpdateRateLimit429CooldownSettings 更新429默认回避配置
@@ -141,12 +133,8 @@ func (h *SettingHandler) UpdateRateLimit429CooldownSettings(c *gin.Context) {
}
settings := &service.RateLimit429CooldownSettings{
Strategy: req.Strategy,
RetryIntervalMs: req.RetryIntervalMs,
RetryMaxDurationSeconds: req.RetryMaxDurationSeconds,
MaxAccountSwitches: req.MaxAccountSwitches,
Enabled: req.Enabled,
CooldownSeconds: req.CooldownSeconds,
Enabled: req.Enabled,
CooldownSeconds: req.CooldownSeconds,
}
if err := h.settingService.SetRateLimit429CooldownSettings(c.Request.Context(), settings); err != nil {
@@ -161,12 +149,8 @@ func (h *SettingHandler) UpdateRateLimit429CooldownSettings(c *gin.Context) {
}
response.Success(c, dto.RateLimit429CooldownSettings{
Enabled: updatedSettings.Enabled,
CooldownSeconds: updatedSettings.CooldownSeconds,
Strategy: updatedSettings.Strategy,
RetryIntervalMs: updatedSettings.RetryIntervalMs,
RetryMaxDurationSeconds: updatedSettings.RetryMaxDurationSeconds,
MaxAccountSwitches: updatedSettings.MaxAccountSwitches,
Enabled: updatedSettings.Enabled,
CooldownSeconds: updatedSettings.CooldownSeconds,
})
}
+71 -101
View File
@@ -148,23 +148,21 @@ type SystemSettings struct {
GoogleOAuthRedirectURL string `json:"google_oauth_redirect_url"`
GoogleOAuthFrontendRedirectURL string `json:"google_oauth_frontend_redirect_url"`
SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"`
APIBaseURL string `json:"api_base_url"`
ContactInfo string `json:"contact_info"`
SupportQRCodes []service.SupportQRCodeEntry `json:"support_qr_codes"`
DownloadToolsURL string `json:"download_tools_url"`
DocURL string `json:"doc_url"`
HomeContent string `json:"home_content"`
CompactHomeEnabled bool `json:"compact_home_enabled"`
HideCcsImportButton bool `json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL string `json:"purchase_subscription_url"`
TableDefaultPageSize int `json:"table_default_page_size"`
TablePageSizeOptions []int `json:"table_page_size_options"`
CustomMenuItems []CustomMenuItem `json:"custom_menu_items"`
CustomEndpoints []CustomEndpoint `json:"custom_endpoints"`
SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"`
APIBaseURL string `json:"api_base_url"`
ContactInfo string `json:"contact_info"`
DocURL string `json:"doc_url"`
HomeContent string `json:"home_content"`
CompactHomeEnabled bool `json:"compact_home_enabled"`
HideCcsImportButton bool `json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL string `json:"purchase_subscription_url"`
TableDefaultPageSize int `json:"table_default_page_size"`
TablePageSizeOptions []int `json:"table_page_size_options"`
CustomMenuItems []CustomMenuItem `json:"custom_menu_items"`
CustomEndpoints []CustomEndpoint `json:"custom_endpoints"`
DefaultConcurrency int `json:"default_concurrency"`
DefaultBalance float64 `json:"default_balance"`
@@ -172,9 +170,6 @@ type SystemSettings struct {
AffiliateRebateFreezeHours int `json:"affiliate_rebate_freeze_hours"`
AffiliateRebateDurationDays int `json:"affiliate_rebate_duration_days"`
AffiliateRebatePerInviteeCap float64 `json:"affiliate_rebate_per_invitee_cap"`
AffiliateRebateCap float64 `json:"affiliate_rebate_cap"`
AffiliateRebateInviteeLimit int `json:"affiliate_rebate_invitee_limit"`
AffiliateSignupBonus float64 `json:"affiliate_signup_bonus"`
AdminRechargeRebateEnabled bool `json:"affiliate_admin_recharge_enabled"`
DefaultUserRPMLimit int `json:"default_user_rpm_limit"`
DefaultSubscriptions []DefaultSubscriptionSetting `json:"default_subscriptions"`
@@ -310,23 +305,13 @@ type SystemSettings struct {
ChannelMonitorMode string `json:"channel_monitor_mode"`
ChannelMonitorDefaultIntervalSeconds int `json:"channel_monitor_default_interval_seconds"`
ChannelMonitorHideThroughput bool `json:"channel_monitor_hide_throughput"`
ChannelMonitorShowQuota bool `json:"channel_monitor_show_quota"`
// Grok model mapping policy (admin settings; empty account mapping falls back to these).
GrokDefaultTextModel string `json:"grok_default_text_model"`
GrokCrossClientModelMapEnabled bool `json:"grok_cross_client_model_map_enabled"`
GrokDefaultBaseURLMode string `json:"grok_default_base_url_mode"`
// Kiro runtime defaults (admin-only; not exposed on PublicSettings).
KiroDefaultVersion string `json:"kiro_version"`
KiroDefaultCommit string `json:"kiro_commit"`
KiroDefaultSystemVersion string `json:"system_version"`
KiroDefaultNodeVersion string `json:"node_version"`
KiroCacheHitRateScale int `json:"cache_hit_rate_scale"`
KiroCacheMinBlockTokens int `json:"cache_min_block_tokens"`
KiroCacheIndependentTTLSeconds int `json:"cache_independent_ttl_seconds"`
KiroCachePrefixTTLSeconds int `json:"cache_prefix_ttl_seconds"`
KiroCodeExecutionSandboxCommand string `json:"kiro_code_execution_sandbox_command"`
// Available Channels feature switch (user-facing aggregate view)
AvailableChannelsEnabled bool `json:"available_channels_enabled"`
@@ -345,14 +330,6 @@ type SystemSettings struct {
// Affiliate (邀请返利) feature switch
AffiliateEnabled bool `json:"affiliate_enabled"`
// Ticket feature switch (default enabled)
TicketEnabled bool `json:"ticket_enabled"`
IPMultiAccountBanEnabled bool `json:"ip_multi_account_ban_enabled"`
IPMultiAccountBanWindowMinutes int `json:"ip_multi_account_ban_window_minutes"`
IPMultiAccountBanThreshold int `json:"ip_multi_account_ban_threshold"`
IPMultiAccountBanLearningUntil string `json:"ip_multi_account_ban_learning_until"`
// OpenAI fast/flex policy
OpenAIFastPolicySettings *OpenAIFastPolicySettings `json:"openai_fast_policy_settings,omitempty"`
@@ -372,61 +349,58 @@ type DefaultSubscriptionSetting struct {
}
type PublicSettings struct {
RegistrationEnabled bool `json:"registration_enabled"`
EmailVerifyEnabled bool `json:"email_verify_enabled"`
ForceEmailOnThirdPartySignup bool `json:"force_email_on_third_party_signup"`
RegistrationEmailSuffixWhitelist []string `json:"registration_email_suffix_whitelist"`
RegistrationEmailDomainQuotaEnabled bool `json:"registration_email_domain_quota_enabled"`
PromoCodeEnabled bool `json:"promo_code_enabled"`
PasswordResetEnabled bool `json:"password_reset_enabled"`
InvitationCodeEnabled bool `json:"invitation_code_enabled"`
TotpEnabled bool `json:"totp_enabled"` // TOTP 双因素认证
PasskeyEnabled bool `json:"passkey_enabled"`
LoginAgreementEnabled bool `json:"login_agreement_enabled"`
LoginAgreementMode string `json:"login_agreement_mode"`
LoginAgreementUpdatedAt string `json:"login_agreement_updated_at"`
LoginAgreementRevision string `json:"login_agreement_revision"`
LoginAgreementDocuments []LoginAgreementDocument `json:"login_agreement_documents"`
TurnstileEnabled bool `json:"turnstile_enabled"`
TurnstileSiteKey string `json:"turnstile_site_key"`
TencentCaptchaEnabled bool `json:"tencent_captcha_enabled"`
TencentCaptchaAppID string `json:"tencent_captcha_app_id"`
TencentCaptchaRegion string `json:"tencent_captcha_region"`
AliyunCaptchaEnabled bool `json:"aliyun_captcha_enabled"`
AliyunCaptchaSceneID string `json:"aliyun_captcha_scene_id"`
AliyunCaptchaPrefix string `json:"aliyun_captcha_prefix"`
AliyunCaptchaRegion string `json:"aliyun_captcha_region"`
SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"`
APIBaseURL string `json:"api_base_url"`
ContactInfo string `json:"contact_info"`
SupportQRCodes []service.SupportQRCodeEntry `json:"support_qr_codes"`
DownloadToolsURL string `json:"download_tools_url"`
DocURL string `json:"doc_url"`
HomeContent string `json:"home_content"`
CompactHomeEnabled bool `json:"compact_home_enabled"`
HideCcsImportButton bool `json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL string `json:"purchase_subscription_url"`
TableDefaultPageSize int `json:"table_default_page_size"`
TablePageSizeOptions []int `json:"table_page_size_options"`
CustomMenuItems []CustomMenuItem `json:"custom_menu_items"`
CustomEndpoints []CustomEndpoint `json:"custom_endpoints"`
DingTalkOAuthEnabled bool `json:"dingtalk_oauth_enabled"`
LinuxDoOAuthEnabled bool `json:"linuxdo_oauth_enabled"`
WeChatOAuthEnabled bool `json:"wechat_oauth_enabled"`
WeChatOAuthOpenEnabled bool `json:"wechat_oauth_open_enabled"`
WeChatOAuthMPEnabled bool `json:"wechat_oauth_mp_enabled"`
WeChatOAuthMobileEnabled bool `json:"wechat_oauth_mobile_enabled"`
OIDCOAuthEnabled bool `json:"oidc_oauth_enabled"`
OIDCOAuthProviderName string `json:"oidc_oauth_provider_name"`
GitHubOAuthEnabled bool `json:"github_oauth_enabled"`
GoogleOAuthEnabled bool `json:"google_oauth_enabled"`
SoraClientEnabled bool `json:"sora_client_enabled"`
BackendModeEnabled bool `json:"backend_mode_enabled"`
PaymentEnabled bool `json:"payment_enabled"`
Version string `json:"version"`
RegistrationEnabled bool `json:"registration_enabled"`
EmailVerifyEnabled bool `json:"email_verify_enabled"`
ForceEmailOnThirdPartySignup bool `json:"force_email_on_third_party_signup"`
RegistrationEmailSuffixWhitelist []string `json:"registration_email_suffix_whitelist"`
RegistrationEmailDomainQuotaEnabled bool `json:"registration_email_domain_quota_enabled"`
PromoCodeEnabled bool `json:"promo_code_enabled"`
PasswordResetEnabled bool `json:"password_reset_enabled"`
InvitationCodeEnabled bool `json:"invitation_code_enabled"`
TotpEnabled bool `json:"totp_enabled"` // TOTP 双因素认证
PasskeyEnabled bool `json:"passkey_enabled"`
LoginAgreementEnabled bool `json:"login_agreement_enabled"`
LoginAgreementMode string `json:"login_agreement_mode"`
LoginAgreementUpdatedAt string `json:"login_agreement_updated_at"`
LoginAgreementRevision string `json:"login_agreement_revision"`
LoginAgreementDocuments []LoginAgreementDocument `json:"login_agreement_documents"`
TurnstileEnabled bool `json:"turnstile_enabled"`
TurnstileSiteKey string `json:"turnstile_site_key"`
TencentCaptchaEnabled bool `json:"tencent_captcha_enabled"`
TencentCaptchaAppID string `json:"tencent_captcha_app_id"`
TencentCaptchaRegion string `json:"tencent_captcha_region"`
AliyunCaptchaEnabled bool `json:"aliyun_captcha_enabled"`
AliyunCaptchaSceneID string `json:"aliyun_captcha_scene_id"`
AliyunCaptchaPrefix string `json:"aliyun_captcha_prefix"`
AliyunCaptchaRegion string `json:"aliyun_captcha_region"`
SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"`
APIBaseURL string `json:"api_base_url"`
ContactInfo string `json:"contact_info"`
DocURL string `json:"doc_url"`
HomeContent string `json:"home_content"`
CompactHomeEnabled bool `json:"compact_home_enabled"`
HideCcsImportButton bool `json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled bool `json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL string `json:"purchase_subscription_url"`
TableDefaultPageSize int `json:"table_default_page_size"`
TablePageSizeOptions []int `json:"table_page_size_options"`
CustomMenuItems []CustomMenuItem `json:"custom_menu_items"`
CustomEndpoints []CustomEndpoint `json:"custom_endpoints"`
DingTalkOAuthEnabled bool `json:"dingtalk_oauth_enabled"`
LinuxDoOAuthEnabled bool `json:"linuxdo_oauth_enabled"`
WeChatOAuthEnabled bool `json:"wechat_oauth_enabled"`
WeChatOAuthOpenEnabled bool `json:"wechat_oauth_open_enabled"`
WeChatOAuthMPEnabled bool `json:"wechat_oauth_mp_enabled"`
WeChatOAuthMobileEnabled bool `json:"wechat_oauth_mobile_enabled"`
OIDCOAuthEnabled bool `json:"oidc_oauth_enabled"`
OIDCOAuthProviderName string `json:"oidc_oauth_provider_name"`
GitHubOAuthEnabled bool `json:"github_oauth_enabled"`
GoogleOAuthEnabled bool `json:"google_oauth_enabled"`
BackendModeEnabled bool `json:"backend_mode_enabled"`
PaymentEnabled bool `json:"payment_enabled"`
Version string `json:"version"`
// 服务器全局时区(IANA 名称与当前 UTC 偏移,如 "Asia/Shanghai" / "+08:00")。
// 高峰时段等按服务器本地时间判定的窗口,前端展示时据此标注,避免用户按浏览器本地时间误读。
ServerTimezone string `json:"server_timezone"`
@@ -440,6 +414,7 @@ type PublicSettings struct {
ChannelMonitorMode string `json:"channel_monitor_mode"`
ChannelMonitorDefaultIntervalSeconds int `json:"channel_monitor_default_interval_seconds"`
ChannelMonitorHideThroughput bool `json:"channel_monitor_hide_throughput"`
ChannelMonitorShowQuota bool `json:"channel_monitor_show_quota"`
AvailableChannelsEnabled bool `json:"available_channels_enabled"`
@@ -447,7 +422,6 @@ type PublicSettings struct {
ModelPlazaRequireAuth bool `json:"model_plaza_require_auth"`
AffiliateEnabled bool `json:"affiliate_enabled"`
TicketEnabled bool `json:"ticket_enabled"`
RiskControlEnabled bool `json:"risk_control_enabled"`
@@ -468,12 +442,8 @@ type OverloadCooldownSettings struct {
// RateLimit429CooldownSettings 429默认回避配置 DTO
type RateLimit429CooldownSettings struct {
Strategy string `json:"strategy"`
RetryIntervalMs int `json:"retry_interval_ms"`
RetryMaxDurationSeconds int `json:"retry_max_duration_seconds"`
MaxAccountSwitches int `json:"max_account_switches"`
Enabled bool `json:"enabled"`
CooldownSeconds int `json:"cooldown_seconds"`
Enabled bool `json:"enabled"`
CooldownSeconds int `json:"cooldown_seconds"`
}
// PanelRateLimitSettings 面板 API 限流配置 DTO
+16 -75
View File
@@ -56,16 +56,13 @@ const (
const profitVetoExhaustedMessage = "No available accounts: all candidates rejected by group profit control"
func sameAccountRetryDelayFor(failoverErr *service.UpstreamFailoverError, retryCount int) time.Duration {
// An OAuth 429 with a request-scoped deadline intentionally retries
// immediately; zero is meaningful here and must not fall back to 500ms.
if failoverErr != nil && failoverErr.StatusCode == http.StatusTooManyRequests &&
!failoverErr.SameAccountRetryDeadline.IsZero() && failoverErr.SameAccountRetryDelay <= 0 {
return 0
if failoverErr == nil {
return sameAccountRetryDelay
}
if failoverErr != nil && failoverErr.SameAccountRetryDelay > 0 {
if failoverErr.SameAccountRetryDelay > 0 {
return failoverErr.SameAccountRetryDelay
}
if failoverErr == nil || !failoverErr.RequestScopedTransient || retryCount <= 1 {
if !failoverErr.RequestScopedTransient || retryCount <= 1 {
return sameAccountRetryDelay
}
@@ -79,43 +76,10 @@ func sameAccountRetryDelayFor(failoverErr *service.UpstreamFailoverError, retryC
return delay
}
func sameAccountRetryAllowed(failoverErr *service.UpstreamFailoverError, retryCount, retryLimit int) bool {
if failoverErr == nil || !failoverErr.RetryableOnSameAccount {
return false
}
if retryLimit > 0 && retryCount >= retryLimit {
return false
}
if !failoverErr.SameAccountRetryDeadline.IsZero() {
return time.Now().Before(failoverErr.SameAccountRetryDeadline)
}
return retryCount < retryLimit
}
func pinSameAccountRetryContext(
ctx context.Context,
fs *FailoverState,
accountID int64,
groupID *int64,
failoverErr *service.UpstreamFailoverError,
prevRetryCount int,
prevSwitchCount int,
bridgeOldKeys bool,
) context.Context {
if ctx == nil || fs == nil || failoverErr == nil || !failoverErr.RetryableOnSameAccount {
return ctx
}
if fs.SwitchCount != prevSwitchCount {
return ctx
}
if fs.SameAccountRetryCount[accountID] != prevRetryCount+1 {
return ctx
}
prefetchedGroupID := int64(0)
if groupID != nil {
prefetchedGroupID = *groupID
}
return service.WithPrefetchedStickySession(ctx, accountID, prefetchedGroupID, bridgeOldKeys)
// sameAccountRetryDeadlineAllows prevents a retry from starting after the
// service-provided same-account retry window has elapsed.
func sameAccountRetryDeadlineAllows(failoverErr *service.UpstreamFailoverError) bool {
return failoverErr == nil || failoverErr.SameAccountRetryDeadline.IsZero() || time.Now().Before(failoverErr.SameAccountRetryDeadline)
}
// FailoverState 跨循环迭代共享的 failover 状态
@@ -168,24 +132,6 @@ func (s *FailoverState) RecordProfitVeto(accountID int64) FailoverAction {
return FailoverContinue
}
// RecordConcurrencyTimeout excludes a busy account after the slot ladder
// (deadline two-shot or wait-queue full) and continues onto another account
// while the original sticky binding is preserved by the caller.
func (s *FailoverState) RecordConcurrencyTimeout(accountID int64) FailoverAction {
if s == nil {
return FailoverExhausted
}
if s.FailedAccountIDs == nil {
s.FailedAccountIDs = make(map[int64]struct{})
}
s.FailedAccountIDs[accountID] = struct{}{}
if s.SwitchCount >= s.MaxSwitches {
return FailoverExhausted
}
s.SwitchCount++
return FailoverContinue
}
// ProfitVetoCount 返回本次请求累计的利润否决次数(供日志使用)。
func (s *FailoverState) ProfitVetoCount() int { return s.profitVetoCount }
@@ -223,27 +169,27 @@ func (s *FailoverState) HandleFailoverError(
return FailoverExhausted
}
retryMax := retryLimit
if failoverErr.SameAccountRetryMax > 0 {
retryMax = failoverErr.SameAccountRetryMax
}
// 同账号重试不算切换账号,粘性会话仅在实际切换时强制缓存计费。
sameAccountRetry := sameAccountRetryAllowed(failoverErr, s.SameAccountRetryCount[accountID], retryMax)
retryCount := s.SameAccountRetryCount[accountID]
sameAccountRetryAllowed := failoverErr.RetryableOnSameAccount && retryLimit > 0 && retryCount < retryLimit
if sameAccountRetryAllowed && !failoverErr.SameAccountRetryDeadline.IsZero() {
sameAccountRetryAllowed = time.Now().Before(failoverErr.SameAccountRetryDeadline)
}
sameAccountRetry := sameAccountRetryAllowed
if needForceCacheBilling(s.hasBoundSession, failoverErr, sameAccountRetry) {
s.ForceCacheBilling = true
}
// 同账号重试:对 RetryableOnSameAccount 的临时性错误,先在同一账号上重试。
// 重试次数上限 retryLimit 由调用方传入(账号级 pool_mode_retry_count 配置)。
if sameAccountRetryAllowed(failoverErr, s.SameAccountRetryCount[accountID], retryMax) {
if sameAccountRetryAllowed {
s.SameAccountRetryCount[accountID]++
retryDelay := sameAccountRetryDelayFor(failoverErr, s.SameAccountRetryCount[accountID])
logger.FromContext(ctx).Warn("gateway.failover_same_account_retry",
zap.Int64("account_id", accountID),
zap.Int("upstream_status", failoverErr.StatusCode),
zap.Int("same_account_retry_count", s.SameAccountRetryCount[accountID]),
zap.Int("same_account_retry_max", retryMax),
zap.Int("same_account_retry_max", retryLimit),
zap.Duration("retry_delay", retryDelay),
)
if !sleepWithContext(ctx, retryDelay) {
@@ -259,11 +205,6 @@ func (s *FailoverState) HandleFailoverError(
// 加入失败列表
s.FailedAccountIDs[accountID] = struct{}{}
for _, excludedAccountID := range failoverErr.ExcludedAccountIDs {
if excludedAccountID > 0 {
s.FailedAccountIDs[excludedAccountID] = struct{}{}
}
}
// 检查是否耗尽
if s.SwitchCount >= s.MaxSwitches {