mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 优化粘性会话TTL管理策略
- 将默认TTL从1小时延长至15天,更适合长期项目开发 - 实现智能续期机制:剩余时间<14天时自动续期到15天 - 添加配置化支持:通过环境变量STICKY_SESSION_TTL_DAYS和STICKY_SESSION_RENEWAL_THRESHOLD_DAYS调整TTL策略 - 集成到所有调度器:Claude、OpenAI、Gemini的普通会话和分组会话 - 提升用户体验:活跃项目会话持续有效,停用项目自动清理 - 性能优化:智能判断减少不必要的Redis EXPIRE操作 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,14 @@ const config = {
|
|||||||
enableTLS: process.env.REDIS_ENABLE_TLS === 'true'
|
enableTLS: process.env.REDIS_ENABLE_TLS === 'true'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 🔗 会话管理配置
|
||||||
|
session: {
|
||||||
|
// 粘性会话TTL配置(天)
|
||||||
|
stickyTtlDays: parseInt(process.env.STICKY_SESSION_TTL_DAYS) || 15,
|
||||||
|
// 续期阈值(天)
|
||||||
|
renewalThresholdDays: parseInt(process.env.STICKY_SESSION_RENEWAL_THRESHOLD_DAYS) || 14
|
||||||
|
},
|
||||||
|
|
||||||
// 🎯 Claude API配置
|
// 🎯 Claude API配置
|
||||||
claude: {
|
claude: {
|
||||||
apiUrl: process.env.CLAUDE_API_URL || 'https://api.anthropic.com/v1/messages',
|
apiUrl: process.env.CLAUDE_API_URL || 'https://api.anthropic.com/v1/messages',
|
||||||
|
|||||||
@@ -1356,9 +1356,11 @@ class RedisClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🔗 会话sticky映射管理
|
// 🔗 会话sticky映射管理
|
||||||
async setSessionAccountMapping(sessionHash, accountId, ttl = 3600) {
|
async setSessionAccountMapping(sessionHash, accountId, ttl = null) {
|
||||||
|
const appConfig = require('../../config/config')
|
||||||
|
const defaultTTL = ttl !== null ? ttl : (appConfig.session?.stickyTtlDays || 15) * 24 * 60 * 60
|
||||||
const key = `sticky_session:${sessionHash}`
|
const key = `sticky_session:${sessionHash}`
|
||||||
await this.client.set(key, accountId, 'EX', ttl)
|
await this.client.set(key, accountId, 'EX', defaultTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSessionAccountMapping(sessionHash) {
|
async getSessionAccountMapping(sessionHash) {
|
||||||
@@ -1366,6 +1368,52 @@ class RedisClient {
|
|||||||
return await this.client.get(key)
|
return await this.client.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🚀 智能会话TTL续期:剩余时间少于阈值时自动续期
|
||||||
|
async extendSessionAccountMappingTTL(sessionHash) {
|
||||||
|
const appConfig = require('../../config/config')
|
||||||
|
const key = `sticky_session:${sessionHash}`
|
||||||
|
|
||||||
|
// 📊 从配置获取参数
|
||||||
|
const ttlDays = appConfig.session?.stickyTtlDays || 15
|
||||||
|
const thresholdDays = appConfig.session?.renewalThresholdDays || 14
|
||||||
|
|
||||||
|
const fullTTL = ttlDays * 24 * 60 * 60 // 转换为秒
|
||||||
|
const renewalThreshold = thresholdDays * 24 * 60 * 60 // 转换为秒
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取当前剩余TTL(秒)
|
||||||
|
const remainingTTL = await this.client.ttl(key)
|
||||||
|
|
||||||
|
// 键不存在或已过期
|
||||||
|
if (remainingTTL === -2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 键存在但没有TTL(永不过期,不需要处理)
|
||||||
|
if (remainingTTL === -1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🎯 智能续期策略:仅在剩余时间少于阈值时才续期
|
||||||
|
if (remainingTTL < renewalThreshold) {
|
||||||
|
await this.client.expire(key, fullTTL)
|
||||||
|
logger.debug(
|
||||||
|
`🔄 Renewed sticky session TTL: ${sessionHash} (was ${Math.round(remainingTTL / 24 / 3600)}d, renewed to ${ttlDays}d)`
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 剩余时间充足,无需续期
|
||||||
|
logger.debug(
|
||||||
|
`✅ Sticky session TTL sufficient: ${sessionHash} (remaining ${Math.round(remainingTTL / 24 / 3600)}d)`
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Failed to extend session TTL:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async deleteSessionAccountMapping(sessionHash) {
|
async deleteSessionAccountMapping(sessionHash) {
|
||||||
const key = `sticky_session:${sessionHash}`
|
const key = `sticky_session:${sessionHash}`
|
||||||
return await this.client.del(key)
|
return await this.client.del(key)
|
||||||
|
|||||||
@@ -695,6 +695,8 @@ class ClaudeAccountService {
|
|||||||
// 验证映射的账户是否仍然可用
|
// 验证映射的账户是否仍然可用
|
||||||
const mappedAccount = activeAccounts.find((acc) => acc.id === mappedAccountId)
|
const mappedAccount = activeAccounts.find((acc) => acc.id === mappedAccountId)
|
||||||
if (mappedAccount) {
|
if (mappedAccount) {
|
||||||
|
// 🚀 智能会话续期:剩余时间少于14天时自动续期到15天
|
||||||
|
await redis.extendSessionAccountMappingTTL(sessionHash)
|
||||||
logger.info(
|
logger.info(
|
||||||
`🎯 Using sticky session account: ${mappedAccount.name} (${mappedAccountId}) for session ${sessionHash}`
|
`🎯 Using sticky session account: ${mappedAccount.name} (${mappedAccountId}) for session ${sessionHash}`
|
||||||
)
|
)
|
||||||
@@ -815,6 +817,8 @@ class ClaudeAccountService {
|
|||||||
)
|
)
|
||||||
await redis.deleteSessionAccountMapping(sessionHash)
|
await redis.deleteSessionAccountMapping(sessionHash)
|
||||||
} else {
|
} else {
|
||||||
|
// 🚀 智能会话续期:剩余时间少于14天时自动续期到15天
|
||||||
|
await redis.extendSessionAccountMappingTTL(sessionHash)
|
||||||
logger.info(
|
logger.info(
|
||||||
`🎯 Using sticky session shared account: ${mappedAccount.name} (${mappedAccountId}) for session ${sessionHash}`
|
`🎯 Using sticky session shared account: ${mappedAccount.name} (${mappedAccountId}) for session ${sessionHash}`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ class UnifiedClaudeScheduler {
|
|||||||
requestedModel
|
requestedModel
|
||||||
)
|
)
|
||||||
if (isAvailable) {
|
if (isAvailable) {
|
||||||
|
// 🚀 智能会话续期:剩余时间少于14天时自动续期到15天
|
||||||
|
await redis.extendSessionAccountMappingTTL(sessionHash)
|
||||||
logger.info(
|
logger.info(
|
||||||
`🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
`🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
||||||
)
|
)
|
||||||
@@ -789,6 +791,8 @@ class UnifiedClaudeScheduler {
|
|||||||
requestedModel
|
requestedModel
|
||||||
)
|
)
|
||||||
if (isAvailable) {
|
if (isAvailable) {
|
||||||
|
// 🚀 智能会话续期:剩余时间少于14天时自动续期到15天
|
||||||
|
await redis.extendSessionAccountMappingTTL(sessionHash)
|
||||||
logger.info(
|
logger.info(
|
||||||
`🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
`🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ class UnifiedGeminiScheduler {
|
|||||||
mappedAccount.accountType
|
mappedAccount.accountType
|
||||||
)
|
)
|
||||||
if (isAvailable) {
|
if (isAvailable) {
|
||||||
|
// 🚀 智能会话续期:剩余时间少于14天时自动续期到15天
|
||||||
|
await redis.extendSessionAccountMappingTTL(sessionHash)
|
||||||
logger.info(
|
logger.info(
|
||||||
`🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
`🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
||||||
)
|
)
|
||||||
@@ -382,6 +384,8 @@ class UnifiedGeminiScheduler {
|
|||||||
mappedAccount.accountType
|
mappedAccount.accountType
|
||||||
)
|
)
|
||||||
if (isAvailable) {
|
if (isAvailable) {
|
||||||
|
// 🚀 智能会话续期:剩余时间少于14天时自动续期到15天
|
||||||
|
await redis.extendSessionAccountMappingTTL(sessionHash)
|
||||||
logger.info(
|
logger.info(
|
||||||
`🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
`🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ class UnifiedOpenAIScheduler {
|
|||||||
mappedAccount.accountType
|
mappedAccount.accountType
|
||||||
)
|
)
|
||||||
if (isAvailable) {
|
if (isAvailable) {
|
||||||
|
// 🚀 智能会话续期:剩余时间少于14天时自动续期到15天
|
||||||
|
await redis.extendSessionAccountMappingTTL(sessionHash)
|
||||||
logger.info(
|
logger.info(
|
||||||
`🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
`🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
|
||||||
)
|
)
|
||||||
@@ -388,6 +390,8 @@ class UnifiedOpenAIScheduler {
|
|||||||
mappedAccount.accountType
|
mappedAccount.accountType
|
||||||
)
|
)
|
||||||
if (isAvailable) {
|
if (isAvailable) {
|
||||||
|
// 🚀 智能会话续期:剩余时间少于14天时自动续期到15天
|
||||||
|
await redis.extendSessionAccountMappingTTL(sessionHash)
|
||||||
logger.info(
|
logger.info(
|
||||||
`🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType})`
|
`🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType})`
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user