Skip to content

Redis 排查 Cheatsheet(Pub/Sub + Streams · 协作模式线上排障) ​

线上协作出问题时的一页速查。先查 Key 约定 → 再敲命令 → 按症状对照。 命令都标注 🔒只读安全 / ⚠️有副作用。线上务必先读后写。 配套教程:docs/协作模式/2026-07-04-redis-pubsub-入门教程.md、docs/协作模式/2026-07-04-redis-pubsub-进阶-Streams对比与项目读码.md。


0. 先连上 Redis ​

bash
# 方式一(推荐):用项目现成 CLI,自动读 config.REDIS_URL
cd backend
make redis-channels                 # 列出正在协作的 channel
make redis-sub ARGS=<workspace_id>  # 实时监听某 workspace 消息流
make redis-presence ARGS=<workspace_id>  # 查在线成员 + 光标

# 方式二:裸 redis-cli(自己填 REDIS_URL)
redis-cli -u "$REDIS_URL"
# 或带库索引
redis-cli -u "$REDIS_URL" -n 0

💡 排查 Pub/Sub 第一原则:Pub/Sub 消息发完即焚、KEYS 查不到,必须实时监听才能看到。看到症状时第一反应是 make redis-sub 挂上听。


1. 本项目 Redis Key / Channel 约定(排查入口) ​

排查第一步永远是知道数据存在哪个 key。协作模式涉及的全部约定:

1.1 实时协同(Pub/Sub + Hash) ​

用途类型Key / ChannelTTL来源
画布实时广播频道Pub/Sub channelworkspace:realtime:{workspace_id}无(发完即焚)redis_broadcaster.py:_CHANNEL_PREFIX
在线成员/光标/选区Hashworkspace:presence:{workspace_id}120sworkspace_presence_service.py:_PRESENCE_KEY_PREFIX

1.2 聊天续接(Streams + 辅助) ​

用途类型KeyTTL来源
流式事件日志(续接核心)Streamchat:stream:{message_id}活跃期内resume/redis_keys.py:STREAM_PATTERN
会话当前活跃消息 IDStringchat:active:conversation:{conversation_id}活跃期内ACTIVE_CONVERSATION_PATTERN
用户占用并发槽位ZSetchat:active:user:{user_id}活跃期内ACTIVE_USER_PATTERN
消息归属用户Stringchat:message:{message_id}:user活跃期内MESSAGE_USER_PATTERN
生成租约(谁在生成)Stringchat:generation:lease:{message_id}短租GENERATION_LEASE_PATTERN
终态收敛锁(防重复落库)Stringchat:generation:finalize-lock:{message_id}短锁GENERATION_FINALIZE_LOCK_PATTERN
暂停标记Stringchat:pause:{message_id}—PAUSE_PATTERN

1.3 其他 ​

用途类型Key来源
视频流活跃标记Stringvideo:active_stream:{thread_id}services/core/video_stream_guard.py
续接指标Stringchat:resume:metrics:{day}:{name}{label_suffix}resume/metrics.py

🔍 找 key 的通用招:不确定 key 全名时用 SCAN 模糊扫(见 §6),不要用 KEYS *(线上大库会阻塞)。


2. Pub/Sub 命令速查 ​

2.1 发布 / 订阅 ​

命令作用风险
SUBSCRIBE ch1 ch2 🔒订阅指定频道(进入订阅态,该连接只能收)—
UNSUBSCRIBE [ch] 🔒退订;不带参数=退订全部—
PSUBSCRIBE order.* 🔒P=Pattern,按通配符订阅(*/?/[abc])—
PUNSUBSCRIBE [pattern] 🔒退订模式—
PUBLISH ch msg ⚠️往频道发消息,返回送达订阅者数(0=没人听)副作用:触发订阅者逻辑

⚠️ 进了 SUBSCRIBE 态的 redis-cli 连接不能再敲别的命令,排查时务必单独开终端。

2.2 查台(排查最常用) ​

bash
# 🔒 列出当前有订阅者的协作 channel(即正在协作的 workspace)
PUBSUB CHANNELS workspace:realtime:*

# 🔒 查某 channel 有几个订阅者
PUBSUB NUMSUB workspace:realtime:<workspace_id>

# 🔒 查当前活跃的模式订阅
PUBSUB NUMSUB
PUBSUB CHANNELS

💡 PUBSUB CHANNELS 只列"当前有活跃订阅者"的频道。如果返回空,不代表频道从没存在,只代表此刻没人订阅——这是排查"消息没收到"的关键线索。


3. Streams 命令速查 ​

命令作用风险
XADD ch * field val ⚠️追加一条,* 自动生成 ID写入
XLEN ch 🔒流里有几条—
XRANGE ch - + 🔒倒带读全部(-最老 +最新)—
XRANGE ch <id> + 🔒从某 ID 之后读(排查断点)—
XREAD COUNT 50 STREAMS ch <id> 🔒从某 ID 之后读新消息;$=只读未来—
XREAD BLOCK 15000 STREAMS ch $ 🔒阻塞最多 15s 等新消息—
XINFO STREAM ch 🔒看流元信息(长度、首尾 ID)—
XINFO GROUPS ch 🔒看消费组—
XPENDING ch <group> 🔒看消费组内未 ACK 的消息(排查卡住)—
XACK ch <group> <id> ⚠️确认处理完改变消费状态
XTRIM ch MAXLEN 1000 ⚠️裁剪到最近 N 条丢弃旧消息
XDEL ch <id> ⚠️删除指定消息不可逆

项目实战:查某条 AI 消息的续接流 ​

bash
# 🔒 看这个 message_id 的流里有多少事件、首尾 ID
XINFO STREAM chat:stream:<message_id>

# 🔒 倒带看全部流式事件(token 序列),排查"续接内容对不对"
XRANGE chat:stream:<message_id> - +

# 🔒 从某个事件 ID 之后读(模拟前端断点续读)
XREAD COUNT 50 STREAMS chat:stream:<message_id> <entry-id>

# 🔒 看这条消息还活着吗(活跃消息 ID 登记)
GET chat:active:conversation:<conversation_id>
GET chat:generation:lease:<message_id>     # 生成租约还在=还在生成

4. 通用排查命令(读 key 本身) ​

命令作用风险
TYPE <key> 🔒先确认 key 类型(决定后续用哪条命令)—
TTL <key> 🔒剩余秒数(-1=永不过期,-2=不存在/已过期)—
EXISTS <key> 🔒是否存在—
SCAN 0 MATCH workspace:presence:* COUNT 100 🔒安全模糊扫(线上用这个)—
KEYS <pattern> ❌模糊扫(阻塞,线上大库禁用)阻塞
HGETALL <key> 🔒Hash 全量(presence 用)—
HGET <key> <field> 🔒Hash 单字段(presence 某用户)—
ZRANGE <key> 0 -1 🔒ZSet 全量(用户并发槽位用)—
DBSIZE 🔒当前库 key 数—
INFO clients 🔒客户端连接数(排查连接池)—
INFO memory 🔒内存占用(排查 Streams 堆积)—
CLIENT LIST 🔒列出连接(排查谁占着连接)—
SLOWLOG GET 10 🔒慢查询—

有副作用的命令(谨慎) ​

命令作用风险
EXPIRE <key> <sec> ⚠️给 key 设过期续期/提前过期
DEL <key> ⚠️删单个 key(必须带具体 key,禁止批量)不可逆
FLUSHDB / FLUSHALL ❌❌清库绝对禁止(违反仓库安全规则)

5. 症状驱动排障流程(实战核心) ​

症状 A:画布操作(移动产物/新增),其他成员看不到 ​

关键命令:

bash
# 1. 确认对方实例有没有订阅这个频道(在对方实例的 redis 上查全局)
PUBSUB NUMSUB workspace:realtime:<workspace_id>

# 2. 自己挂监听,操作画布看后端到底发没发
make redis-sub ARGS=<workspace_id>

# 3. 没消息 → 查后端日志(发布侧)
grep "workspace.output\|workspace.realtime" <日志>

症状 B:某人在线,但别人看不到他的光标/在线状态 ​

bash
# 1. 🔒 查 presence hash 里有没有他(field = user_id)
make redis-presence ARGS=<workspace_id>
# 或裸命令:
HGETALL workspace:presence:<workspace_id>

# 2. 🔒 查 hash 还剩多久过期(-2=已过期,说明他超过 120s 没动)
TTL workspace:presence:<workspace_id>

# 3. 🔒 查他单个人
HGET workspace:presence:<workspace_id> <user_id>

💡 presence 在用户真移动鼠标时才写入,120s 不动自动过期。如果某人看不到另一人光标:① 后者可能没动鼠标(hash 没写)② 后者 hash 已过期 ③ 前者重连时没收到 presence:state 快照(查 WS 初始同步)。

症状 C:聊天刷新后,AI 回答丢失/续接不上 ​

bash
# 1. 🔒 这条消息的 Stream 还在吗?
TYPE chat:stream:<message_id>          # 应返回 stream;none=已清理
XINFO STREAM chat:stream:<message_id>  # 长度、首尾 ID

# 2. 🔒 Stream 里到底有没有内容(倒带看 token 序列)
XRANGE chat:stream:<message_id> - +

# 3. 🔒 这条消息还"活跃"吗(续接的前提)
GET chat:active:conversation:<conversation_id>
GET chat:generation:lease:<message_id>   # 返回值=还在生成;nil=已结束

# 4. 🔒 用户占用的并发槽位(超限会拒续接)
ZRANGE chat:active:user:<user_id> 0 -1
现象可能原因
TYPE 返回 noneStream 已过期清理,续接窗口已过(只能重新生成)
XINFO 有数据但前端空白前端没带 message_id 续接,或带的 entry-id 错位
XRANGE 内容不全生成中途异常,查 lease_refresh_failed 指标 / finalize-lock
lease 已 nil 但 Stream 还在生成已结束,前端应走终态而非续接

症状 D:消息发布出去 PUBLISH 返回 0 / 没人收到 ​

检查命令
订阅者先于发布订阅了吗?Pub/Sub 发完即焚,发布时没人听=永久丢
频道名拼对了吗?PUBSUB CHANNELS workspace:realtime:* 核对
多实例下对方实例订阅了吗?PUBSUB NUMSUB 是全局的,返回的是所有实例订阅者总和
订阅连接池满了吗?INFO clients + 日志 broadcaster

症状 E:Redis 慢 / 连接池打满 / 内存涨 ​

bash
# 1. 🔒 连接数排查(项目 pubsub 独立池 64,publish 共享池 50)
INFO clients
CLIENT LIST | head         # 看谁占着连接(注意 pubsub 会长期占)

# 2. 🔒 内存排查(Streams 不裁剪会涨)
INFO memory
SCAN 0 MATCH chat:stream:* COUNT 100   # 有多少续接 Stream 没清

# 3. 🔒 慢查询
SLOWLOG GET 20

# 4. ⚠️ 单个异常 Stream 太大,按 message_id 精准清(禁止 FLUSH)
XLEN chat:stream:<message_id>
# 确认无误后再处理,优先让它自然过期(EXPIRE 短一点)而不是 DEL

6. 多实例排查重要提醒 ​

协作模式线上是多实例部署,排查时要分清"本实例"和"全局":

数据作用域说明
Pub/Sub channel 订阅者全局(跨实例)PUBSUB NUMSUB 返回所有实例订阅者总和
WebSocket 连接(ConnectionRegistry)本实例每个 python 进程一份内存表
presence hash全局(存 Redis)所有实例读写同一个 workspace:presence:*
Stream全局(存 Redis)按 message_id 全局唯一

💡 所以"画布消息跨实例不通"时:先确认 PUBSUB NUMSUB 全局有订阅者(排除 Redis 本身问题),再分别查两个实例的 _pubsub_loop 日志(可能是某实例订阅协程挂了/没重连)。


7. 红线(违反会出大事) ​

对应仓库 AGENTS.md 安全规则,Redis 排查时同样适用:

❌ 禁止✅ 正确做法
FLUSHDB / FLUSHALL永远不用;要清让 key 自然过期或精准 DEL <具体key>
KEYS *(线上大库)SCAN 0 MATCH ... COUNT 100 分页扫
无目标 DEL / 批量删只对具体 key 操作,删前先 TYPE/GET 确认
改/删 workspace:presence:* 想清在线态让它 120s 自然过期;非要清只能 DEL 单个 hash
在生产 PUBLISH 测试消息触发业务用专用测试 workspace,或只 SUBSCRIBE 监听不动手

8. 一页速查卡(打印备用) ​

┌──────────────────────── 协作模式 Redis 排查速查 ────────────────────────┐
│ 连接:  make redis-sub ARGS=<W>   make redis-presence ARGS=<W>          │
│        make redis-channels        redis-cli -u "$REDIS_URL"            │
│                                                                        │
│ KEY 约定:                                                              │
│   画布广播 channel : workspace:realtime:{W}     (发完即焚)              │
│   在线成员 hash    : workspace:presence:{W}     (TTL 120s)              │
│   续接 Stream      : chat:stream:{msg_id}       (XADD/XRANGE/XREAD)     │
│   生成租约         : chat:generation:lease:{msg_id}                    │
│                                                                        │
│ Pub/Sub 查台:    PUBSUB CHANNELS workspace:realtime:*                  │
│                  PUBSUB NUMSUB   workspace:realtime:{W}                │
│                                                                        │
│ Stream 倒带:     XRANGE chat:stream:{msg} - +                          │
│   续点读:         XREAD COUNT 50 STREAMS chat:stream:{msg} {id}        │
│   状态:           XINFO STREAM chat:stream:{msg}                       │
│                                                                        │
│ 安全:   只读用 SCAN 不用 KEYS;只删具体 key;禁 FLUSH;禁批量 DEL        │
│ 多实例: NUMSUB/presence/Stream 是全局;WS 连接表是本实例                 │
└────────────────────────────────────────────────────────────────────────┘

9. 相关文件索引 ​

文件职责
backend/scripts/redis_debug.py现成排查 CLI(sub/presence/channels)
backend/services/workspace_realtime/redis_broadcaster.pyPub/Sub 实现(channel 前缀)
backend/services/workspace_realtime/workspace_presence_service.pypresence hash 实现
backend/services/core/resume/redis_keys.py续接相关全部 key 约定
backend/services/core/resume/stream_service.pyStream(xadd/xread)实现
backend/core/redis.py全局 Redis 客户端单例(get_redis)