Redis-智能体开发中的大杀器

与系列统一 :本文 Harness = LLM Agent 运行时治理层 (见 综述)。Redis 不替代 Harness,而是为 状态、限流、队列、协调、缓存 提供高性能底座。
示例语言 :Python 3.10+,redis>=5.0redis-py)。正文业务片段统一用变量 r§1 连接池 / Sentinel / Cluster;§14 生产必备:TLS、ACL、Key 规范、监控告警、容量与持久化(开篇那句 checklist 的展开版)。

文档目录(建议阅读顺序)

主题
§0 / §0b 价值与场景自检表
§1 连接池、Sentinel、Cluster
§2--§12 会话、锁、幂等、限流、Stream、Pub/Sub、缓存、HITL...
§6 重点:XREAD / XREADGROUP 全集
§10b--§10c 延迟队列、Pipeline
§17--§22 前后端、端云、流式、定时、发布消费、杂项
§13 Harness 模块映射
§14 生产:TLS、ACL、监控、Key 规范、Checklist
§15 本地 Docker
§16 竞品与组合(§16.0 速查)
§23 延伸阅读

说明:§13--§16 在文件中位于 §22 之后,是为避免打断代码主题块;按上表顺序读即可。


0. 为什么 Agent 工程里常把 Redis 当「大杀器」

智能体系统有三个共性:高并发短请求多步长会话多进程/多实例协作。关系型数据库擅长事务与复杂查询,但在以下场景往往过重或太慢:

诉求 Redis 擅长点
会话态、工具中间结果、checkpoint 毫秒级读写;Hash / JSON(RedisJSON)
全局限流、Token 预算、熔断计数 原子 INCR + EXPIRE;滑动窗口(ZSET)
多 Agent 抢任务、防重复执行 SETNX 分布式锁;XREADGROUP + XACK
实时步进推前端 / 审计旁路 XREAD + BLOCK(Fan-out,各自 last_id)
人机协同审批、事件总线 List / Stream;Pub/Sub
语义缓存、Embedding 元数据索引 String + TTL;可选 RediSearch / Vector

Redis(横切能力)
Agent Harness
调用方
用户 / Webhook
Orchestrator
路由 / 策略 / 校验
LLM
Tools
Session / Checkpoint
Lock / Idempotency
Queue / Stream
Rate / Budget
Cache

定位一句话 :模型负责「想」,Harness 负责「做」;Redis 负责让「做」在 多实例、可恢复、可限流 的前提下依然够快。


0b. 应用覆盖全景(前后端 · 端云 · 队列 · 流式 · 定时 · 发布消费 · ...)

下面是一张 自检表 :左列是智能体系统里常见工程维度,中列是 Redis 典型用法,右列指向本文章节。 表示文内已有可运行示例; 表示有要点/思路,§17--§22 补全代码。

维度 典型场景 Redis 能力 本文
前后端 登录会话、run_id 绑定、防刷 String/Hash + TTL §17 ✓
前后端 浏览器看 Agent 步进(SSE/WebSocket) Stream XREAD BLOCK §6.3、§17 ✓
前后端 上传大文件/工具产物前的短期凭证 String SETEX §17 ✓
前后端 用户级 QPS / 并发 Run 上限 ZSET 滑动窗口 / INCR §5、§17 ✓
端云一体 手机/边缘端 Agent 与云端状态同步 Hash 版本号 + Stream 指令下行 §18 ✓
端云一体 离线排队、联网后批量上报 List / Stream 本地队列 → 云 XADD §18 ✓
端云一体 设备在线、最后心跳 String/ZSET last_seen §18 ✓
队列 轻量任务、单消费者 List BRPOP §6.1 ✓
队列 多 Worker 竞争、ACK、重试 Stream XREADGROUP §6.6--6.8 ✓
队列 优先级、延迟执行 ZSET + Stream / ZSET 时间轮 §10b、§20 ✓
队列 死信(多次失败) 独立 Stream / List DLQ §22 ✓
流式 LLM Token 增量推前端 Stream XADD + 前端 XREAD §19 ✓
流式 工具 stdout 行级推送 Stream / Pub/Sub §6、§19 ✓
定时任务 Cron 扫描到期 Run、清理僵尸会话 ZSET score=执行时间 §10b、§20 ✓
定时任务 租约/心跳超时回收 TTL + 调度器 ZRANGEBYSCORE §11、§20 ✓
发布 + 消费 广播取消、配置变更 Pub/Sub PUBLISH §7、§21 ✓
发布 + 消费 至少一次、可回溯 Stream XADD/XREAD §6 ✓
发布 + 消费 一条任务只处理一次 Stream XREADGROUP §6.6 ✓
协调 分布式锁、幂等 SET NX、Lua §3、§4 ✓
连接/部署 连接池、Sentinel、Cluster ConnectionPool / RedisCluster §1、§14.1 ✓
生产安全 TLS、ACL、Key 规范、监控 rediss://、ACL 用户、Exporter §14 ✓
竞品/组合 Kafka、Temporal、PG、向量库 热路径 Redis + 冷路径分工 §16.0、§16 ✓
协调 集群单点 Leader 扫 pending SET NX 租约 §22 ✓
缓存 相同 Prompt 省 Token String + TTL §8 ✓
缓存 RAG 片段 / 工具结果 Hash / String §8、§22 △
多租户 配额、隔离 Key 前缀 {tenant}: Hash Tag §1.4、§14.2、§22 ✓
人机 审批、超时 List + ZSET §9 ✓
可观测 QPS、工具失败率 INCR、HyperLogLog §12、§22 ✓
Serverless 函数无状态,会话放 Redis Hash Checkpoint §2、§22 ✓
安全 密钥不下发模型,短期句柄 String 指针 + TTL §17、§22 ✓

Redis
云端 Backend
前端 / 边缘
HTTPS SSE
XREAD 代理 tail
同步 checkpoint
上行 XADD
XREADGROUP
可选 SUBSCRIBE
Web / App
边缘 Agent
API / BFF
Orchestrator
Agent Workers
会话 / 配额
Stream 事件与队列
Pub/Sub 广播
ZSET 定时 / 优先级
缓存 / 锁

怎么读这张表

  • 前后端 :Redis 几乎总在 云端 ;浏览器不直连 Redis(安全),由 BFF/APIXREAD 转 SSE 或 WebSocket。
  • 端云一体 :边缘只保留 小 checkpoint + 离线队列;真相源在云端 Stream/Hash。
  • 队列 vs 流式 vs 发布消费 :任务用 消费者组 ;旁路观测用 XREAD ;纯广播用 Pub/Sub(允许丢)。
  • 细节示例见 §17--§22 ;Stream 命令全集见 §6

1. 连接方式:单连接、连接池、Sentinel、Cluster

后文所有 r.xxx() 均假设 r 来自 get_redis() (进程内单例)。
开发 可用单连接;生产 默认 连接池高可用 用 Sentinel 或 Redis Cluster
高可用
生产推荐
开发 / 单测
本地
redis.Redis 单连接
ConnectionPool

  • Redis 客户端
    Sentinel 故障转移
    Cluster 分片
    API / Agent Worker

1.0 公共:Key 命名(概要)

完整生产规范见 §14.2(前缀、TTL、禁止项、按实体分类表)。代码里建议统一走工厂函数,避免手写拼接:

python 复制代码
import json
import os
import time
import uuid
from functools import lru_cache
from typing import Any

import redis
from redis.cluster import RedisCluster

ENV = os.getenv("HARNESS_ENV", "dev")  # dev | staging | prod

def key(*parts: str) -> str:
    """harness:{env}:... 见 §14.2"""
    return ":".join(["harness", ENV, *parts])
规范 说明
统一前缀 harness:{env}:{tenant?}:{实体}:...,便于 SCAN、按租户清理、监控聚合
必设 TTL 会话、缓存、锁、幂等键都应有过期,避免「僵尸 Key」
值序列化 小对象用 JSON;大对象考虑压缩或只存 object storage 的指针
集群 多 Key 事务 / Lua 多 key 须 同一 hash slot → 使用 Hash Tag {tenant}:...(见 §1.4)

1.1 单连接(仅本地调试)

python 复制代码
# ❌ 生产反模式:每个 HTTP 请求都 Redis(host=...) 一次 → 连接风暴
def redis_single() -> redis.Redis:
    return redis.Redis(
        host=os.getenv("REDIS_HOST", "127.0.0.1"),
        port=int(os.getenv("REDIS_PORT", "6379")),
        password=os.getenv("REDIS_PASSWORD") or None,
        db=int(os.getenv("REDIS_DB", "0")),
        decode_responses=True,
        socket_timeout=5,
        socket_connect_timeout=2,
    )

1.2 连接池(生产默认)

redis-pyRedis 客户端自带连接池 :同一个 Redis 实例复用池内 TCP 连接;多线程/多协程共享该实例即可(勿每线程 new 一个池)。

python 复制代码
@lru_cache
def get_redis_pool() -> redis.Redis:
    """
    进程内单例。Uvicorn/Gunicorn 多 worker = 每进程一个池,属正常。
    可用 REDIS_URL 或 REDIS_HOST/PORT。
    """
    url = os.getenv("REDIS_URL")
    if url:
        pool = redis.ConnectionPool.from_url(
            url,
            max_connections=int(os.getenv("REDIS_POOL_MAX", "50")),
            decode_responses=True,
            health_check_interval=30,
            retry_on_timeout=True,
        )
    else:
        pool = redis.ConnectionPool(
            host=os.getenv("REDIS_HOST", "127.0.0.1"),
            port=int(os.getenv("REDIS_PORT", "6379")),
            password=os.getenv("REDIS_PASSWORD") or None,
            db=int(os.getenv("REDIS_DB", "0")),
            decode_responses=True,
            max_connections=int(os.getenv("REDIS_POOL_MAX", "50")),
            socket_timeout=5,
            socket_connect_timeout=2,
            health_check_interval=30,
            retry_on_timeout=True,
        )
    return redis.Redis(connection_pool=pool)
参数 建议
max_connections API:按 QPS×RTT 估算;Worker:并发协程数 + 余量
进程模型 每进程一个 get_redis();不要全局跨 fork 共享池(Celery prefork 需在子进程重建)
TLS ConnectionPool(..., connection_class=redis.SSLConnection, ssl_cert_reqs="required")

异步(FastAPI / asyncio Agent):用独立池,勿与同步混用。

python 复制代码
import redis.asyncio as aioredis

@lru_cache
def get_redis_async() -> aioredis.Redis:
    return aioredis.from_url(
        os.getenv("REDIS_URL", "redis://127.0.0.1:6379/0"),
        max_connections=int(os.getenv("REDIS_POOL_MAX", "50")),
        decode_responses=True,
        socket_timeout=5,
    )

# async def handler():
#     r = get_redis_async()
#     await r.xread({stream: last_id}, block=5000)

Pipeline 与池r.pipeline() 仍走同一客户端的池;高吞吐写 checkpoint 时 batch 可减少 RTT(§10c)。


1.3 Sentinel(主从 + 自动故障转移)

一主多从 + Sentinel 选举时,用 Sentinel + master_for,底层同样是连接池。

python 复制代码
@lru_cache
def get_redis_sentinel() -> redis.Redis:
    sentinel = redis.Sentinel(
        [
            (os.getenv("SENTINEL_HOST", "127.0.0.1"), int(os.getenv("SENTINEL_PORT", "26379"))),
        ],
        socket_timeout=2,
        password=os.getenv("SENTINEL_PASSWORD") or None,
    )
    # 服务名与 sentinel monitor 配置一致,如 "mymaster"
    return sentinel.master_for(
        os.getenv("REDIS_SENTINEL_MASTER", "mymaster"),
        socket_timeout=5,
        password=os.getenv("REDIS_PASSWORD") or None,
        db=int(os.getenv("REDIS_DB", "0")),
        decode_responses=True,
    )

# 只读副本(会话读、缓存读)可路由到 slave_for,注意复制延迟
def get_redis_replica() -> redis.Redis:
    sentinel = redis.Sentinel([(os.getenv("SENTINEL_HOST", "127.0.0.1"), 26379)])
    return sentinel.slave_for(
        os.getenv("REDIS_SENTINEL_MASTER", "mymaster"),
        decode_responses=True,
    )

Agent 场景XREADGROUP 消费、分布式锁、幂等 必须写主;SSE 读历史/trace 在延迟敏感低时可读从库。


1.4 Redis Cluster(分片 + 水平扩展)

数据分片到多 master 时,使用 RedisClusterredis>=4.3)。客户端根据 CRC16 slot 路由命令。

python 复制代码
@lru_cache
def get_redis_cluster() -> RedisCluster:
    # 方式 B(多节点 URL):return RedisCluster.from_url(os.environ["REDIS_CLUSTER_URL"], ...)
    # 方式 A:至少配置一个种子节点,客户端会自动发现拓扑
    return RedisCluster(
        host=os.getenv("REDIS_CLUSTER_HOST", "127.0.0.1"),
        port=int(os.getenv("REDIS_CLUSTER_PORT", "6379")),
        password=os.getenv("REDIS_PASSWORD") or None,
        decode_responses=True,
        socket_timeout=5,
        require_full_coverage=False,  # slot 迁移期可放宽;稳定后配合监控
    )

def tenant_key(tenant: str, *parts: str) -> str:
    """Hash Tag:花括号内子串决定 slot,保证同租户多 key 可 pipeline / 事务。"""
    return ":".join([f"{{{tenant}}}", "harness", *parts])

# 示例:锁 + checkpoint 同 slot
# lock:   {acme}:harness:lock:repo:main
# session:{acme}:harness:session:sess_01
Cluster 限制 对 Agent 的影响
多 key 命令须同 slot MGET、事务、Lua 多 key → 用 同一 {tag}
KEYS 禁用 SCAN + 前缀;按 tenant 设计 key
Pub/Sub channel 不跨 slot 路由;大规模广播慎用,优先 Stream
XREAD 多 stream 多个 stream 可能在不同节点;多 stream 的 XREAD 要求所有 key 同 slot → 多 run 并行 tail 时用 每 run 独立连接轮询 ,或 每 run 一个带 tag 的 stream 名 且接受限制
db 编号 Cluster 仅 db 0

Stream 在 Cluster 上 :单个 Stream key 落在一个 slot,XADD/XREAD/XREADGROUP 与单机一致;不要 对多个不同 run 的 stream 做 一条 XREAD 跨 slot(§6.4 多 run 监听在 Cluster 下需改为循环单 stream 或聚合服务)。

python 复制代码
# Cluster 下推荐:单 run 单 stream(天然单 key)
def xread_one_run_cluster(run_id: str, last_id: str) -> list:
    stream = tenant_key("acme", "events", "run", run_id)  # 全 key 同一 {acme}
    rc = get_redis_cluster()
    return rc.xread({stream: last_id}, block=5000, count=50) or []

1.5 统一工厂(按环境变量切换)

python 复制代码
@lru_cache
def get_redis() -> redis.Redis | RedisCluster:
    mode = os.getenv("REDIS_MODE", "pool")  # single | pool | sentinel | cluster
    if mode == "single":
        return redis_single()
    if mode == "sentinel":
        return get_redis_sentinel()
    if mode == "cluster":
        return get_redis_cluster()
    return get_redis_pool()

# 后文业务示例统一:
r = get_redis()
REDIS_MODE 典型部署
pool 单机 Redis / 云厂商单实例
sentinel 自建主从 + Sentinel
cluster 大数据量、高写 QPS、需要分片

1.6 部署形态对照(选型)

形态 连接方式 扩展 Agent 常用
单机 ConnectionPool 垂直扩容 开发、中小规模
主从 + Sentinel Sentinel.master_for 读扩展、故障转移 生产主流
Redis Cluster RedisCluster 水平分片 海量 session / 多 Stream
托管(ElastiCache 等) 厂商 URL + TLS 按产品选 replica/cluster 少运维

小结 :正文示例逻辑不变,只把 r = redis.Redis(...) 换成 r = get_redis() ;上 Cluster 时 key 带 {tenant} tag ,并避免跨 slot 多 key XREAD


2. 会话态与 Harness Checkpoint(Hash + TTL)

长跑 Agent 跨多个 context window 时,Harness 需要 结构化交接物(进度、plan、最近工具输出摘要)。Redis Hash 适合「字段级更新」,避免每次全量覆盖一个大 JSON。

python 复制代码
SESSION_TTL = 60 * 60 * 24  # 24h

def save_checkpoint(session_id: str, patch: dict[str, Any]) -> None:
    k = key("harness", "session", session_id)
    pipe = r.pipeline()
    for field, value in patch.items():
        pipe.hset(k, field, json.dumps(value, ensure_ascii=False))
    pipe.expire(k, SESSION_TTL)
    pipe.execute()

def load_checkpoint(session_id: str) -> dict[str, Any]:
    k = key("harness", "session", session_id)
    raw = r.hgetall(k)
    return {f: json.loads(v) for f, v in raw.items()}

# 使用示例
sid = "sess_" + uuid.uuid4().hex[:12]
save_checkpoint(sid, {
    "phase": "coding",
    "features_done": ["auth", "billing"],
    "last_error": None,
})
state = load_checkpoint(sid)

Harness 要点 :Checkpoint 字段应由 Orchestrator 写入,Coding Agent 只读/增量写;避免把整段对话原文塞进 Redis(放对象存储 + 只存摘要与指针)。


3. 分布式锁:同一资源禁止并发工具执行(SET NX + Lua 安全释放)

例如:两个 Agent 实例同时改同一 Git 分支、同一 K8s Deployment。锁必须 带 token,释放时校验 token,防止误删他人锁。

python 复制代码
LOCK_TTL_MS = 30_000

def acquire_lock(resource: str, owner: str) -> str | None:
    token = f"{owner}:{uuid.uuid4().hex}"
    ok = r.set(
        key("harness", "lock", resource),
        token,
        nx=True,
        px=LOCK_TTL_MS,
    )
    return token if ok else None

_RELEASE_LUA = """
if redis.call("GET", KEYS[1]) == ARGV[1] then
  return redis.call("DEL", KEYS[1])
else
  return 0
end
"""

def release_lock(resource: str, token: str) -> bool:
    deleted = r.eval(
        _RELEASE_LUA,
        1,
        key("harness", "lock", resource),
        token,
    )
    return bool(deleted)

# 使用示例
resource = "repo:HarnessAgent:branch:main"
token = acquire_lock(resource, owner="worker-1")
if not token:
    raise RuntimeError("resource busy")
try:
    ...  # 执行 git pull / apply patch
finally:
    release_lock(resource, token)

生产提示 :长任务用 看门狗续期 (后台线程定期 PEXPIRE);更复杂场景可用 Redlock 或 etcd,但单 Redis 实例 + 合理 TTL 已覆盖多数 Agent 工具链。


4. 幂等键:防止重试导致重复扣费、重复发邮件(SET NX)

HTTP/Webhook 触发 Agent 时,客户端常会重试。Harness 在入口用 idempotency key 保证「同一业务请求只执行一次」。

python 复制代码
IDEM_TTL = 60 * 60  # 1h

def begin_idempotent(run_id: str) -> bool:
    """返回 True 表示首次执行;False 表示重复请求应直接返回缓存结果。"""
    return bool(
        r.set(
            key("harness", "idem", run_id),
            "processing",
            nx=True,
            ex=IDEM_TTL,
        )
    )

def finish_idempotent(run_id: str, result: dict[str, Any]) -> None:
    k = key("harness", "idem", run_id)
    r.set(k, json.dumps(result), ex=IDEM_TTL)

def get_idempotent_result(run_id: str) -> dict[str, Any] | None:
    v = r.get(key("harness", "idem", run_id))
    if not v or v == "processing":
        return None
    return json.loads(v)

5. Token / 成本预算与全局限流(INCR + 固定窗口 / 滑动窗口)

5.1 按租户日预算(简单固定窗口)

python 复制代码
def consume_token_budget(tenant: str, tokens: int, daily_limit: int) -> bool:
    k = key("harness", "budget", tenant, time.strftime("%Y%m%d"))
    used = r.incrby(k, tokens)
    if used == tokens:
        r.expire(k, 60 * 60 * 48)
    return used <= daily_limit

5.2 滑动窗口限流(ZSET,适合 API QPS)

python 复制代码
def allow_request(user_id: str, limit: int, window_sec: int) -> bool:
    now = time.time()
    k = key("harness", "rl", user_id)
    pipe = r.pipeline()
    pipe.zremrangebyscore(k, 0, now - window_sec)
    pipe.zadd(k, {str(uuid.uuid4()): now})
    pipe.zcard(k)
    pipe.expire(k, window_sec + 1)
    _, _, count, _ = pipe.execute()
    return count <= limit

Harness 要点 :限流应在 调用 LLM / 昂贵工具之前 由策略层执行,失败时返回结构化错误,便于 Agent 改计划而非盲目重试。


6. Stream 深度:XADD / XREAD / XREADGROUP(Agent 事件总线核心)

智能体系统里 Stream 比 List 多两样能力:每条消息有 ID(可回溯)消费者组( competing worker + ACK)

很多人只写了 XREADGROUP,却漏了 XREAD ------它才是 「多个订阅方各自读全量事件」(UI 实时日志、审计、旁路分析)的标准姿势。

6.0 命令速查:什么时候用谁

命令 典型场景 是否竞争消费 是否 ACK Agent 例子
XADD 写入事件 --- --- tool_start / tool_end / token_delta
XREAD Fan-out:每个读者独立游标 否(各自 last_id) 前端 SSE tail run;审计落库
XREAD + BLOCK 阻塞等新事件 Orchestrator 盯「人类已批准」事件流
XREADGROUP 任务队列:一条只给一个 Worker XACK 多实例跑 Coding Agent
XPENDING / XCLAIM 僵尸 pending 重领 重领后仍要 ACK Worker 崩溃后任务不丢
XRANGE 按 ID 区间重放 --- --- 复盘某次 run 的全链路
XTRIM / MAXLEN 控制内存 --- --- Trace 只保留最近 1 万条

XADD
XREAD 各自 last_id
XREAD 各自 last_id
XREADGROUP 竞争
XREADGROUP 竞争
Producer

Harness / Tool Runtime
Stream
UI / SSE
Audit / Metrics
Worker-1
Worker-2

6.1 List:轻量单消费者队列(对比用)

无消息 ID、无回溯、多消费者会抢同一条,只适合 极简后台任务

python 复制代码
QUEUE = key("harness", "queue", "tool-jobs")

def enqueue_job(payload: dict[str, Any]) -> None:
    r.lpush(QUEUE, json.dumps(payload))

def dequeue_job(block_sec: int = 5) -> dict[str, Any] | None:
    item = r.brpop(QUEUE, timeout=block_sec)
    if not item:
        return None
    _, raw = item
    return json.loads(raw)

6.2 XADD:统一事件信封(建议字段)

python 复制代码
EVENTS = key("harness", "events", "run")  # 实际: events:run:{run_id}

def emit(run_id: str, kind: str, data: dict[str, Any]) -> str:
    """返回 Redis Stream ID,如 1715000000000-0"""
    stream = f"{EVENTS}:{run_id}"
    return r.xadd(
        stream,
        {
            "kind": kind,           # tool_start | tool_end | llm_delta | error | done
            "data": json.dumps(data, ensure_ascii=False),
            "ts": str(time.time()),
        },
    )

# CLI 等价:
# XADD harness:events:run:sess_abc * kind tool_start data '{"tool":"bash"}'

6.3 XREAD:Fan-out 实时 tail(无消费者组)

要点 :每个读者维护自己的 last_id。从开头读用 0-0;只读新消息用 $(仅对「第一次 BLOCK 之后」有效,见 6.4)。

python 复制代码
def xread_once(stream: str, last_id: str = "0-0", count: int = 100) -> list[tuple[str, dict]]:
    """非阻塞:拉一批历史或增量。"""
    rows = r.xread({stream: last_id}, count=count)
    if not rows:
        return []
    out = []
    for _name, entries in rows:
        for msg_id, fields in entries:
            out.append((msg_id, fields))
    return out

def xread_block_loop(stream: str, last_id: str = "$", block_ms: int = 15_000):
    """
    阻塞 tail:Agent UI / 旁路监控的标准写法。
    last_id='$' 表示:先建立连接,之后只收新 XADD。
    """
    cursor = last_id
    while True:
        rows = r.xread({stream: cursor}, block=block_ms, count=50)
        if not rows:
            continue
        for _name, entries in rows:
            for msg_id, fields in entries:
                cursor = msg_id  # 关键:推进游标,避免重复
                yield msg_id, fields

Harness 场景:SSE 把 Agent 步进推给浏览器

python 复制代码
# FastAPI 风格伪代码
async def sse_run_events(run_id: str):
    stream = f"{EVENTS}:{run_id}"
    last_id = "0-0"  # 新连接可从开头回放;只要 live 可改为 $
    for msg_id, fields in xread_block_loop(stream, last_id=last_id):
        payload = {"id": msg_id, "kind": fields["kind"], "data": json.loads(fields["data"])}
        yield f"data: {json.dumps(payload)}\n\n"
        if fields["kind"] == "done":
            break

redis-cli 示例(可先 XADD 再 XREAD)

bash 复制代码
XADD harness:events:run:demo * kind tool_start data '{"tool":"grep"}'
XREAD COUNT 10 STREAMS harness:events:run:demo 0-0
# 阻塞 5s 等新消息(另开终端 XADD 可看到返回)
XREAD BLOCK 5000 STREAMS harness:events:run:demo $

6.4 XREAD:多 Stream、多 run 同时盯(Orchestrator)

一个调度器同时 watch 多个 run 的审批/完成事件:

python 复制代码
def xread_many(streams: dict[str, str], block_ms: int = 5000) -> list[tuple[str, str, dict]]:
    """
    streams = { stream_key: last_id, ... }
    返回 (stream_name, msg_id, fields)
    """
    rows = r.xread(streams, block=block_ms, count=20)
    result = []
    for stream_name, entries in rows:
        for msg_id, fields in entries:
            result.append((stream_name, msg_id, fields))
    return result

# 使用:同时监听 3 个 run 的 done 事件
cursors = {
    f"{EVENTS}:run_a": "$",
    f"{EVENTS}:run_b": "$",
    f"{EVENTS}:run_c": "$",
}
while True:
    batch = xread_many(cursors, block_ms=10_000)
    for stream_name, msg_id, fields in batch:
        cursors[stream_name] = msg_id
        if fields["kind"] == "done":
            ...  # 回收资源 / 更新 DB

6.5 XREAD vs XREADGROUP:选型口诀

需求
每条消息要被 UI、审计、指标 各读一遍 XREAD(各自 last_id)
每条任务只能被 一个 Worker 执行 XREADGROUP + XACK
读者挂了不能丢、要 pending 重领 XREADGROUP + XPENDING / XCLAIM
极简、不在乎 ID BRPOP List

6.6 XREADGROUP:竞争消费 + ACK(Orchestrator 任务队列)

python 复制代码
RUN_STREAM = key("harness", "stream", "runs")
GROUP = "orchestrator-workers"

def ensure_group(stream: str = RUN_STREAM, start_id: str = "0") -> None:
    try:
        r.xgroup_create(stream, GROUP, id=start_id, mkstream=True)
    except redis.ResponseError as e:
        if "BUSYGROUP" not in str(e):
            raise

def publish_run(run: dict[str, Any]) -> str:
    return r.xadd(RUN_STREAM, {"payload": json.dumps(run)})

def consume_runs_group(consumer: str, count: int = 1, block_ms: int = 5000):
    """
    '>' = 只拉从未投递给本组的新消息。
    处理成功后必须 xack,否则进入 pending。
    """
    ensure_group()
    rows = r.xreadgroup(
        groupname=GROUP,
        consumername=consumer,
        streams={RUN_STREAM: ">"},
        count=count,
        block=block_ms,
    )
    for _stream, entries in rows or []:
        for msg_id, fields in entries:
            yield msg_id, json.loads(fields["payload"])

def ack_run(msg_id: str) -> None:
    r.xack(RUN_STREAM, GROUP, msg_id)

# Worker 主循环
def worker_loop(consumer: str) -> None:
    for msg_id, run in consume_runs_group(consumer):
        try:
            execute_agent_run(run)  # 你的 Harness 入口
            ack_run(msg_id)
        except Exception:
            # 不 ACK → 留在 pending,由 XPENDING/XCLAIM 处理
            raise

redis-cli

bash 复制代码
XGROUP CREATE harness:stream:runs orchestrator-workers 0 MKSTREAM
XADD harness:stream:runs * payload '{"run_id":"r1"}'
XREADGROUP GROUP orchestrator-workers w1 COUNT 1 BLOCK 5000 STREAMS harness:stream:runs >
XACK harness:stream:runs orchestrator-workers <msg-id>

6.7 启动时先清 pending:0> 的区别

Stream 参数 含义
> 只要 新进入组 的消息(常规主循环)
0 pending 列表 里拿本 consumer 未 ACK 的历史(重启恢复)
python 复制代码
def consume_pending_self(consumer: str, count: int = 10):
    """进程重启后,先处理自己名下的 pending,再切 '>'."""
    rows = r.xreadgroup(GROUP, consumer, {RUN_STREAM: "0"}, count=count)
    for _s, entries in rows or []:
        for msg_id, fields in entries:
            yield msg_id, json.loads(fields["payload"])

6.8 XPENDING / XCLAIM:Worker 崩溃不丢任务

python 复制代码
def pending_summary() -> list[dict]:
    """查看组内未 ACK 消息(idle 过久可被别的 worker 领走)。"""
    # redis-py: xpending_range 封装
    pending = r.xpending_range(RUN_STREAM, GROUP, min="-", max="+", count=20)
    return [
        {
            "id": p["message_id"],
            "consumer": p["consumer"],
            "idle_ms": p["time_since_delivered"],
            "delivery_count": p["times_delivered"],
        }
        for p in pending
    ]

def claim_stale(min_idle_ms: int = 60_000, new_consumer: str = "worker-recovery") -> list[str]:
    """idle 超过阈值的 pending 划归新 consumer(Agent 节点宕机场景)。"""
    pending = r.xpending_range(RUN_STREAM, GROUP, min="-", max="+", count=50)
    claimed_ids = []
    for p in pending:
        if p["time_since_delivered"] < min_idle_ms:
            continue
        msgs = r.xclaim(
            RUN_STREAM,
            GROUP,
            new_consumer,
            min_idle_time=min_idle_ms,
            message_ids=[p["message_id"]],
        )
        for msg_id, fields in msgs:
            claimed_ids.append(msg_id)
            retry_run(json.loads(fields["payload"]))
            r.xack(RUN_STREAM, GROUP, msg_id)
    return claimed_ids

Redis 7+ 可用 XAUTOCLAIM 一次拉一批 idle 消息(语义同多次 XCLAIM)。

6.9 XRANGE / XREVRANGE:按 ID 重放与调试

python 复制代码
def replay_run(run_id: str, start: str = "-", end: str = "+", count: int = 500) -> list[dict]:
    stream = f"{EVENTS}:{run_id}"
    entries = r.xrange(stream, min=start, max=end, count=count)
    events = []
    for msg_id, fields in entries:
        events.append({
            "id": msg_id,
            "kind": fields["kind"],
            "data": json.loads(fields["data"]),
            "ts": float(fields["ts"]),
        })
    return events

def last_n_events(run_id: str, n: int = 20) -> list[dict]:
    stream = f"{EVENTS}:{run_id}"
    entries = r.xrevrange(stream, max="+", min="-", count=n)
    return list(reversed([
        {"id": mid, "kind": f["kind"]} for mid, f in entries
    ]))

用途 :用户投诉「Agent 为什么删了文件」→ 用 XRANGE 还原 tool_start/tool_end 时间线,无需翻模型对话。

6.10 XTRIM / XADD MAXLEN:Trace 防爆内存

python 复制代码
def append_trace_capped(run_id: str, event: dict[str, Any], maxlen: int = 10_000) -> str:
    stream = f"{EVENTS}:{run_id}"
    # approximate=True 性能更好,长度略有浮动
    return r.xadd(stream, {"e": json.dumps(event)}, maxlen=maxlen, approximate=True)

def trim_old(stream: str, maxlen: int = 50_000) -> int:
    return r.xtrim(stream, maxlen=maxlen, approximate=True)

6.11 完整对照:同一条 run 的三类读者

审计 XREAD last_id Worker XREADGROUP 前端 SSE XREAD last_id Stream events:run:1 Harness 审计 XREAD last_id Worker XREADGROUP 前端 SSE XREAD last_id Stream events:run:1 Harness XADD tool_start XREAD BLOCK 推送 XREAD BLOCK 写 ClickHouse XADD job_step XREADGROUP 投递 执行工具 XADD tool_end XACK

python 复制代码
# 三行总结
# UI / 审计:  r.xread({stream: last_id}, block=15000)
# 任务 Worker: r.xreadgroup(GROUP, consumer, {stream: ">"}, ...)
# 历史复盘:    r.xrange(stream, "-", "+")

7. Pub/Sub:多 Agent 实时协调(注意:无持久化)

适合 进度广播、取消信号;不适合「至少一次」任务投递(用 Stream)。

python 复制代码
CHANNEL_CANCEL = key("harness", "channel", "cancel")

def publish_cancel(run_id: str) -> None:
    r.publish(CHANNEL_CANCEL, run_id)

def subscribe_cancel():
    pubsub = r.pubsub()
    pubsub.subscribe(CHANNEL_CANCEL)
    for msg in pubsub.listen():
        if msg["type"] == "message":
            yield msg["data"]  # run_id

Worker 在长跑循环中轮询取消:

python 复制代码
def is_cancelled(run_id: str) -> bool:
    return r.sismember(key("harness", "cancelled"), run_id)

def mark_cancelled(run_id: str) -> None:
    r.sadd(key("harness", "cancelled"), run_id)
    r.expire(key("harness", "cancelled"), 3600)
    publish_cancel(run_id)

8. LLM 响应缓存与语义缓存(String + TTL)

8.1 精确缓存(相同 model + messages hash)

python 复制代码
import hashlib

def cache_key(model: str, messages: list[dict]) -> str:
    blob = json.dumps({"model": model, "messages": messages}, sort_keys=True)
    h = hashlib.sha256(blob.encode()).hexdigest()
    return key("harness", "llm-cache", h)

def get_cached_llm(model: str, messages: list[dict]) -> str | None:
    return r.get(cache_key(model, messages))

def set_cached_llm(model: str, messages: list[dict], text: str, ttl: int = 3600) -> None:
    r.setex(cache_key(model, messages), ttl, text)

8.2 语义缓存(思路)

  1. 对 user query 算 embedding;
  2. 在 Redis Vector(Redis Stack)或外部向量库做 近似最近邻
  3. 距离低于阈值则直接返回历史答案。

Harness 层应记录 cache_hit 指标,并允许按租户关闭缓存(合规场景)。


9. 人机回路(HITL):审批队列与超时(List + ZSET)

python 复制代码
APPROVAL_QUEUE = key("harness", "approval", "pending")
APPROVAL_DEADLINE = key("harness", "approval", "deadline")

def request_approval(run_id: str, summary: str, timeout_sec: int = 1800) -> None:
    payload = json.dumps({"run_id": run_id, "summary": summary})
    r.lpush(APPROVAL_QUEUE, payload)
    r.zadd(APPROVAL_DEADLINE, {run_id: time.time() + timeout_sec})

def pop_approval() -> dict | None:
    item = r.brpop(APPROVAL_QUEUE, timeout=1)
    if not item:
        return None
    return json.loads(item[1])

def approve(run_id: str) -> None:
    r.setex(key("harness", "approval", "ok", run_id), 3600, "1")
    r.zrem(APPROVAL_DEADLINE, run_id)

def is_approved(run_id: str) -> bool:
    return r.exists(key("harness", "approval", "ok", run_id)) == 1

def expired_run_ids(now: float | None = None) -> list[str]:
    now = now or time.time()
    return r.zrangebyscore(APPROVAL_DEADLINE, 0, now)

Agent 在执行危险工具前阻塞等待 is_approved 或超时策略(自动拒绝/降级)。


10. 工具 Trace:XADD 写 + XREAD 推(与 §6 同一套流)

调试与可观测的标准路径:Harness 用 XADD 写步进;前端/CLI 用 XREAD BLOCK tail;复盘用 XRANGE(见 §6.9、§6.10)。不要只用 Pub/Sub------断线期间事件会丢。

python 复制代码
TRACE_PREFIX = key("harness", "trace", "run")

def trace_stream(run_id: str) -> str:
    return f"{TRACE_PREFIX}:{run_id}"

def append_trace(run_id: str, event: dict[str, Any], maxlen: int = 10_000) -> str:
    return r.xadd(
        trace_stream(run_id),
        {"e": json.dumps(event, ensure_ascii=False)},
        maxlen=maxlen,
        approximate=True,
    )

def tail_trace_cli(run_id: str) -> None:
    """本地调试:打印 live trace。"""
    stream = trace_stream(run_id)
    last_id = "0-0"
    while True:
        rows = r.xread({stream: last_id}, block=5000, count=100)
        if not rows:
            continue
        for _n, entries in rows:
            for msg_id, fields in entries:
                last_id = msg_id
                print(msg_id, json.loads(fields["e"]))

10b. 延迟重试:ZSET 调度 + Stream 触发(Agent 工具失败退避)

工具调用失败需要 指数退避 时:ZSET 存「何时可重试」,调度器 ZRANGEBYSCORE 到期后 XADD 到任务 Stream,Worker 仍用 XREADGROUP 消费。

python 复制代码
RETRY_ZSET = key("harness", "retry", "at")
RETRY_STREAM = key("harness", "stream", "retries")

def schedule_retry(run_id: str, payload: dict, execute_at: float) -> None:
    r.zadd(RETRY_ZSET, {json.dumps({"run_id": run_id, **payload}): execute_at})

def flush_due_retries(now: float | None = None) -> int:
    now = now or time.time()
    due = r.zrangebyscore(RETRY_ZSET, 0, now, start=0, num=100)
    if not due:
        return 0
    pipe = r.pipeline()
    for item in due:
        pipe.xadd(RETRY_STREAM, {"payload": item})
        pipe.zrem(RETRY_ZSET, item)
    pipe.execute()
    return len(due)

# 调度循环(独立进程)
# while True: flush_due_retries(); sleep(1)
# Worker: xreadgroup 消费 RETRY_STREAM

10c. Pipeline:批量写 Checkpoint(降 RTT)

Agent 一步可能更新多个 Hash 字段 + 发多条事件,用 pipeline 合并往返:

python 复制代码
def save_step_bundle(session_id: str, checkpoint: dict, events: list[dict]) -> None:
    sk = key("harness", "session", session_id)
    ek = f"{EVENTS}:{session_id}"
    pipe = r.pipeline()
    for field, value in checkpoint.items():
        pipe.hset(sk, field, json.dumps(value))
    pipe.expire(sk, SESSION_TTL)
    for ev in events:
        pipe.xadd(ek, {"kind": ev["kind"], "data": json.dumps(ev["data"])})
    pipe.execute()

11. 多 Agent 分工:租约与心跳(String + TTL)

Initializer / Coding / Reviewer 多角色时,用 租约 标记当前谁「持有」该 run:

python 复制代码
def claim_run(run_id: str, role: str, ttl_sec: int = 120) -> bool:
    return bool(
        r.set(
            key("harness", "lease", run_id),
            role,
            nx=True,
            ex=ttl_sec,
        )
    )

def heartbeat(run_id: str, role: str, ttl_sec: int = 120) -> None:
  # 仅当仍是同一 role 时续期(Lua 更安全,此处略)
    current = r.get(key("harness", "lease", run_id))
    if current == role:
        r.expire(key("harness", "lease", run_id), ttl_sec)

12. 特性开关与熔断状态(Hash / String)

python 复制代码
FLAGS = key("harness", "flags")

def flag_enabled(name: str, default: bool = False) -> bool:
    v = r.hget(FLAGS, name)
    if v is None:
        return default
    return v == "1"

def record_tool_failure(tool: str) -> int:
    k = key("harness", "cb", tool)
    n = r.incr(k)
    r.expire(k, 300)
    return n

def circuit_open(tool: str, threshold: int = 5) -> bool:
    return record_tool_failure(tool) >= threshold  # 示例:应用层读次数后拒绝

17. 前后端:会话、SSE 桥、上传凭证、并发 Run 上限

智能体控制台常见链路:前端 → API → Redis → Worker 。Redis 存 会话与 run 元数据步进流 用 Stream,API 层 XREAD 转 SSE(§6.3)。

17.1 服务端会话(替代把 state 全塞 JWT)

python 复制代码
def create_user_session(user_id: str, meta: dict) -> str:
    sid = "usess_" + uuid.uuid4().hex[:16]
    k = key("harness", "web", "session", sid)
    r.hset(k, mapping={
        "user_id": user_id,
        "meta": json.dumps(meta),
    })
    r.expire(k, 60 * 60 * 8)
    return sid

def bind_run_to_session(session_id: str, run_id: str) -> None:
    r.sadd(key("harness", "web", "session", session_id, "runs"), run_id)
    r.expire(key("harness", "web", "session", session_id, "runs"), 60 * 60 * 8)

17.2 BFF:Redis Stream → SSE(前端消费端)

python 复制代码
# GET /api/runs/{run_id}/events  (FastAPI/Flask 伪代码)
def sse_from_redis(run_id: str, after_id: str | None = None):
    stream = f"{EVENTS}:{run_id}"
    last_id = after_id or "0-0"
    while True:
        batches = r.xread({stream: last_id}, block=20_000, count=200)
        if not batches:
            yield ": keepalive\n\n"
            continue
        for _n, entries in batches:
            for msg_id, fields in entries:
                last_id = msg_id
                yield f"id: {msg_id}\ndata: {json.dumps(fields)}\n\n"
                if fields.get("kind") == "done":
                    return

前端用 EventSource;断线重连带 Last-Event-ID = Redis msg id,即可续传。

17.3 短期上传凭证(工具产物、截图进沙箱)

python 复制代码
def issue_upload_token(run_id: str, ttl: int = 600) -> str:
    token = uuid.uuid4().hex
    r.setex(key("harness", "upload", token), ttl, run_id)
    return token

def verify_upload_token(token: str) -> str | None:
    return r.get(key("harness", "upload", token))

17.4 每用户并发 Run 上限(前后端都拦)

python 复制代码
def try_start_run(user_id: str, run_id: str, max_concurrent: int = 3) -> bool:
    k = key("harness", "user", user_id, "active_runs")
    n = r.scard(k)
    if n >= max_concurrent:
        return False
    r.sadd(k, run_id)
    r.expire(k, 3600)
    return True

def finish_run(user_id: str, run_id: str) -> None:
    r.srem(key("harness", "user", user_id, "active_runs"), run_id)

18. 端云一体:边缘 Agent、离线队列、云端同步

端云一体 指:边缘设备/桌面端也跑轻量 Agent(采集、预处理),云端 跑重模型与工具;Redis 在云端做 汇聚与指令下发

18.1 设备注册与心跳

python 复制代码
def device_heartbeat(device_id: str, meta: dict) -> None:
    now = time.time()
    r.hset(key("harness", "device", device_id), mapping={
        "last_seen": str(now),
        "meta": json.dumps(meta),
    })
    r.zadd(key("harness", "devices", "online"), {device_id: now})

def stale_devices(older_than_sec: int = 120) -> list[str]:
    cutoff = time.time() - older_than_sec
    return r.zrangebyscore(key("harness", "devices", "online"), 0, cutoff)

18.2 云端 → 边缘:下行指令 Stream

边缘端通过 长轮询 APIMQTT 网关 间接读 Redis;云端直接 XADD

python 复制代码
DOWNLINK = key("harness", "downlink")  # 实际: downlink:{device_id}

def push_command(device_id: str, cmd: dict) -> str:
    stream = f"{DOWNLINK}:{device_id}"
    return r.xadd(stream, {"cmd": json.dumps(cmd)})

# 边缘同步服务(云端侧代理)对单设备 XREAD
def fetch_commands(device_id: str, cursor: str) -> tuple[str, list[dict]]:
    stream = f"{DOWNLINK}:{device_id}"
    rows = r.xread({stream: cursor}, block=1000, count=50)
    cmds, last = [], cursor
    for _n, entries in rows or []:
        for mid, fields in entries:
            last = mid
            cmds.append(json.loads(fields["cmd"]))
    return last, cmds

18.3 边缘 → 云端:上行合并 + 版本冲突

python 复制代码
def edge_push_checkpoint(device_id: str, version: int, patch: dict) -> bool:
    """CAS:仅当云端 version 小于边缘上报 version 时合并。"""
    k = key("harness", "edge", "state", device_id)
    current = int(r.hget(k, "version") or 0)
    if version <= current:
        return False  # 云端更新,边缘应拉取
    pipe = r.pipeline()
    pipe.hset(k, "version", version)
    for f, v in patch.items():
        pipe.hset(k, f, json.dumps(v))
    pipe.execute()
    return True

Harness 要点 :大文件、日志正文 不要 进 Redis;只同步 指针 (S3 key)与 结构化进度


19. 流式:LLM Token、工具 stdout、多端同时 tail

「流式」在 Agent 里分两层:

  1. 模型 Token 流(delta);
  2. Harness 事件流(tool_start、log_line、done)。

推荐:统一写入同一 Streamkind 区分;前端一条 SSE 订阅。

python 复制代码
def emit_token_delta(run_id: str, delta: str, seq: int) -> str:
    return emit(run_id, "llm_delta", {"text": delta, "seq": seq})

def emit_log_line(run_id: str, line: str) -> str:
    return emit(run_id, "log_line", {"line": line})

# 可选:Pub/Sub 做「极低延迟、可丢」的 Token 旁路(仅调试)
TOKEN_CHAN = key("harness", "channel", "token")

def publish_token_ephemeral(run_id: str, delta: str) -> None:
    r.publish(TOKEN_CHAN, json.dumps({"run_id": run_id, "delta": delta}))
方式 延迟 断线可续 适用
Stream + XREAD ✓(msg id) 生产 UI、审计
Pub/Sub 最低 临时调试、同源多订阅
仅 HTTP chunked 无 Redis 时的兜底

Gateway 聚合示例:Worker XADD → API XREAD BLOCK → 客户端 SSE(§17.2)。


20. 定时任务:ZSET 时间轮、周期扫 pending、会话清理

除 §10b 延迟重试 外,智能体系统常见 三类定时

类型 做法 示例
一次性延迟 ZSET score=unix ts 30s 后重试工具
周期扫描 Cron 进程 + ZRANGEBYSCORE 每 1min 清理过期 run
租约超时 Key TTL + 扫描 Worker 死亡释放锁
python 复制代码
SCHEDULE = key("harness", "schedule", "once")
CRON_TICK = key("harness", "cron", "last_tick")

def schedule_once(job_id: str, payload: dict, run_at: float) -> None:
    r.zadd(SCHEDULE, {json.dumps({"id": job_id, **payload}): run_at})

def run_scheduler_tick() -> int:
    now = time.time()
    due = r.zrangebyscore(SCHEDULE, 0, now, start=0, num=200)
    if not due:
        return 0
    for item in due:
        data = json.loads(item)
        publish_run(data)  # 投入 §6.6 的 RUN_STREAM
        r.zrem(SCHEDULE, item)
    r.set(CRON_TICK, str(now))
    return len(due)

# 周期:清理 7 天前已 done 的 trace stream(应用层按 run 列表扫)
def cleanup_trace_streams(run_ids: list[str]) -> None:
    for rid in run_ids:
        r.delete(f"{EVENTS}:{rid}")

生产环境可用 K8s CronJob / 云函数run_scheduler_tick;Redis 只存 调度事实,不替代完整 Job 系统(复杂 DAG 用 Temporal 等)。


21. 发布 + 消费:Pub/Sub、Stream、List 怎么选

模式 命令 语义 Agent 用法
广播 PUBLISH / SUBSCRIBE 在线订阅者收到;离线即丢 全局「停服开关」、取消 run
持久日志 XADD + XREAD 可回放;多读者独立游标 SSE、审计、复盘
任务消费 XADD + XREADGROUP 竞争 + ACK + pending Worker 执行 run
简单队列 LPUSH + BRPOP 单消费者为主 内部脚本任务
python 复制代码
# 模式订阅:某租户下所有 run 取消
def publish_tenant_cancel(tenant: str, run_id: str) -> None:
    r.publish(key("harness", "cancel", tenant), run_id)

def subscribe_tenant_cancel(tenant: str):
    ch = key("harness", "cancel", tenant)
    ps = r.pubsub()
    ps.subscribe(ch)
    for msg in ps.listen():
        if msg["type"] == "message":
            yield msg["data"]

注意 :Pub/Sub 在 Redis Cluster 下 channel 与节点绑定,跨 slot 无事务;高可靠 取消/审批 建议 Stream 或 DB 为准,Pub/Sub 只做加速通知。


22. 其他高频场景(速查 + 片段)

22.1 Leader 选举:单实例扫 XPENDING

python 复制代码
LEADER = key("harness", "leader", "scheduler")

def acquire_leader(holder: str, ttl: int = 15) -> bool:
    return bool(r.set(LEADER, holder, nx=True, ex=ttl))

def renew_leader(holder: str, ttl: int = 15) -> None:
    if r.get(LEADER) == holder:
        r.expire(LEADER, ttl)

# 仅 leader 调用 claim_stale() / run_scheduler_tick()

22.2 死信队列 DLQ

python 复制代码
DLQ = key("harness", "stream", "dlq")

def move_to_dlq(original_id: str, payload: dict, error: str) -> None:
    r.xadd(DLQ, {
        "original_id": original_id,
        "payload": json.dumps(payload),
        "error": error[:2000],
    })

22.3 全局 Run 号

python 复制代码
def next_run_id() -> str:
    n = r.incr(key("harness", "seq", "run"))
    return f"run_{n}"

22.4 Webhook 去重(短窗口)

python 复制代码
def webhook_seen(delivery_id: str, ttl: int = 86400) -> bool:
    """True=已处理过,应跳过。"""
    return not r.set(key("harness", "webhook", delivery_id), "1", nx=True, ex=ttl)

22.5 多租户 Key(Cluster Hash Tag)

与 §1.4 相同,生产 Cluster 必须用 tag:

python 复制代码
def tenant_key(tenant: str, *parts: str) -> str:
    return ":".join([f"{{{tenant}}}", "harness", *parts])

22.6 Serverless Function 无状态

每次 invocation:从 Redis load_checkpoint → 执行一步 → save_checkpoint + emit不要假设本机内存有上一轮状态。

22.7 指标:HyperLogLog 估算 UV / 工具种类

python 复制代码
def track_tool_use(run_id: str, tool_name: str) -> None:
    r.pfadd(key("harness", "hll", "tools", run_id), tool_name)

def approx_tool_count(run_id: str) -> int:
    return r.pfcount(key("harness", "hll", "tools", run_id))

13. 与 Harness Engineering 的映射(面试/设计评审用)

Harness 模块 Redis 典型用法 场景章节
记忆 / 状态 Session Hash、Checkpoint、租约 §2、§17、§18
规划 / 调度 XREADGROUP 队列;ZSET 定时/延迟 §6、§10b、§20
执行 / 工具 分布式锁、幂等、熔断 §3、§4、§12
校验 / 反思 LLM 缓存;XRANGE 重放 §8、§6.9
安全 / 权限 限流、预算、HITL、上传凭证 §5、§9、§17
观测 XREAD tail、Trace、XRANGE §6、§10、§19
前后端 / BFF SSE 桥、并发 Run 上限 §17
端云一体 设备心跳、上下行 Stream、CAS §18
流式输出 Token/log Stream;Pub/Sub 旁路 §19
发布消费 Pub/Sub 广播 vs Stream 持久 §7、§21

Redis
Harness Engineering
策略 / 预算
调度
执行
校验
限流/预算
队列/Stream
锁/幂等
会话/缓存


14. 生产环境实战(连接池 · TLS · ACL · 监控 · Key 规范)

开篇「生产请配合连接池、TLS、ACL、监控与 Key 命名规范」的 落地展开。与 §1(连怎么接)、§16(和 Kafka/Temporal 分工)配合阅读。
Redis 生产
安全
Agent 服务
API / BFF
Workers
TLS in-transit
ACL 最小权限
连接池
Exporter + 告警


14.1 连接池(生产必做)

已有示例 :§1.2 同步池、§1.2 异步池、§1.5 get_redis() 工厂。

实践 说明
进程单例 @lru_cache 或应用 lifespan 内创建 一个 Redis(connection_pool=...)
禁止 per-request 每个 HTTP 请求 Redis(host=...)连接风暴、TIME_WAIT 暴涨
多 worker Gunicorn/Uvicorn workers=N每进程一个池max_connections × workers 小于 Redis maxclients
Celery prefork 子进程 fork 后重建 池(父进程池不可继承)
阻塞命令 XREAD BLOCKBRPOP占满连接 ;阻塞消费者用 独立池 或专用连接,与 API 池分离
优雅退出 shutdown 时 pool.disconnect()

容量粗算

text 复制代码
Redis maxclients 余量 > API_pool_max × API_workers
                        + Worker_pool_max × Worker_replicas
                        + 阻塞 tail 连接数
                        + 20% 运维余量
python 复制代码
# 阻塞消费(SSE 代理 / XREADGROUP)建议独立池,避免占光 API 池
@lru_cache
def get_redis_blocking() -> redis.Redis:
    pool = redis.ConnectionPool.from_url(
        os.environ["REDIS_URL"],
        max_connections=int(os.getenv("REDIS_BLOCKING_POOL_MAX", "10")),
        decode_responses=True,
        socket_timeout=None,  # 长 BLOCK 不宜过短 socket_timeout
    )
    return redis.Redis(connection_pool=pool)

重试 :启用 retry_on_timeout=True;业务层对 ConnectionError / TimeoutError 做指数退避;写操作 结合幂等键(§4)避免重试双写。


14.2 Key 命名规范(生产)

14.2.1 推荐格式
text 复制代码
harness:{env}:{tenant}:{domain}:{entity}:{id}[:{field}]
         │      │        │       │       │
         prod   acme     session sess_01  (Hash field 另说)
规则 示例
harness 固定产品前缀 多产品共集群时防冲突
{env} dev / staging / prod 禁止 prod 与 dev 共 db 且无 env 段
{tenant} 多租户必填;单租户可用 _ Cluster 用 {acme} Hash Tag 包住 tenant
{domain} 业务域 sessionstreamlockrlcache
{entity} 类型 runuserdevice
{id} 主键 run_128usess_abc
python 复制代码
def tenant_key(tenant: str, *parts: str) -> str:
    """Cluster:{tenant} 决定 slot。"""
    return ":".join(["harness", ENV, f"{{{tenant}}}", *parts])

def key_no_tenant(*parts: str) -> str:
    return ":".join(["harness", ENV, *parts])

# ✓ harness:prod:{acme}:session:sess_01
# ✓ harness:prod:{acme}:events:run:run_128
# ✗ session:sess_01          (无 env/租户,难治理)
# ✗ harness:prod:run:1       (易与其它 run 混淆)
14.2.2 TTL 与体积
Key 类型 建议 TTL 值大小
Web 会话 8h--24h < 10KB
Run checkpoint 24h--7d(done 后主动删) < 64KB;更大放 S3
分布式锁 30s--5min + 看门狗 token 字符串
幂等 1h--24h 结果摘要,非全文
LLM 缓存 1h--24h 注意 PII;可按租户关
Trace Stream MAXLEN ~ + 定期删 run 单行 < 4KB
14.2.3 禁止项
  • 不要 KEYS harness:* 在生产执行 → 用 SCAN + 前缀,或按 tenant 维护索引 Set。
  • 不要 把 API Key、用户密码、完整 prompt 写入 Redis(合规与泄漏面)。
  • 不要 无 TTL 的临时 key。
  • 不要db1/db2 区分租户(Cluster 仅 db0;用 key 前缀)。
  • 不要 超大 String(> 512KB 警惕;> 1MB 改对象存储)。
14.2.4 按实体速查表
实体 Key 模式 结构 TTL
用户会话 ...:web:session:{sid} Hash 8h
Run checkpoint ...:{t}:session:run:{rid} Hash 7d
任务队列 ...:stream:runs Stream 持久/MAXLEN
Run 事件 tail ...:{t}:events:run:{rid} Stream MAXLEN
分布式锁 ...:{t}:lock:{resource} String 30s
幂等 ...:idem:{id} String 1h
限流 ...:rl:{user} ZSET 窗口+1
日预算 ...:budget:{tenant}:{date} String 48h
上传凭证 ...:upload:{token} String 10min
特性开关 ...:flags Hash 无或长 TTL

14.3 TLS(传输加密)

生产 必须 加密公网或跨 VPC 流量。托管 Redis 通常提供 rediss://(Redis over TLS)。

14.3.1 URL 方式(推荐)
python 复制代码
# rediss:// 默认 SSL;云厂商常带证书校验
REDIS_URL = "rediss://:password@redis.example.com:6379/0"

pool = redis.ConnectionPool.from_url(
    REDIS_URL,
    max_connections=50,
    decode_responses=True,
    ssl_cert_reqs="required",           # 校验服务端证书
    ssl_ca_certs=os.getenv("REDIS_CA_CERT"),  # 自签或私有 CA 时指定
)
r = redis.Redis(connection_pool=pool)
14.3.2 显式 SSLConnection
python 复制代码
pool = redis.ConnectionPool(
    host="redis.example.com",
    port=6380,
    password=os.environ["REDIS_PASSWORD"],
    connection_class=redis.SSLConnection,
    ssl_cert_reqs="required",
    ssl_ca_certs="/etc/ssl/certs/redis-ca.pem",
    max_connections=50,
    decode_responses=True,
)
建议
ssl_cert_reqs 生产用 required ,勿 none
证书轮换 CA 放 Secret/文件,支持热更新池重建
Cluster / Sentinel 各节点域名须在证书 SAN 内,或用云厂商统一入口
性能 TLS 有 CPU 开销;同 AZ 部署、连接池复用可降低握手次数

14.4 ACL(访问控制,最小权限)

Redis 6+ ACL :按角色拆分 API 只读+写会话Worker 消费 Stream运维只读 INFO ,禁用 FLUSHALLCONFIGDEBUG

14.4.1 服务端配置示例(redis.conf / 托管控制台)
text 复制代码
# 应用 API:会话、限流、读 trace;不写全局 stream 消费组
ACL SETUSER harness_api on >api_secret ~harness:prod:* +@read +@write -@dangerous +xadd +xread +hset +set +incr +expire

# Worker:队列消费、锁、checkpoint
ACL SETUSER harness_worker on >worker_secret ~harness:prod:* +@read +@write -@dangerous +xreadgroup +xack +xclaim +set +eval

# 只读监控/排障
ACL SETUSER harness_ro on >ro_secret ~harness:* +@read +info +slowlog|get

# 保留 default 仅本地 admin,生产禁用弱口令

具体 +/- 命令集按你实际用到的命令微调;原则:能写 stream 的不给 FLUSHDB

14.4.2 客户端使用不同账号
python 复制代码
def get_redis_api() -> redis.Redis:
    return redis.Redis.from_url(
        os.environ["REDIS_URL_API"],  # redis://harness_api:...@host/0
        ssl_cert_reqs="required",
        max_connections=30,
    )

def get_redis_worker() -> redis.Redis:
    return redis.Redis.from_url(
        os.environ["REDIS_URL_WORKER"],
        ssl_cert_reqs="required",
        max_connections=50,
    )
实践 说明
密钥管理 密码放 K8s Secret / Vault,不进镜像与 Git
命令面 Agent 禁止 把 Redis 当 Shell;工具层白名单命令
网络 安全组仅允许应用子网访问 6379;不暴露公网
审计 开启慢日志;敏感操作走变更流程

14.5 监控与告警

14.5.1 采集方式
方式 用途
redis_exporter → Prometheus 生产标准:QPS、内存、连接、延迟
云监控 ElastiCache / Tair 自带 CPU、连接、命中率
应用埋点 池等待时间、XREADGROUP lag、业务 cache hit
INFO / SLOWLOG 排障
14.5.2 Agent 系统必盯指标
指标 来源 告警建议
redis_memory_used_bytes exporter > 75% maxmemory
redis_connected_clients exporter 接近 maxclients
redis_commands_duration_seconds exporter / APM P99 突增
blocked_clients INFO > 0 持续(大量 BLOCK)
Stream lag XINFO GROUPSlag lag > 阈值(Worker 跟不上)
Stream pending XPENDING 持续增长(ACK 失败/僵死)
键空间 自定义 某前缀 key 数量日环比异常
复制延迟 master_repl_offset 读从库时 > 1s 警惕
python 复制代码
def stream_group_lag(stream: str, group: str) -> dict:
    """运维脚本:拉消费组积压。"""
    groups = r.xinfo_groups(stream)
    for g in groups:
        if g["name"] == group:
            return {"lag": g["lag"], "pending": g["pending"], "consumers": g["consumers"]}
    return {}
14.5.3 日志与追踪
  • 应用日志:记录 run_idtenant、Redis 命令失败(打完整 value)。
  • OpenTelemetry:span 标注 db.system=redisdb.operation(如 XREADGROUP)。
  • 慢查询:CONFIG SET slowlog-log-slower-than 10000(10ms)+ 定期 SLOWLOG GET
14.5.4 告警 Runbook(简)
告警 可能原因 动作
内存高 Trace 无 MAXLEN、缓存无 TTL 查大 key、MEMORY USAGE key
pending 涨 Worker 宕机、处理抛错未 ACK XPENDINGXCLAIM / 修 Worker
连接数爆 池未复用、BLOCK 连接过多 查代码、拆阻塞池
主从切换 Sentinel 故障转移 客户端应自动重连;检查 master_for

14.6 持久化、备份与高可用

场景 RDB/AOF 说明
纯缓存、可重建会话 可关或仅 RDB 丢了可接受
Stream 任务队列 AOF everysec 或托管自动备份 宕机少丢 pending
审批 / 计费相关 key Redis + DB 双写,以 DB 为准 Redis 不是唯一真相
  • maxmemory-policy :缓存类用 allkeys-lruvolatile-lru队列 Stream 慎用激进淘汰,优先 MAXLEN + 业务删。
  • 备份:托管快照;自建则 RDB + AOF + 定期恢复演练。
  • HA:生产至少 Sentinel 或云托管多 AZ(§1.3)。

14.7 部署与环境隔离

建议
环境 dev / staging / prod 独立实例 或独立 ACL + key 前缀 harness:{env}:
配置 REDIS_URLREDIS_MODEREDIS_POOL_MAX 走环境变量/Secret
发布 变更 Stream 消费者组名、key 结构时需 兼容期(双写或版本域)
压测 模拟 XREAD BLOCK 连接数 + Worker XREADGROUP 与线上一致

14.8 生产上线 Checklist(可打印)

# 完成
1 使用 ConnectionPool + 进程单例(§1.2、§14.1)
2 阻塞 tail / 消费者 独立小池
3 TLSrediss:// 或 SSLConnection,校验证书)
4 ACL 分 API / Worker / 只读;禁用危险命令
5 Key 符合 harness:{env}:... + 租户 + TTL(§14.2)
6 Cluster 使用 Hash Tag ;无跨 slot 多 key XREAD
7 Stream MAXLEN;Trace/事件不无限增长
8 redis_exporter + 告警(内存、连接、lag、pending)
9 密钥不进 Redis;不进仓库
10 幂等 + 锁 token;重试策略已测
11 故障演练:主从切换、Worker 全挂恢复 pending
12 与 PG/S3/Kafka 职责边界清晰(§16)

15. 本地快速体验(Docker)

bash 复制代码
# 仅开发;无 TLS/ACL
docker run -d --name redis-harness -p 6379:6379 redis:7-alpine
pip install redis

# 可选:本地体验 ACL(redis.conf 挂载)
# docker run ... redis:7-alpine redis-server --aclfile /etc/redis/users.acl

生产配置请直接对照 §14 ,勿用裸 6379 暴露公网。


16. 竞品、可替代方案与组合协作(Agent / Harness 语境)

Redis 在智能体架构里通常只占 「热路径」一层 (毫秒级状态、协调、轻队列)。没有银弹:会话真相、长任务 DAG、审计海量日志、向量检索 往往要换专用系统或与 Redis 分工协作

下文 §16.0 为速查表(与对外分享用的浓缩版一致);§16.1--16.8 为展开说明、链接与架构图。

16.0 速查版:替代 vs 协作(浓缩)

按能力
能力 可替代 Redis 的 更推荐与 Redis 一起用
会话 / Checkpoint PostgreSQL、DynamoDB Redis 热 + PG 权威
任务队列 RabbitMQ、Kafka、NATS、SQS Redis 抢 Run;Kafka 审计归档
实时推前端 轮询 DB(差) Redis Stream → BFF → SSE
定时 / 长跑编排 Temporal、Celery、Inngest Redis 短延迟;Temporal 人审等跨天
分布式锁 etcd、Consul 短锁 Redis 即可
LLM 缓存 进程 LRU、GPTCache Redis 跨实例共享
语义 / RAG Qdrant、pgvector、Milvus Redis 热点 + 向量库全量
大日志 / Trace Kafka、ClickHouse Redis live tail + Kafka 冷存
文件产物 --- S3/MinIO(Redis 只存指针)
Redis 协议「平替」
类型 说明
Valkey、KeyDB、Dragonfly 兼容 Redis 客户端;偏性能 / 许可 / 成本
Memcached 仅 KV 缓存; Stream / 锁 / ZSET,不能完整替代 Harness 用法
云托管 ElastiCache / Tair 等 生产最常见
常见组合(按规模)
阶段
MVP Redis + PostgreSQL
成长 Redis Sentinel + S3 + OpenTelemetry
规模化 Redis Cluster + Kafka + Temporal + PG
重 RAG Redis + pgvector/Qdrant + GPTCache
Python 常一起出现的包
和 Redis 的关系
redis-py / redis.asyncio 直连
Celery / arq / rq / dramatiq Redis 或 RabbitMQ 作 broker
LangGraph checkpoint-redis / checkpoint-postgres 图状态;与自建 Checkpoint 可二选一
GPTCache 语义缓存,底层可接 Redis + 向量库
Socket.IO redis-adapter 多实例 WebSocket,用 Pub/Sub
一句话分工
  • Redis :毫秒级协调、Run 队列、XREAD 推流、限流锁。
  • PostgreSQL:真相、审批、计费。
  • Kafka:不可丢、可回放的海量事件。
  • Temporal:多步、等人、跨天的 Agent 工作流。
  • 对象存储:工具输出 / 日志正文。

不必「全押 Redis」,也不必「全不用 Redis」------多数是 Redis 扛热路径,其它系统扛持久与编排


16.1 按 Harness 能力对照:谁能替、谁该组合(展开)

Agent / Harness 能力 本文 Redis 用法 可替代(竞品) 更常组合(协作)
会话 / Checkpoint Hash + TTL PostgreSQL、SQLite(边缘)、DynamoDB Redis 热 + PG 冷 ;LangGraph PostgresSaver
限流 / Token 预算 INCR、ZSET API 网关(Kong/Envoy)、漏桶在边缘 Redis 全局限流 + 网关 per-IP
分布式锁 / 幂等 SET NX、Lua etcd、Consul、ZooKeeper、DynamoDB 锁 短锁 Redis;跨服务编排用 Temporal
轻量任务队列 List、XREADGROUP RabbitMQ 、NATS JetStream、SQS、Kafka Redis 抢单 + Kafka 审计;或 Celery broker
实时步进 / SSE Stream XREAD 无替代时需 HTTP 轮询 DB;Socket 服务 Redis → BFF → SSE;Socket.io Redis Adapter
广播(可丢) Pub/Sub NATS、Kafka compact topic 通知用 Pub/Sub;事实用 Stream/Kafka
定时 / 延迟 ZSET Temporal timer、Celery ETA、Inngest、CronJob+DB Redis 轻调度 + Temporal 长跑
长跑工作流 / DAG (仅点到) Temporal、Prefect、Airflow、Dagster Agent Run 编排首选 Temporal;Redis 作侧车
LLM 精确缓存 String 进程内 LRU、GPTCache、CDN Redis 跨实例共享
语义 / RAG 缓存 Redis Stack Vector Qdrant 、Milvus、Pinecone、pgvector Redis 热点 + 向量库全量
Trace / 审计海量 Stream( capped) Kafka、Pulsar、Loki、ClickHouse Redis 实时 tail + Kafka 持久
工具产物 / 日志正文 只存指针 S3、MinIO、GCS Redis 元数据 + 对象存储
可观测 INCR、HLL OpenTelemetry、Prometheus Redis 暂存 span 批写后端
多实例 Leader SET NX K8s Lease、etcd election 单集群扫 pending 用 Redis 即可

冷路径 审计/分析
温数据 秒~分钟
热路径 毫秒级
异步刷盘
事件归档
Redis / Valkey
PostgreSQL
对象存储 S3
Kafka / Pulsar
ClickHouse
Agent Harness


16.2 同类软件:Redis 的「平替」与分支

产品 关系 说明
Valkey Redis 协议兼容 fork 开源替代;客户端常直接当 Redis 用
KeyDB 多线程 Redis 兼容 更高单实例 QPS;运维生态小于 Redis
Dragonfly 现代多线程、兼容 RESP 大内存、高吞吐场景评估
Memcached 部分重叠 仅 KV 缓存, Stream/锁/复杂结构;适合纯 cache
KeyDB / Garnet(.NET) 微软开源兼容 .NET 栈可评估
云托管 ElastiCache / Azure Cache / 阿里云 Tair 托管 Redis 协议 Agent 生产最常见;Tair 有增强模块

选型提示 :要 Stream、分布式锁、Hash、ZSET 的 Agent Harness,不要 用 Memcached 单换 Redis;要合规/成本可看 Valkey 或云托管。


16.3 消息与队列:和 Redis Stream 怎么分工

软件 强项 弱项 / 注意 Agent 典型用法
Redis Stream 低延迟、和会话同集群、运维简单 持久与回放弱于 Kafka;Cluster 多 stream 受限 Run 队列、Trace tail、延迟 ZSET
Kafka 高吞吐、长期保留、回放 重、延迟高于 Redis 全链路 audit、tool 日志归档、离线评测
RabbitMQ AMQP、路由灵活、成熟 超高吞吐不如 Kafka Celery broker、异构系统集成
NATS JetStream 轻、低延迟 Pub/Sub + 持久 生态小于 Kafka 多 Agent 信令、边缘云指令
AWS SQS / GCP Pub/Sub 免运维 延迟与语义各异 Serverless Agent、事件入云

组合范式

  • RedisXREADGROUP 执行 Run(抢任务)+ XREAD 推 SSE。
  • Kafka :同一事件 异步双写(fire-and-forget)做审计与 BI。
  • 不要 用 Kafka 替代 Redis 做 分布式锁毫秒 session

16.4 工作流与定时:长跑 Agent 的「编排层」

软件 / 包 替代 Redis 的哪块 说明
Temporal ZSET 调度、复杂重试、多步 DAG 长跑 Coding Agent、人审等待、版本化 Workflow
Celery + Redis/RabbitMQ List/Stream 队列 Python 生态熟;子任务多时常够用
Inngest / Trigger.dev 事件驱动 + 延迟 SaaS 友好,少运维
Airflow / Prefect 批处理定时 适合离线评测流水线,非在线 Agent 热路径
APScheduler + DB 简单 Cron 单进程调度,多副本需 Leader

Redis 仍适合:「30 秒后重试工具」会话 TTL ;Temporal 适合:「等人审批 3 天再继续」


16.5 Python / Node 常用包(与 Redis 协作)

作用 与 Redis 关系
redis(redis-py) 官方客户端 本文主示例
redis.asyncio 异步池 FastAPI / asyncio Agent
celery 分布式任务 broker=redis://amqp://
arq 轻量 async 队列 基于 Redis,比 Celery 简
rq Redis Queue 极简后台任务
dramatiq 任务队列 broker:Redis / RabbitMQ
LangGraph langgraph-checkpoint-redis Agent 图状态 与自建 Hash checkpoint 二选一
LangGraph langgraph-checkpoint-postgres 图状态持久 更可审计;Redis 做 cache
GPTCache 语义缓存框架 底层可接 Redis / 向量库
Socket.IO redis-adapter 多节点 WebSocket Pub/Sub 同步房间;与 Stream tail 互补
BullMQ(Node) 队列 Redis 原生;Node Agent 栈

16.6 状态与记忆:不要只用 Redis

存储 适合存什么 与 Redis
PostgreSQL / SQLite Run 元数据、审批记录、计费 Redis 缓存 + PG 权威;崩溃可重建
DynamoDB / Cosmos Serverless 会话 无运维;成本高延迟略高
向量库(Qdrant、pgvector) Embedding、语义缓存 Redis 存 query hash → 指向 vector id
图数据库 实体关系(少见) 与 Agent 记忆论文相关,非默认栈

LangChain / LangGraph 常见写法 :checkpoint 落 Postgres ;工具结果指针落 S3Redis 只管 run 锁、限流、live tail。


16.7 推荐组合(按团队规模)

阶段 组合 说明
MVP Redis 单机 + ConnectionPool + PG 队列、会话、SSE 一条龙
成长 Redis Sentinel + S3 + OTel 高可用;产物不进 Redis
规模化 Redis Cluster + Kafka + Temporal + PG 热冷分离;编排与人审交给 Temporal
重 RAG Redis + pgvector/Qdrant + GPTCache 语义缓存分层
Serverless Upstash Redis + SQS + DynamoDB 按量付费;注意 XREAD BLOCK 超时计费

16.8 何时不必上 Redis

情况 可考虑
单进程 Demo 内存 dict + 本地 SQLite
强一致事务编排 直接 Temporal + PG,Redis 只作 cache
仅批处理、无在线 SSE Kafka + DB,无需 Stream tail
团队零运维 托管 Upstash / ElastiCache + 托管 PG

23. 延伸阅读

主题 链接
Redis 命令与数据结构 redis.io/docs
redis-py 文档 redis.readthedocs.io
Valkey(Redis 兼容) valkey.io
Temporal(长跑工作流) docs.temporal.io
Agent Harness 综述(本仓库) csdn-综述-AI-Agent-Harness与Harness-Engineering.md
长跑 Agent 结构化状态 Anthropic · Effective harnesses for long-running agents

小结 :Redis 贯穿 前后端 · 端云 · 队列 · 流式 · 定时 · 发布消费 等场景;§14 落地 连接池、TLS、ACL、Key 规范、监控告警与上线 Checklist§16 说明与 Kafka/Temporal/PG 等分工。先查 §0b ,生产上线前过一遍 §14.8 ;延伸链接见 §23

相关推荐
dinglu1030DL2 小时前
CSS如何实现背景颜色的棋盘格分布_利用repeating-gradient
jvm·数据库·python
2303_821287382 小时前
Golang reflect反射怎么用_Golang反射教程【通俗】
jvm·数据库·python
Mike117.2 小时前
GBase 8c 里 search_path 没管住,SQL 可能跑到另一个对象上
数据库·sql·postgresql
升鲜宝供应链及收银系统源代码服务2 小时前
升鲜宝云商品库功能设计与数据库表结构详细文档(一)---升鲜宝生鲜配送供应链管理系统源代码服务
数据库·生鲜配送源代码·供应链源代码·生鲜供应链源代码·升鲜宝供应链管理系统源代码·b2b客户订货源代码
2301_783848652 小时前
如何用 IDBKeyRange 范围匹配检索特定区间的本地数据
jvm·数据库·python
解决问题no解决代码问题2 小时前
【无标题】
数据库
倒流时光三十年2 小时前
PostgreSQL 中的 NULL 陷阱:从一次排除过滤说起
java·数据库·postgresql
xkxnq2 小时前
第七阶段:企业级项目实战核心能力(118天)Vue项目缓存策略:接口缓存(内存+本地)+ 组件缓存+路由缓存组合方案
vue.js·spring·缓存
weixin_444012932 小时前
SQL处理大规模分组聚合的内存限制_调整服务器配置.txt
jvm·数据库·python