KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
07 · 生产落地:监控指标、故障手册与四类角色的形态选择 — keel 龙骨
最后一章把前六章收成能直接用的东西:该盯哪些指标、出问题先查什么、以及回到第 00 章那四类 Redis 角色(缓存 / 队列 / 锁 / 事件流),逐个说明它们该配哪种形态、代码上要注意什么。
最后一章把前六章收成能直接用的东西:该盯哪些指标、出问题先查什么、以及回到第 00 章那四类 Redis 角色(缓存 / 队列 / 锁 / 事件流),逐个说明它们该配哪种形态、代码上要注意什么。
一、现场:报警响了,但不知道该看哪
凌晨收到"接口 P99 变高"。打开监控:CPU 正常、内存平稳、连接数不高。没有方向。
这类工单的共性是:监控采的是"资源指标",而出问题的是"拓扑状态"。复制延迟、槽覆盖、切换事件这些拓扑指标没采,就只能靠猜。
二、监控清单:按形态分组
通用(三套形态都要)
| 指标 | 来源 | 关注点 |
|---|---|---|
used_memory / maxmemory |
INFO memory |
水位;配合 evicted_keys 看是否开始淘汰(命中率下降的前兆) |
instantaneous_ops_per_sec |
INFO stats |
吞吐基线,扩容判断依据 |
| 客户端侧 P99 延迟 | 客户端埋点 | 服务端命令耗时漏掉了网络与排队,必须客户端侧测 |
rejected_connections |
INFO stats |
连接数打满 |
| 慢日志 | SLOWLOG GET |
大 key、KEYS、大集合操作的第一现场;从库也要采 |
主从 / 哨兵
| 指标 | 来源 | 告警建议 |
|---|---|---|
master_link_status |
INFO replication |
不是 up 立即告警 |
connected_slaves |
同上 | 少于预期说明有副本掉线 |
master_repl_offset - slave_repl_offset |
同上(两侧相减) | 复制延迟的真实量,用字节差而不是 lag 秒数 |
repl_backlog_histlen / repl_backlog_size |
同上 | 接近 100% 说明即将触发全量重传 |
master_sync_in_progress |
同上(从库) | 为 1 时该从库正在加载 RDB,读服务质量下降 |
num-other-sentinels |
SENTINEL master <name> |
少于 哨兵总数-1 说明有哨兵失联 |
flags 含 s_down / o_down |
同上 | 出现即告警 |
+switch-master 事件 |
哨兵 pub/sub | 切换次数本身是核心指标:频繁切换说明网络或主库有问题 |
集群
| 指标 | 来源 | 告警建议 |
|---|---|---|
cluster_state |
CLUSTER INFO |
不是 ok 立即告警 |
cluster_slots_assigned / _ok / _pfail / _fail |
同上 | 必须是 16384 / 16384 / 0 / 0 |
cluster_known_nodes / cluster_size |
同上 | 少于预期说明有节点掉队 |
| 各节点的槽数与内存 | CLUSTER NODES |
分布不均是隐性问题:槽数不多但内存高 → 大 key 或热点 |
MOVED / ASK 计数 |
客户端埋点 | 稳定期大量 MOVED 说明路由表在抖 |
三、故障手册:症状 → 根因 → 第一步动作
| 症状 | 最可能的根因 | 第一步动作 |
|---|---|---|
| 从库读到旧值 | ① 复制延迟;② 从库被慢查询阻塞;③ 从库正在加载 RDB | 比 offset 差;查从库 SLOWLOG;查 master_sync_in_progress |
写报 READONLY |
连到了从库:切换后客户端没刷新,或配置写反 | 查该节点 role;强制客户端重新发现主库(第 03 章) |
| 复制反复全量重传 | backlog 太小,或从库长期落后 | 看 repl_backlog_histlen 是否打满;按第 01 章公式调大 |
| 主库 CPU 高但 QPS 不高 | 从库过多导致复制输出放大 / 大 key / 慢命令 | 查 SLOWLOG 与 connected_slaves |
| 切换后任务/写入丢失 | 异步复制的固有窗口 | 确认丢失范围;关键写入加 WAIT;队列类数据改独立实例(第 04 章) |
集群 cluster_state:fail |
有槽无人认领(主节点挂且无副本) | CLUSTER NODES 找 fail 的槽;补副本 |
用户看到 MOVED 报错 |
客户端不支持集群协议 | 换支持集群的客户端(第 05 章) |
报 CROSSSLOT |
多键 / Lua / 事务跨槽 | 加 hash tag,或拆成多次单键操作 |
| 迁移后部分 key 读不到 | 迁移中断,槽停在中间态 | CLUSTER SETSLOT ... STABLE 或 --cluster fix,再核对 key 归属 |
| 单个 key 访问极慢 | 热点 key(分片解决不了) | 本地缓存 / 拆分 key / 限流 |
排障路径收敛成一张图:
flowchart TD
A["Redis 相关报警"] --> B{"是连通性问题<br/>还是正确性问题?"}
B -- 连通性 --> C{"集群还是主从?"}
C -- 主从 --> C1["看 master_link_status<br/>与哨兵 flags"]
C -- 集群 --> C2["看 cluster_state<br/>与 cluster_slots_fail"]
C1 --> C3{"正在切换?"}
C3 -- 是 --> C4["等重试生效<br/>(第 03 章)"]
C3 -- 否 --> C5["查进程/网络/连接数"]
C2 --> C6{"槽有缺失?"}
C6 -- 是 --> C7["补副本或重新认领槽"]
C6 -- 否 --> C8["查节点存活与总线端口"]
B -- 正确性 --> D{"读到旧值<br/>还是读到缺失?"}
D -- 旧值 --> D1["比 offset 差<br/>确认该读是否必须走主"]
D -- 缺失 --> D2{"刚发生切换或迁移?"}
D2 -- 是 --> D3["异步复制丢数据(04章)<br/>或迁移中断(06章)"]
D2 -- 否 --> D4["查是否写到了别的节点<br/>(MOVED/CROSSSLOT 被吞)"]
B -- 慢 --> E["查 SLOWLOG + 客户端侧 P99<br/>区分服务端慢与网络慢"]
四、四类角色怎么选形态(本课的应用落地)
回到第 00 章那张表。核心建议先说:按角色拆实例,不要让"要安全的数据"替"要吞吐的数据"做妥协。
缓存(article:{1001}:detail)
- 形态:主从 + 读写分离最合适(读多写少、丢了可重建);量大了再考虑集群。
- 代码:可陈旧读走从库,写后读走主库(第 02 章的
allow_stale)。 - 集群下:批量
MGET需要 hash tag,否则拆成 pipeline 单取。 - 兜底:缓存不可用时必须能穿透到源(数据库),否则从库挂一次整站就挂。
队列(ARQ)
- 形态:独立的一套主从实例。副本策略按"能不能丢"决定:能容忍少量丢失就配副本;不能容忍就宁可不轻易切换,改用持久化与对账。
- 集群下的现实问题:ARQ 一次操作会碰多个 key(任务 Hash、结果 key、队列 Stream、延迟 ZSet),而集群要求同一操作——尤其是 Lua 脚本——涉及的所有 key 落在同一个槽。社区版 ARQ 没有官方的集群支持。
- 两条可行做法:① 用 hash tag 把相关 key 绑到同槽(要改 key 生成逻辑,等于改库,成本高);② 更现实的是让它跑在非集群实例上。这正是"按角色拆实例"的价值。
- 无论哪种形态:队列的正确性最终靠消费者 ACK + 可重放 + 对账,不靠复制(第 04 章)。
锁(lock:index:{1001})
- 单 key,三种形态下都能用,没有跨槽问题。
- 真正的风险是第 04 章那条:切换会让锁失效,这在集群里同样成立(副本提升也是异步复制)。
- 形态建议:锁放在切换频率低的实例上(例如不与缓存共用),并把临界区做成幂等。
事件流(Stream + 消费者组)
- Stream 本身是单 key,天然同槽;跨多个 stream 的
XREAD需要它们同槽(hash tag)。 - 断线重续依赖客户端保存的 last-id;切换后要能用 last-id 在新主上继续读,丢失的那一小段要有补偿(重放或兜底查询)。
- 与本站「实时推送与 SSE」课程第 04 章(断线重连与续播)互为补充。
五、客户端侧的落地清单
上任何形态之前,客户端这一层必须过关:
- 读写分流有显式开关(
allow_stale),而不是埋在配置里; - 写失败有重试,重试覆盖切换窗口(第 03 章
safe_write); - 从库失败能回退主库,且回退有打点;
- 连接池按角色分开(读池 / 写池),避免一个池打满影响另一路;
- 超时设置合理:连接超时 < 命令超时 < 业务超时,否则重试会叠加成雪崩;
- 多键与 Lua 已按 hash tag 改造(若上集群);
- 客户端库支持对应形态:哨兵客户端 / 集群客户端,并验证过切换与重定向行为。
六、上线检查清单
| 检查项 | 通过标准 |
|---|---|
| 复制关系写进配置文件 | 只用命令配的关系,重启后仍在(不是"重启就没了") |
| backlog 按写入速率调过 | 不是默认 1MB(写密集实例) |
| 哨兵数量与部署 | ≥3 个、奇数、跨机器、与数据节点分开 |
| 客户端重试演练 | 切换时持续写入,失败后重试成功 |
| 从库宕机演练 | 读仍成功、回退有打点、恢复后回到从库 |
| 集群槽覆盖 | --cluster check 通过,16384 全覆盖 |
| 集群副本 | 每个主节点至少 1 个副本,且与主不同机 |
| 监控与告警 | 第 02 节的拓扑指标已采且有阈值 |
| 对账机制 | 能回答"到底有没有丢数据" |
七、练习与验收
练习 1:按第 02 节的表,把你环境的实际取值填一遍,指出哪些指标目前没采集。
练习 2:从第 06 节的四类角色里挑一类,为它写一份"形态选择说明":为什么选这个形态、代码上要改什么、出问题怎么降级。
练习 3:按第 03 节的故障手册,给自己系统里最近的一次 Redis 故障写一份复盘:症状属于哪一类、当时的第一步动作对不对。
验收点:不看资料,说出三套形态各自的关键监控项;对"读到旧值""写报 READONLY""切换后丢数据""CROSSSLOT"四种症状,立刻给出第一步排查动作;说明四类角色各自的形态选择与理由。
全课收口:回到第 00 章的决策图。现在你应该能不看资料画出它,并对每条分支说出"为什么"——瓶颈是什么、这套形态解决什么、代价是什么、应用代码要改什么。三句话总结全课:
- 复制让数据多一份,但它是异步的,且不提供路由;
- 哨兵解决主库单点,不解决容量,也不保证写入安全;
- 集群解决容量,代价是跨槽能力受限——而真正的难点从来不是搭集群,是 key 设计。