数据、缓存与一致性
Sub2API 同时依赖 PostgreSQL、Redis 和进程内缓存。理解每类数据的归属,才能正确备份、扩容和排障。
PostgreSQL:事实来源
长期业务数据以 PostgreSQL 为准:
| 数据域 | 代表表 |
|---|---|
| 身份与用户 | users、auth_identities、pending_auth_sessions |
| 网关资源 | api_keys、accounts、groups、account_groups、proxies |
| 权益 | user_subscriptions、user_platform_quotas、redeem_codes |
| 支付 | payment_orders、payment_provider_instances、payment_audit_logs |
| 用量 | usage_logs、usage_billing_dedup、清理任务 |
| 运维 | error logs、metrics、alert events、job heartbeats |
| 批量图片 | job、item、event 表 |
管理员在页面中修改账号、用户和分组,本质上是在修改这些表,再触发缓存失效或快照更新。
Redis:运行时协调
常见 Redis 数据可以分为五类:
鉴权与业务缓存
- API Key 快照。
- 用户余额和订阅摘要。
- 用户平台配额。
- 分组倍率和模型列表。
这类缓存丢失后通常可以从 PostgreSQL 重建,但会造成数据库回源压力。
并发与限流
- 用户、API Key、账号并发 ZSET。
- 等待队列计数。
- RPM 与费用窗口。
- 模型、账号和上游错误冷却状态。
它们决定请求是否能执行。Redis 故障时不能简单假设“只是慢一点”。
会话与连接提示
- 粘性会话账号绑定。
- Previous Response ID 绑定。
- 临时账号选择和 session limit。
这类数据通常带 TTL,丢失会降低会话连续性,但不应破坏永久业务数据。
分布式锁
- OAuth token 刷新锁。
- 周期任务 leader lock。
- 幂等与临界区锁。
锁 value 包含 owner,释放时采用 compare-and-delete,防止过期持有者删除新持有者的锁。
队列
- 批量图片 ready/delayed/active 队列。
- inflight 与 job lock。
- 用户消息串行队列。
队列数据是否可丢取决于功能。启用异步批量任务时,应为 Redis 配置持久化和恢复策略。
进程内状态
- L1 鉴权与订阅缓存。
- HTTP client 和连接池。
- OpenAI WebSocket pool。
- worker pool 队列与指标。
- 本地配置快照与去重日志状态。
进程重启会丢失这些状态。正常设计应允许重建,但正在执行的流式请求和内存队列任务会中断。
API Key 多级缓存
典型读取链:
request key
│
├─ L1 hit ──────────────▶ auth snapshot
│
├─ Redis L2 hit ────────▶ populate L1
│
└─ PostgreSQL ──────────▶ populate L2 + L1配置中可以调整 L1 大小、L1/L2 TTL、negative cache TTL、jitter 和 singleflight。负缓存可以减少无效 Key 攻击造成的数据库查询,但也意味着刚创建的 Key 必须正确触发缓存失效。
调度快照
高并发时每个请求都从 PostgreSQL查询账号与分组关系成本很高。调度器维护账号候选快照,并通过:
- 管理写路径触发更新。
- scheduler outbox 传播变化。
- 周期性全量重建。
- 受控数据库 fallback。
来保持快照可用。
重要监控包括 outbox lag、backlog、快照重建失败和 DB fallback QPS。调度缓存异常会表现为“后台账号已修改,但网关仍使用旧状态”。
用量写入
请求结束后,RecordUsage 可能更新:
- usage log。
- 用户余额或订阅窗口。
- API Key quota 与费用窗口。
- 用户平台配额。
- 账号成本和使用统计。
- 仪表盘缓存与聚合输入。
项目使用有界 worker pool,默认配置以同步 fallback 保护计费任务。运维不能只看 HTTP 成功率,还要看用量任务失败、丢弃和数据库写延迟。
幂等与去重
系统中存在两类容易混淆的幂等:
- 管理/支付操作幂等:避免重复请求创建或执行相同操作。
- usage billing dedup:避免同一请求或同一 turn 重复扣费。
它们有不同 key、TTL 和失败状态。清理过早可能导致重复执行,保留过久会增加数据库体积。
缓存失效而不是缓存更新
很多写路径选择删除缓存或递增版本,让下一次读取重新构建,而不是尝试在多个缓存结构中同步修改。这降低写路径复杂度,但会造成短暂 cold miss。
发布或批量修改后出现短时间数据库 QPS 上升,可能是缓存集中失效,而不一定是流量增长。
Redis 故障时会发生什么
不同组件的策略不同:
- 某些只读缓存可以回源 PostgreSQL。
- 并发、限流和调度可能 fail closed 或返回服务不可用。
- leader lock 获取失败时,部分任务回退 PostgreSQL advisory lock。
- 粘性会话可能退化为普通调度。
- 账号 OAuth 刷新可能因无法加锁而等待或失败。
不能用一句“Redis 有降级”概括全部行为。生产演练应分别验证鉴权、普通请求、OAuth 刷新、后台任务和批量任务。
备份范围
| 数据 | 必须备份 | 说明 |
|---|---|---|
| PostgreSQL | 是 | 所有长期业务事实 |
.env / config | 是 | 固定密钥与运行配置 |
| Redis | 视功能 | 批量任务/队列启用时重要性更高 |
data/ | 是 | 配置、日志、定价、备份和自定义文件 |
| 进程内缓存 | 否 | 重启自动重建 |
备份加密和恢复演练见升级、备份与迁移。