Skip to content

08 · Redis 协调平面

Redis 在 Buzz 中不是数据库替代品,而是跨 Relay pod 的协调平面:事件广播、动态 topic 管理、presence、限流、NIP-98 防重放、缓存失效和连接控制都依赖它。安全设计的核心原则是:Redis 可以决定谁收到候选消息,但不能决定谁有权看到消息

1. 两类连接

buzz-pubsub 明确分离:

连接用途
poolGET/SET/INCR/EVAL/PUBLISH 等普通命令,可并发借用
dedicated PubSubSUBSCRIBE/UNSUBSCRIBE 是有状态协议,必须由单独任务持有

如果用连接池随机执行订阅,连接归还后其协议状态会污染下一个调用者;如果把所有普通命令塞进 PubSub 连接,又会被订阅读取循环卡住。

2. Topic 设计

事件 topic 大致按社区和频道划分:

text
buzz:{community}:channel:{channel_id}
buzz:{community}:global

此外还有缓存失效、连接控制和其他子系统 key/channel。community 是 key 的一部分,使 Redis 层也尽量不混租户;但即使 topic 错配,Relay fan-out 仍要用 TenantContext 与 access gate 阻止泄露。

3. 动态订阅状态机

PubSubManager 维护 desired topic refcount:

text
retain(topic)
  0 → 1 : SUBSCRIBE
  n → n+1 : only refcount

release(topic)
  n → n-1 : only refcount
  1 → 0 : wait 500ms → if still 0 UNSUBSCRIBE

500 ms debounce 避免用户快速切换频道时产生订阅风暴。Redis 断线后以 1s 到 30s 指数退避重连,并从 desired set 恢复订阅,不以服务器记忆为准。

4. 本地 broadcast channel

Redis reader 把事件解析后送入 Tokio broadcast channel(代码文档给出 4096 容量)。每个消费者自己跟进;落后的 consumer 会收到 lag 错误,而不是拖住 Redis reader。

这种“允许 lag、靠历史补偿”的策略适合事件通知,但不适合不可丢的财务命令。Buzz 把需要可靠恢复的 Push 放入 PostgreSQL outbox,就是在区分两类语义。

5. Presence

Presence 使用 community-scoped Redis key,TTL 约 180 秒。它是租约式状态:

  • 客户端周期刷新。
  • 超时自然离线,不要求完美 disconnect。
  • pod 崩溃不会留下永久在线。
  • 读取 presence 不需要扫描持久事件表。

Typing 更短命,主要通过 ephemeral kind/fan-out 表达;不要把根架构文档中旧的模块描述误认为当前一定存在独立 typing store。

6. 固定窗口限流

RedisRateLimiter 用 Lua 把计数与首次 EXPIRE 绑定在原子操作中:

text
key = rate:{tenant}:{subject}:{bucket}
count = INCR key
if count == 1: EXPIRE key window
allow = count <= limit

优点是多 pod 共享、实现小、性能可预测;缺点是窗口边界突发和 Redis 依赖。它已在当前 Relay AppState 接入,故旧 ARCHITECTURE.md 的“no rate limiter”限制已经过期。

7. NIP-98 replay guard

HTTP 签名通常有时间窗口,但仅校验时间仍允许窗口内重放。Redis replay store 按认证 event ID/主体记录已用 nonce,使用 SET NX EX 类语义:

  • 第一次成功占位。
  • 同一认证事件再次使用被拒绝。
  • TTL 后自动回收。
  • 多 pod 共享判断。

对写入、Git、上传等非幂等端点尤其重要。

8. 缓存失效与连接控制

成员、封禁、频道配置变化后,单 pod 内更新缓存不够。Relay 通过 Redis 广播 invalidation/control:

  • 清除其他 pod 的 community/channel/member cache。
  • 要求关联连接重验或断开。
  • 让新的访问规则尽快作用于 live fan-out。

即便失效消息丢失,关键请求仍应有 TTL 或权威 DB 复核作为最终安全保障;缓存失效只缩短陈旧窗口。

9. Redis 故障时的语义

能力Redis 故障影响权威事实是否丢失
跨 pod fan-out其他 pod 实时通知延迟,重连可补历史否,event 已在 PostgreSQL
presence/typing暂时未知或离线这些本来就是 ephemeral
rate/replay按 endpoint 选择拒绝或降级不应静默放宽高风险边界
cache invalidation其他 pod 短暂陈旧DB 仍是权威
connection control断开传播延迟请求/fan-out 权限仍应复核

10. 为什么 Redis 不是安全边界

Redis 缺少读者身份上下文:一个频道 topic 上的消息面对的是整 pod,而不是最终连接。授权需要当前数据库成员、Agent owner、kind privacy 和 tenant label。把授权放进 topic 名会导致角色变更、私密 kind 和主体门难以表达。

正确分层是:

text
Redis topic = routing hint
subscription index = candidate hint
Relay access gate = authorization decision

11. 源码入口

独立源码研究笔记 · 非 Buzz 官方文档