From 433f0c5f23db670d41ac5dc64fb8765b59be1bfc Mon Sep 17 00:00:00 2001 From: sususu98 Date: Wed, 10 Sep 2025 15:53:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E7=BB=AD=E6=9C=9F=E9=80=BB=E8=BE=91=EF=BC=8C=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E7=BB=AD=E6=9C=9F=E9=98=88=E5=80=BC=E5=92=8CTTL=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E7=A1=AE=E4=BF=9D=E7=BB=9F=E4=B8=80=E8=B0=83?= =?UTF-8?q?=E5=BA=A6=E4=BC=9A=E8=AF=9D=E6=98=A0=E5=B0=84=E6=8C=89=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=AD=A3=E7=A1=AE=E7=BB=AD=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 +- src/services/unifiedClaudeScheduler.js | 58 ++++++++++++++++++++++---- src/services/unifiedGeminiScheduler.js | 51 ++++++++++++++++++---- src/services/unifiedOpenAIScheduler.js | 51 ++++++++++++++++++---- 4 files changed, 138 insertions(+), 24 deletions(-) diff --git a/.env.example b/.env.example index fa640f15..c70db5fd 100644 --- a/.env.example +++ b/.env.example @@ -26,7 +26,7 @@ REDIS_ENABLE_TLS= # 粘性会话TTL配置(小时),默认1小时 STICKY_SESSION_TTL_HOURS=1 # 续期阈值(分钟),默认0分钟(不续期) -STICKY_SESSION_RENEWAL_THRESHOLD_MINUTES=0 +STICKY_SESSION_RENEWAL_THRESHOLD_MINUTES=15 # 🎯 Claude API 配置 CLAUDE_API_URL=https://api.anthropic.com/v1/messages diff --git a/src/services/unifiedClaudeScheduler.js b/src/services/unifiedClaudeScheduler.js index 22ffa6c0..584398ee 100644 --- a/src/services/unifiedClaudeScheduler.js +++ b/src/services/unifiedClaudeScheduler.js @@ -244,8 +244,8 @@ class UnifiedClaudeScheduler { effectiveModel ) if (isAvailable) { - // 🚀 智能会话续期:剩余时间少于14天时自动续期到15天 - await redis.extendSessionAccountMappingTTL(sessionHash) + // 🚀 智能会话续期:剩余时间少于14天时自动续期到15天(续期正确的 unified 映射键) + await this._extendSessionMappingTTL(sessionHash) logger.info( `🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}` ) @@ -806,9 +806,11 @@ class UnifiedClaudeScheduler { async _setSessionMapping(sessionHash, accountId, accountType) { const client = redis.getClientSafe() const mappingData = JSON.stringify({ accountId, accountType }) - - // 设置1小时过期 - await client.setex(`${this.SESSION_MAPPING_PREFIX}${sessionHash}`, 3600, mappingData) + // 依据配置设置TTL(小时) + const appConfig = require('../../config/config') + const ttlHours = appConfig.session?.stickyTtlHours || 1 + const ttlSeconds = Math.max(1, Math.floor(ttlHours * 60 * 60)) + await client.setex(`${this.SESSION_MAPPING_PREFIX}${sessionHash}`, ttlSeconds, mappingData) } // 🗑️ 删除会话映射 @@ -817,6 +819,44 @@ class UnifiedClaudeScheduler { await client.del(`${this.SESSION_MAPPING_PREFIX}${sessionHash}`) } + // 🔁 续期统一调度会话映射TTL(针对 unified_claude_session_mapping:* 键),遵循会话配置 + async _extendSessionMappingTTL(sessionHash) { + try { + const client = redis.getClientSafe() + const key = `${this.SESSION_MAPPING_PREFIX}${sessionHash}` + const remainingTTL = await client.ttl(key) + + // -2: key 不存在;-1: 无过期时间 + if (remainingTTL === -2) return false + if (remainingTTL === -1) return true + + const appConfig = require('../../config/config') + const ttlHours = appConfig.session?.stickyTtlHours || 1 + const renewalThresholdMinutes = appConfig.session?.renewalThresholdMinutes || 0 + + // 阈值为0则不续期 + if (!renewalThresholdMinutes) return true + + const fullTTL = Math.max(1, Math.floor(ttlHours * 60 * 60)) + const threshold = Math.max(0, Math.floor(renewalThresholdMinutes * 60)) + + if (remainingTTL < threshold) { + await client.expire(key, fullTTL) + logger.debug( + `🔄 Renewed unified session TTL: ${sessionHash} (was ${Math.round(remainingTTL / 60)}m, renewed to ${ttlHours}h)` + ) + } else { + logger.debug( + `✅ Unified session TTL sufficient: ${sessionHash} (remaining ${Math.round(remainingTTL / 60)}m)` + ) + } + return true + } catch (error) { + logger.error('❌ Failed to extend unified session TTL:', error) + return false + } + } + // 🚫 标记账户为限流状态 async markAccountRateLimited( accountId, @@ -989,8 +1029,8 @@ class UnifiedClaudeScheduler { requestedModel ) if (isAvailable) { - // 🚀 智能会话续期:剩余时间少于14天时自动续期到15天 - await redis.extendSessionAccountMappingTTL(sessionHash) + // 🚀 智能会话续期:续期 unified 映射键 + await this._extendSessionMappingTTL(sessionHash) logger.info( `🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}` ) @@ -1131,8 +1171,8 @@ class UnifiedClaudeScheduler { effectiveModel ) if (isAvailable) { - // 🚀 智能会话续期:剩余时间少于14天时自动续期到15天 - await redis.extendSessionAccountMappingTTL(sessionHash) + // 🚀 智能会话续期:续期 unified 映射键 + await this._extendSessionMappingTTL(sessionHash) logger.info( `🎯 Using sticky CCR session account: ${mappedAccount.accountId} for session ${sessionHash}` ) diff --git a/src/services/unifiedGeminiScheduler.js b/src/services/unifiedGeminiScheduler.js index 27c2387f..890803a9 100644 --- a/src/services/unifiedGeminiScheduler.js +++ b/src/services/unifiedGeminiScheduler.js @@ -61,8 +61,8 @@ class UnifiedGeminiScheduler { mappedAccount.accountType ) if (isAvailable) { - // 🚀 智能会话续期:剩余时间少于14天时自动续期到15天 - await redis.extendSessionAccountMappingTTL(sessionHash) + // 🚀 智能会话续期(续期 unified 映射键,按配置) + await this._extendSessionMappingTTL(sessionHash) logger.info( `🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}` ) @@ -285,9 +285,11 @@ class UnifiedGeminiScheduler { async _setSessionMapping(sessionHash, accountId, accountType) { const client = redis.getClientSafe() const mappingData = JSON.stringify({ accountId, accountType }) - - // 设置1小时过期 - await client.setex(`${this.SESSION_MAPPING_PREFIX}${sessionHash}`, 3600, mappingData) + // 依据配置设置TTL(小时) + const appConfig = require('../../config/config') + const ttlHours = appConfig.session?.stickyTtlHours || 1 + const ttlSeconds = Math.max(1, Math.floor(ttlHours * 60 * 60)) + await client.setex(`${this.SESSION_MAPPING_PREFIX}${sessionHash}`, ttlSeconds, mappingData) } // 🗑️ 删除会话映射 @@ -296,6 +298,41 @@ class UnifiedGeminiScheduler { await client.del(`${this.SESSION_MAPPING_PREFIX}${sessionHash}`) } + // 🔁 续期统一调度会话映射TTL(针对 unified_gemini_session_mapping:* 键),遵循会话配置 + async _extendSessionMappingTTL(sessionHash) { + try { + const client = redis.getClientSafe() + const key = `${this.SESSION_MAPPING_PREFIX}${sessionHash}` + const remainingTTL = await client.ttl(key) + + if (remainingTTL === -2) return false + if (remainingTTL === -1) return true + + const appConfig = require('../../config/config') + const ttlHours = appConfig.session?.stickyTtlHours || 1 + const renewalThresholdMinutes = appConfig.session?.renewalThresholdMinutes || 0 + if (!renewalThresholdMinutes) return true + + const fullTTL = Math.max(1, Math.floor(ttlHours * 60 * 60)) + const threshold = Math.max(0, Math.floor(renewalThresholdMinutes * 60)) + + if (remainingTTL < threshold) { + await client.expire(key, fullTTL) + logger.debug( + `🔄 Renewed unified Gemini session TTL: ${sessionHash} (was ${Math.round(remainingTTL / 60)}m, renewed to ${ttlHours}h)` + ) + } else { + logger.debug( + `✅ Unified Gemini session TTL sufficient: ${sessionHash} (remaining ${Math.round(remainingTTL / 60)}m)` + ) + } + return true + } catch (error) { + logger.error('❌ Failed to extend unified Gemini session TTL:', error) + return false + } + } + // 🚫 标记账户为限流状态 async markAccountRateLimited(accountId, accountType, sessionHash = null) { try { @@ -384,8 +421,8 @@ class UnifiedGeminiScheduler { mappedAccount.accountType ) if (isAvailable) { - // 🚀 智能会话续期:剩余时间少于14天时自动续期到15天 - await redis.extendSessionAccountMappingTTL(sessionHash) + // 🚀 智能会话续期(续期 unified 映射键,按配置) + await this._extendSessionMappingTTL(sessionHash) logger.info( `🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}` ) diff --git a/src/services/unifiedOpenAIScheduler.js b/src/services/unifiedOpenAIScheduler.js index 99c21f32..ddc691be 100644 --- a/src/services/unifiedOpenAIScheduler.js +++ b/src/services/unifiedOpenAIScheduler.js @@ -90,8 +90,8 @@ class UnifiedOpenAIScheduler { mappedAccount.accountType ) if (isAvailable) { - // 🚀 智能会话续期:剩余时间少于14天时自动续期到15天 - await redis.extendSessionAccountMappingTTL(sessionHash) + // 🚀 智能会话续期(续期 unified 映射键,按配置) + await this._extendSessionMappingTTL(sessionHash) logger.info( `🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}` ) @@ -291,9 +291,11 @@ class UnifiedOpenAIScheduler { async _setSessionMapping(sessionHash, accountId, accountType) { const client = redis.getClientSafe() const mappingData = JSON.stringify({ accountId, accountType }) - - // 设置1小时过期 - await client.setex(`${this.SESSION_MAPPING_PREFIX}${sessionHash}`, 3600, mappingData) + // 依据配置设置TTL(小时) + const appConfig = require('../../config/config') + const ttlHours = appConfig.session?.stickyTtlHours || 1 + const ttlSeconds = Math.max(1, Math.floor(ttlHours * 60 * 60)) + await client.setex(`${this.SESSION_MAPPING_PREFIX}${sessionHash}`, ttlSeconds, mappingData) } // 🗑️ 删除会话映射 @@ -302,6 +304,41 @@ class UnifiedOpenAIScheduler { await client.del(`${this.SESSION_MAPPING_PREFIX}${sessionHash}`) } + // 🔁 续期统一调度会话映射TTL(针对 unified_openai_session_mapping:* 键),遵循会话配置 + async _extendSessionMappingTTL(sessionHash) { + try { + const client = redis.getClientSafe() + const key = `${this.SESSION_MAPPING_PREFIX}${sessionHash}` + const remainingTTL = await client.ttl(key) + + if (remainingTTL === -2) return false + if (remainingTTL === -1) return true + + const appConfig = require('../../config/config') + const ttlHours = appConfig.session?.stickyTtlHours || 1 + const renewalThresholdMinutes = appConfig.session?.renewalThresholdMinutes || 0 + if (!renewalThresholdMinutes) return true + + const fullTTL = Math.max(1, Math.floor(ttlHours * 60 * 60)) + const threshold = Math.max(0, Math.floor(renewalThresholdMinutes * 60)) + + if (remainingTTL < threshold) { + await client.expire(key, fullTTL) + logger.debug( + `🔄 Renewed unified OpenAI session TTL: ${sessionHash} (was ${Math.round(remainingTTL / 60)}m, renewed to ${ttlHours}h)` + ) + } else { + logger.debug( + `✅ Unified OpenAI session TTL sufficient: ${sessionHash} (remaining ${Math.round(remainingTTL / 60)}m)` + ) + } + return true + } catch (error) { + logger.error('❌ Failed to extend unified OpenAI session TTL:', error) + return false + } + } + // 🚫 标记账户为限流状态 async markAccountRateLimited(accountId, accountType, sessionHash = null, resetsInSeconds = null) { try { @@ -408,8 +445,8 @@ class UnifiedOpenAIScheduler { mappedAccount.accountType ) if (isAvailable) { - // 🚀 智能会话续期:剩余时间少于14天时自动续期到15天 - await redis.extendSessionAccountMappingTTL(sessionHash) + // 🚀 智能会话续期(续期 unified 映射键,按配置) + await this._extendSessionMappingTTL(sessionHash) logger.info( `🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType})` )