独家合作CubenceAI 中转平台立减 20%访问

参考文档

Redis 架构设计

Redis 架构设计

Redis 在 Claude Code Hub 中扮演关键角色,提供会话状态缓存、限流计数、熔断器状态共享和分布式锁等核心功能。本文档详细介绍 Redis 的架构设计、数据结构、容错策略和性能优化方案。


连接配置

环境变量

# 基础连接(本地 Redis)
REDIS_URL=redis://localhost:6379

# TLS 连接(托管服务如 Upstash)
REDIS_URL=rediss://username:password@hostname:6379

# 配置开关
ENABLE_RATE_LIMIT=true          # 启用限流功能
SESSION_TTL=300                 # 会话过期时间(秒)
STORE_SESSION_MESSAGES=false    # 是否存储请求 messages 到 Redis

连接池配置

src/lib/redis/client.ts 中的单例客户端配置:

const redisOptions: RedisOptions = {
  enableOfflineQueue: false,      // 快速失败,不排队
  maxRetriesPerRequest: 3,        // 单个命令最多重试 3 次
  retryStrategy(times) {
    if (times > 5) return null;   // 停止重试,触发降级
    return Math.min(times * 200, 2000); // 指数退避,最多 2s
  },
};

// TLS/SNI 配置(rediss:// 协议自动启用)
if (useTls) {
  redisOptions.tls = { host: url.hostname }; // 显式 SNI 修复
}

TLS 连接注意事项

使用托管 Redis 服务(如 Upstash)时,必须使用 rediss:// 协议并提供用户名密码。系统自动检测协议头并配置 TLS + SNI,避免证书验证失败。


核心数据结构

1. 会话状态缓存

会话绑定

Redis Key类型TTL说明
session:{sessionId}:providerSTRING300s会话绑定的供应商 ID
session:{sessionId}:keySTRING300s会话使用的 API Key
session:{sessionId}:infoHASH300s会话元信息(model, 用户)
session:{sessionId}:usageHASH300s实时 Token 使用统计
session:{sessionId}:messagesSTRING300s请求 messages(可选存储)
session:{sessionId}:responseSTRING300s最近一次响应内容
hash:{contentHash}:sessionSTRING300s内容哈希到会话 ID 的映射

实现位置: src/lib/session-manager.ts

使用场景: 5 分钟会话粘性,保证同一对话连续请求打到相同供应商,提升缓存命中率。

会话追踪

Redis Key类型TTL说明
global:active_sessionsZSET3600s全局活跃会话(score=时间戳)
key:{keyId}:active_sessionsZSET3600s单个 Key 的活跃会话
provider:{providerId}:active_sessionsZSET-供应商并发会话追踪
session:{sessionId}:concurrent_countSTRING300s单会话并发计数器

实现位置: src/lib/session-tracker.ts

Lua 脚本: CHECK_AND_TRACK_SESSION 原子性检查并发限制 + 添加会话

-- 原子操作:检查并发限制 -> 添加到 ZSET -> 设置过期时间
local count = redis.call('zcard', provider_key)
if count >= max_sessions then
  return { 0, count }
end
redis.call('zadd', provider_key, timestamp, session_id)
redis.call('expire', provider_key, ttl)
return { 1, count + 1 }

2. 限流计数器

RPM(请求速率)限制

Redis Key类型TTL说明
user:{userId}:rpm_windowZSET60s用户 1 分钟内的请求时间戳
key:{keyId}:rpm_windowZSET60sAPI Key 的 RPM 限制

实现位置: src/lib/rate-limit/service.ts

算法: 滑动窗口,每次请求移除过期时间戳并计数。

成本限制(固定窗口)

Redis Key类型TTL说明
user:{userId}:cost_daily_{HHmm}STRING86400s用户每日成本(固定窗口)
user:{userId}:cost_weeklySTRING604800s用户每周成本
user:{userId}:cost_monthlySTRING2592000s用户每月成本

密钥格式: _daily_{HHmm} 表示每日重置时间(如 _daily_0000 表示零点重置)。

成本限制(滚动窗口)

Redis Key类型TTL说明
user:{userId}:cost_5h_rollingZSET18000s5 小时滚动窗口(score=时间戳)
user:{userId}:cost_daily_rollingZSET86400s24 小时滚动窗口

Lua 脚本:

  • TRACK_COST_5H_ROLLING_WINDOW: 移除 5 小时外的记录 -> 计算当前成本 -> 添加新记录
  • TRACK_COST_DAILY_ROLLING_WINDOW: 同上逻辑,24 小时窗口

优势: 无固定重置点,更平滑的成本控制。

固定窗口 vs 滚动窗口

  • 固定窗口: 性能更优(单个 STRING 键),但存在边界突发(零点前后可能双倍流量)
  • 滚动窗口: 更精确的限流,无边界问题,但 ZSET 操作稍慢 CCH 同时支持两种策略,可通过配置选择。

3. 熔断器状态

配置缓存

Redis Key类型TTL说明
circuit_breaker:config:{providerId}HASH300s供应商熔断器配置缓存

字段:

  • failureThreshold: 触发熔断的失败次数(默认 5)
  • openDuration: 熔断持续时间(默认 1800000ms = 30 分钟)
  • halfOpenSuccessThreshold: HALF-OPEN 转 CLOSED 需要的成功次数(默认 2)

实现位置: src/lib/redis/circuit-breaker-config.ts

特点: 5 分钟缓存 TTL,降低频繁查询 Redis/数据库的压力。

状态机存储(v0.3.20+)

Redis Key类型TTL说明
circuit_breaker:state:{providerId}HASH86400s供应商熔断器运行时状态

字段:

  • failureCount: 连续失败计数(整数字符串)
  • lastFailureTime: 最后失败时间戳(Unix 毫秒字符串)
  • circuitState: 当前状态(closed/open/half-open)
  • circuitOpenUntil: 熔断结束时间戳(仅 OPEN 状态,Unix 毫秒字符串)
  • halfOpenSuccessCount: 半开状态成功计数(整数字符串)

实现位置: src/lib/circuit-breaker.tssrc/lib/redis/circuit-breaker-state.ts

状态存储策略

从 v0.3.20 版本开始,熔断器状态支持 Redis 持久化:

  • Redis 可用时:状态同步存储到 Redis,实现多实例共享和重启保留
  • Redis 不可用时:降级为内存存储(healthMap),重启后状态重置
  • 24 小时 TTL:自动清理长期未使用的供应商状态

这种设计既保证了多实例部署场景下的状态一致性,又确保了 Redis 故障时的服务可用性。


4. 分布式锁

排行榜缓存锁

Redis Key类型TTL说明
leaderboard:{scope}:daily:{date}:{currency}STRING60s每日排行榜缓存
leaderboard:{scope}:monthly:{month}:{currency}STRING60s每月排行榜缓存
{cacheKey}:lockSTRING10s排行榜重建的分布式锁

实现位置: src/lib/redis/leaderboard-cache.ts

锁实现: SET NX EX 原子操作,10 秒 TTL 防止死锁。

数据库备份锁

Redis Key类型TTL说明
database:backup:lockSTRING300s数据库备份全局锁

实现位置: src/lib/database-backup/backup-lock.ts

Lua 脚本: 原子 SET NX PX,返回成功/失败/剩余时间。


5. 业务缓存

Codex 指令缓存

Redis Key类型TTL说明
codex:instructions:{providerId}:{model}STRING86400sCodex CLI 的 instructions 字段缓存

实现位置: src/lib/codex-instructions-cache.ts

用途: 避免每次请求都查询数据库获取 Codex 指令配置。


6. 后台任务队列

CCH 使用 Bull 基于 Redis 实现后台任务队列:

Queue 名称用途文件位置
notifications通知发送队列src/lib/notification/notification-queue.ts
log-cleanup日志清理定时任务src/lib/log-cleanup/cleanup-queue.ts

配置: 与主 Redis 连接共享,支持 TLS/SNI。


Lua 脚本列表

所有 Lua 脚本位于 src/lib/redis/lua-scripts.ts,通过 EVALSHA 调用。

会话管理脚本

脚本名称功能KEYSARGV返回值
CHECK_AND_TRACK_SESSION检查并发限制 + 追踪会话provider:{providerId}:active_sessionssessionId, limit, now{allowed, count, tracked}
BATCH_CHECK_SESSION_LIMITS批量检查多个供应商并发多个供应商 keysessionId, limits..., now[{allowed, count}...]

成本追踪脚本

脚本名称功能KEYSARGV返回值
TRACK_COST_5H_ROLLING_WINDOW5 小时滚动成本追踪{type}:{id}:cost_5h_rollingcost, now, windowtotal_cost (string)
GET_COST_5H_ROLLING_WINDOW查询 5 小时滚动成本{type}:{id}:cost_5h_rollingnow, windowtotal_cost (string)
TRACK_COST_DAILY_ROLLING_WINDOW24 小时滚动成本追踪{type}:{id}:cost_daily_rollingcost, now, windowtotal_cost (string)
GET_COST_DAILY_ROLLING_WINDOW查询 24 小时滚动成本{type}:{id}:cost_daily_rollingnow, windowtotal_cost (string)

脚本实现细节

CHECK_AND_TRACK_SESSION:

-- 原子操作流程:
-- 1. 清理 5 分钟前的过期 session
-- 2. 检查 session 是否已追踪(避免重复计数)
-- 3. 检查并发数是否超限
-- 4. 追踪新 session 并设置兜底 TTL

TRACK_COST_5H_ROLLING_WINDOW:

-- 原子操作流程:
-- 1. 清理窗口外的过期记录
-- 2. 添加当前消费(member = "timestamp:cost")
-- 3. 计算窗口内总消费
-- 4. 设置 6 小时兜底 TTL

优势:

  1. 原子性保证(无竞态条件)
  2. 减少网络往返(多个 Redis 命令合并为一次调用)
  3. 服务端执行,性能更优
  4. 数据格式可追溯(timestamp:cost 便于调试)

容错策略

CCH 采用全面的 Fail-Open 策略,确保 Redis 不可用时服务可降级运行。

Fail-Open 核心原则

Redis 不可用时优先保证服务可用性,而非数据完整性。所有 Redis 依赖的功能在降级后:

  • 限流功能失效(允许所有请求通过)
  • 会话粘性失效(每次请求重新选择供应商)
  • 熔断器使用内存状态(重启后重置)

连接级容错

// enableOfflineQueue: false - Redis 断线时立即失败,不排队
// maxRetriesPerRequest: 3 - 单命令最多重试 3 次
// retryStrategy - 指数退避,5 次后停止重试

if (times > 5) {
  logger.error("Redis retry exhausted, fail-open");
  return null; // 触发降级
}

功能级容错

功能模块降级行为实现位置
限流跳过限流检查,允许所有请求src/lib/rate-limit/service.ts
会话管理降级为无状态,每次请求重新选择供应商src/lib/session-manager.ts
熔断器配置返回默认配置src/lib/redis/circuit-breaker-config.ts
排行榜缓存降级为实时查询数据库src/lib/redis/leaderboard-cache.ts
Codex 指令缓存每次从数据库读取src/lib/codex-instructions-cache.ts

日志记录: 所有降级行为都记录 WARN 级别日志,便于监控和排查。


性能优化

1. Pipeline 批量操作

在会话清理等批量操作中使用 Pipeline:

// src/lib/session-tracker.ts
const pipeline = redis.pipeline();
pipeline.zrem("global:active_sessions", sessionId);
pipeline.zrem(`key:${keyId}:active_sessions`, sessionId);
pipeline.zrem(`provider:${providerId}:active_sessions`, sessionId);
pipeline.del(`session:${sessionId}:concurrent_count`);
await pipeline.exec();

优势: 减少 4 次网络往返为 1 次,延迟降低 75%。

2. 过期策略

数据类型TTL 策略原因
会话绑定300s(5 分钟)会话粘性窗口,超时自动解绑
限流计数器按窗口时长(60s/5h/1d/1w/1m)窗口结束自动清理
熔断器配置缓存300s(5 分钟)平衡新鲜度与查询压力
排行榜缓存60s(1 分钟)快速更新,减少数据库查询
Codex 指令缓存86400s(24 小时)配置更新频率低

自动清理: 利用 Redis 的 TTL 机制,无需额外清理任务。

3. 数据结构选择

场景数据结构原因
滑动窗口计数ZSET支持按时间戳范围查询和删除
固定窗口计数STRINGINCR 原子操作,性能最优
会话信息存储HASH多字段存储,支持部分更新
分布式锁STRINGSET NX EX 原子操作

监控指标

关键指标

指标类型监控项告警阈值建议
连接状态Redis 可用性连续失败 > 5 次
内存使用已用内存 / 最大内存> 80%
命中率会话缓存命中率< 70%
延迟命令执行 P95 延迟> 10ms
降级事件Fail-Open 降级次数> 10 次/小时

监控实现

// 日志示例(降级事件)
logger.warn({
  action: "redis_unavailable_fail_open",
  context: "rate_limit",
  error: error.message,
});

// 指标记录(自行接入 Prometheus)
redisCommandDuration.observe(duration);
redisFailOpenCounter.inc({ context: "session" });

部署建议

单节点部署(默认)

# docker-compose.yml
redis:
  image: redis:7-alpine
  command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
  volumes:
    - redis-data:/data

配置要点:

  • --appendonly yes: 启用 AOF 持久化(降低数据丢失风险)
  • --maxmemory 512mb: 限制内存使用
  • --maxmemory-policy allkeys-lru: 内存满时淘汰最少使用的键

Redis Cluster(生产环境)

适用场景:

  • 并发会话 > 1000
  • 多实例水平扩展
  • 需要高可用(主从 + 哨兵)

注意事项:

  • Bull 队列不支持 Cluster 模式(需要单独的 Redis 实例)
  • Lua 脚本需确保所有键在同一 slot(使用 {hash_tag} 语法)

托管 Redis(推荐)

Upstash Redis 配置示例:

REDIS_URL=rediss://default:your-token@region.upstash.io:6379

优势:

  • 自动 TLS 加密
  • 无需管理基础设施
  • 按请求计费,成本可控

故障排查

问题 1: TLS 连接失败

症状: unable to verify the first certificate

解决: 确保 URL 使用 rediss:// 协议,代码已自动配置 SNI:

if (useTls) {
  redisOptions.tls = { host: url.hostname }; // 修复 SNI
}

问题 2: 限流失效

症状: 用户超出配额仍能继续请求

排查:

  1. 检查 ENABLE_RATE_LIMIT 环境变量
  2. 查看日志是否有 redis_unavailable_fail_open
  3. 验证限流配置是否正确设置

问题 3: 会话粘性失效

症状: 同一会话的请求打到不同供应商

排查:

  1. 检查 SESSION_TTL 是否过短
  2. 确认 Redis 可用性(会话绑定依赖 Redis)
  3. 查看是否有 session_provider_not_found 日志

相关文档

Previous
限流规则详解