-
-
Notifications
You must be signed in to change notification settings - Fork 181
Closed
Labels
area:sessionbugSomething isn't workingSomething isn't workingoncallCritical blocking issue requiring immediate oncall attentionCritical blocking issue requiring immediate oncall attention
Description
问题描述
SessionTracker 模块中的 provider:*:active_sessions 等 ZSet 会无限增长,导致 Redis 内存持续增加。在我的生产环境中,单个 provider 的 ZSet 累积了超过 12 万个过期成员,所有 ZSet 合计超过 21 万个过期成员。
环境信息
- CCH 版本:最新 dev 分支
- Redis 版本:7-alpine
- SESSION_TTL 配置:18000(5 小时)
- 运行时长:约 2 个月
问题根因
1. 写入与清理的不对称
写入操作(每次请求都执行):
trackSession()- 添加到 global、key、user ZSetsupdateProvider()- 添加到 provider ZSetrefreshSession()- 更新所有 ZSet 的时间戳
清理操作(仅在读取时执行):
countFromZSet()- 调用zremrangebyscore清理过期成员- 仅在
getProviderSessionCount()、getProviderSessionCountBatch()等读取方法中触发
2. expire(3600) 无法生效
代码中设置了 expire(key, 3600),但由于每次 zadd 都会刷新 TTL,只要有请求就永远不会过期:
// updateProvider() - 第 130-131 行
pipeline.zadd(`provider:${providerId}:active_sessions`, now, sessionId);
pipeline.expire(`provider:${providerId}:active_sessions`, 3600);3. SESSION_TTL 硬编码问题
SessionTracker 硬编码了 5 分钟的 TTL,未读取环境变量:
// session-tracker.ts 第 20 行
private static readonly SESSION_TTL = 300000; // 5 分钟(毫秒)
// session-manager.ts 第 87 行 - 正确读取环境变量
private static readonly SESSION_TTL = parseInt(process.env.SESSION_TTL || "300", 10);影响的 Redis Keys
| Key 模式 | 清理触发条件 | 泄漏严重程度 |
|---|---|---|
global:active_sessions |
Dashboard 查看 | 中 |
key:${keyId}:active_sessions |
限流检查 | 低 |
user:${userId}:active_sessions |
限流检查 | 低 |
provider:${providerId}:active_sessions |
Dashboard 查看 / 限流检查 | 高 |
实际数据
provider:14:active_sessions: 120861 members, TTL=-1
provider:2:active_sessions: 44764 members, TTL=-1
provider:34:active_sessions: 13539 members, TTL=-1
provider:45:active_sessions: 11772 members, TTL=-1
provider:46:active_sessions: 7232 members, TTL=-1
...
Total stale members: 216,866
最早的成员时间戳距今 1777 小时(约 74 天),从未被清理。
建议修复方案
方案 1:写入时增加概率性清理(推荐)
在 trackSession()、updateProvider()、refreshSession() 中增加概率性清理,避免每次请求都执行清理:
// 在 pipeline 执行前,约 5% 概率触发清理
if (Math.random() < 0.05) {
const expireTime = Date.now() - SessionTracker.SESSION_TTL;
pipeline.zremrangebyscore(`provider:${providerId}:active_sessions`, "-inf", expireTime);
}方案 2:统一 SESSION_TTL
从环境变量读取 TTL,保持一致性:
private static readonly SESSION_TTL = parseInt(process.env.SESSION_TTL || "300", 10) * 1000;方案 3:后台定时清理任务
增加一个定时任务,定期扫描并清理所有 *:active_sessions ZSet。
临时缓解措施
# 手动清理过期成员(5 分钟前)
redis-cli ZREMRANGEBYSCORE "provider:14:active_sessions" -inf $(( $(date +%s%3N) - 300000 ))
# 或直接删除(会重建)
redis-cli DEL "provider:14:active_sessions"相关代码位置
src/lib/session-tracker.ts- 主要问题代码src/lib/session-manager.ts- SESSION_TTL 环境变量读取参考src/lib/rate-limit/service.ts- 限流检查触发清理src/actions/providers.ts- Dashboard 触发清理
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:sessionbugSomething isn't workingSomething isn't workingoncallCritical blocking issue requiring immediate oncall attentionCritical blocking issue requiring immediate oncall attention
Projects
Status
Done