Appearance
Harness 多实例怎样共享短期协调状态:Redis、Atomic Operations 与 Distributed Rate Limit
前置:Harness 哪些数据值得缓存:Cache Aside、TTL、Invalidation 与 Stampede
核心问题:多实例 Harness 怎样用 Redis 做原子限流/短期协调,又为什么不能因此把 Redis 当成所有 Runtime 权威状态?
单机 Semaphore 到多实例后为什么失效
假设 Provider 给某 tenant 每秒最多 100 次请求:
text
worker A local limit = 100
worker B local limit = 100
worker C local limit = 100三个实例各自“没有超限”,合起来却可能达到 300。需要一个跨实例共享的 rate state。
Redis 的价值就在这里:低延迟、共享、提供可组合的原子操作。但它不是自动变成 Workflow database。
先把 Redis 定位成 Coordination Plane
适合:
text
rate-limit counters / token bucket state
short-lived dedup hints
single-flight locks
cache metadata
presence / ephemeral heartbeat
某些 lease primitive 的实现层通常不把以下事实仅存在 Redis 里:
text
Run final status
Checkpoint history
Approval evidence
Tool completion audit
long-lived event log如果 Redis 重启/淘汰一条 key 就会让系统“不知道任务做到哪”,边界就错了。
Data Structure 从需要的原子操作倒推
不要先说“统一用 Hash/String”:
- Counter:配额计数;
- Set:短期 membership/dedup;
- ZSet:按时间排序的窗口或到期项;
- Hash:小型结构化 coordination state;
- Stream:有消费语义的事件流,但一旦承担 durable job delivery,就必须按 Queue 的 ack/redelivery 模型设计。
关键不是结构名字,而是:这个状态允许丢吗、允许 stale 吗、需要哪个原子 transition?
Atomic 只代表“Redis 内部没中间态”
INCR、conditional SET、Lua 等可以原子更新 Redis 数据,但不能自动得到:
text
Redis update
+
Postgres commit
+
remote Tool call三者的跨系统原子性。
例如 Worker claim 的语义需要 lease/fencing,即使底层用 Redis 实现,也要遵守 09-09 的 stale-owner rejection;不能因为 SET NX 成功就宣布 exactly-once。
Distributed Rate Limit 要定义四件事
- Subject:按 tenant、user、provider、tool 还是 credential 限?
- Cost:每请求 1 token,还是按模型 token/Tool weight?
- Time semantics:fixed/sliding/token bucket 的窗口与时钟来源;
- Failure policy:Redis 不可用时 fail-open、fail-closed 还是降级到本地限制?
对高成本 Provider,fail-open 可能造成成本洪峰;对关键交互 API,完全 fail-closed 又可能扩大 Redis 故障。策略必须写入 policy,而不是藏在客户端异常处理里。
Failure Modes
Redis 成为第二套 RunState
后果: DB 与 Redis 状态漂移,不知道哪个是真的。
处理: durable Runtime facts 单一 authority;Redis 保存可重建 coordination/cache state。
Rate Limit Key 太粗
后果: 一个 tenant 把所有用户饿死,或不同 credential 共享错误 quota。
处理: key 明确 subject/credential/provider/version,并记录拒绝原因用于 observability。
如何验证
| 场景 | 必须成立 |
|---|---|
| 3 个 worker 同时压测同一 Provider quota | 总体请求仍被统一限制 |
| Redis 短暂不可用 | 行为符合明确 fail-open/closed/degraded policy |
| Redis 全量清空后恢复 | durable Run facts 不丢失 |
自测
- 两个 Harness 实例同时做限流,如果各自只用进程内计数器,会出现什么全局偏差?
- 为什么需要 Redis 原子操作而不是 GET→本地计算→SET 三步完成分布式计数?