KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
00 · 三套部署形态:先决定该上哪一套 — keel 龙骨
在动手配任何参数之前,先把三套形态解决的问题分清楚。它们经常被当成"由小到大的三个阶段",但其实是三个不同维度:读写分离解决读压力,哨兵解决主库单点,集群解决单机容量上限。选错的代价不是配置白写,而是架构返工。
在动手配任何参数之前,先把三套形态解决的问题分清楚。它们经常被当成"由小到大的三个阶段",但其实是三个不同维度:读写分离解决读压力,哨兵解决主库单点,集群解决单机容量上限。选错的代价不是配置白写,而是架构返工。
一、现场:一次「因为读压力大所以上集群」的返工
某内容站点的 Redis 监控显示:内存 12G / 32G、QPS 3 万(其中 90% 是读)、CPU 60%。结论是"快到顶了,上集群"。搭完 3 主 3 从之后出了三件事:
- 批量取缓存的
MGET大面积报CROSSSLOT Keys in request don't hash to the same slot; - ARQ 的任务脚本一次要碰 4 个 key,同样报错,任务全部堆积;
- 内存问题其实不存在——12G 里 9G 是可以过期淘汰的缓存,真正需要"容量"的数据只有 2G。
真正的瓶颈是读 QPS,正确解法是「一主两从 + 读写分离」,工作量是搭集群的十分之一,而且不需要改一行业务代码。
这一章的结论先摆在这里:形态的选型依据是瓶颈的性质,不是"数据量大了就上集群"。
二、概念边界:三套形态各自解决什么、不解决什么
| 形态 | 解决什么 | 不解决什么 | 数据份数 |
|---|---|---|---|
| 主从复制 | 读扩展(把读分摊到副本)、数据冗余 | 主库挂了谁来接(没有自动切换) | 每个副本一份全量 |
| 主从 + 哨兵 | 主库单点:自动判定、自动提升、通知客户端 | 容量(所有节点仍是全量数据)、写入安全 | 每个副本一份全量 |
| 集群 Cluster | 单机容量与写吞吐瓶颈:把数据分片到多组节点 | 跨槽多键操作、跨槽事务;也不解决单 key 热点 | 每个分片一份,分片之间互补 |
三条容易搞混的边界:
- 复制是地基,不是方案。哨兵和集群都建立在复制之上:哨兵负责在复制组里换主,集群的每个分片内部本身就是一组复制。
- 集群自带高可用,不需要再叠一层哨兵。集群节点之间用集群总线互相探活,分片主节点挂了由它的副本顶上(第 05 章展开)。
- 读写分离在三套形态里都能做。集群的副本默认也能读,只是要客户端显式路由。所以"要不要读写分离"和"上不上集群"是两个独立问题。
三、选型决策图
flowchart TD
A["先定位瓶颈<br/>(看监控,不要猜)"] --> B{"数据量超过单机<br/>可用内存?"}
B -- 是 --> C{"能否接受跨槽<br/>多键 / 事务 / Lua 受限?"}
C -- 能 --> D["上集群 Cluster<br/>每个分片配 1~2 个副本"]
C -- 不能 --> E["按角色拆实例<br/>(缓存一套、队列一套、锁一套)<br/>或改造 key 使用 hash tag"]
B -- 否 --> F{"瓶颈是读 QPS<br/>还是写 QPS?"}
F -- 读 QPS --> G["一主多从 + 读写分离<br/>从库数量按读量线性加"]
F -- 写 QPS --> H{"写压力来自<br/>单 key 热点还是总量?"}
H -- 总量 --> D
H -- 单 key 热点 --> I["分片解决不了<br/>改 key 设计 / 本地缓存 / 限流"]
G --> J{"主库挂了要<br/>自动恢复吗?"}
J -- 要 --> K["加哨兵:至少 3 个<br/>跨机器部署"]
J -- 不要 --> L["人工切换<br/>(有值守的内部系统可接受)"]
D --> M["分片自带切换<br/>仍需监控槽覆盖与迁移状态"]
图上有两个最容易跳过的判定:
- 「能否接受跨槽多键受限」:这不是理论问题。第 05 章会给出
MGET、Lua、事务在集群下的真实报错,判断之前先看那些报错你能不能接受。 - 「单 key 热点」:一个 key 永远落在一个槽、一台机器上,加多少分片都没用。这是分片解决不了的问题,图上单独列出。
四、一次完整运行:本课程的实验环境
后面每章的实验都跑在同一套本机环境上,你可以照着搭一个:
Redis 5.0.14.1(Windows 构建,tporadowski 移植版)
6379 主库
6380 从库 A
6381 从库 B
26379 哨兵(第 03 章)
7000 / 7001 / 7002 集群三主(第 05、06 章)
客户端:redis-py 8.1.0
启动三个数据节点(配置只给端口和工作目录,其余用默认值,便于观察默认行为):
redis-server --port 6379 --save "" --appendonly no --dir ./n6379
redis-server --port 6380 --save "" --appendonly no --dir ./n6380
redis-server --port 6381 --save "" --appendonly no --dir ./n6381
建立复制关系只需一条命令(第 01 章会解释它触发了什么):
redis-cli -p 6380 REPLICAOF 127.0.0.1 6379
redis-cli -p 6381 REPLICAOF 127.0.0.1 6379
两个版本坑,先记下来(后面章节还会遇到):
术语:Redis 5 的日志和
INFO字段仍写slave(slave0、slave_repl_offset、connected_slaves),Redis 7 起改为replica。命令用新写法REPLICAOF(SLAVEOF仍可用),输出保留实测原样。协议:redis-py 5+ 默认尝试 RESP3 握手(
HELLO 3),而 Redis 5 没有HELLO这条命令,连接会直接失败:redis.exceptions.ResponseError: unknown command `HELLO`, with args beginning with: `3`本课所有 Python 示例因此显式传
protocol=2(顺带一提:哨兵客户端还要单独给"连哨兵那条连接"传一次,见第 03 章)。你连 Redis 6+ 时这两处都可以删掉。
五、主动破坏:把选型结论反过来验一次
拿你自己系统里的 Redis 用法,问三个问题。任何一问答不出来,就说明当前的形态是"碰巧能用",不是"选对的":
- 这个 key 所在的实例现在挂了,会发生什么? 如果答案是"不知道"或"全部重建",说明它的可用性要求从来没被满足过(通常是队列类 key)。
- 如果现在把读全部切到从库,有没有哪个查询会因为读到旧值而出错? 有,就必须给它标记"读主"(第 02 章)。
- 这台机器上所有 key 加起来的内存,能不能塞进一台机器? 如果能,集群带来的跨槽限制就是纯成本。
六、生产环境怎样替换
| 教学替身 | 生产替换 | 要点 |
|---|---|---|
| 本机三个端口 | 跨机器的主从,至少跨可用区 | 同机多实例只用来验证机制,不提供任何真实可用性 |
| 默认配置 | 显式写 replicaof、requirepass / masterauth(Redis 6+ 用 ACL) |
只用命令配的复制关系,重启即失效 |
| 无监控 | 采集复制延迟、连接状态、切换事件(第 07 章清单) | 没有监控,"高可用"只是自我感觉 |
| Windows 构建 | Linux 官方 Redis | 本课用的 Windows 构建跑哨兵会崩(内存分配缺陷),不能当部署目标 |
七、练习与验收
练习 1(形态归类):列出你系统的 Redis key,按「能不能丢 / 能不能读旧值 / 会不会跨 key 操作」三列分类,指出每一类适合放在哪种形态的实例上。
练习 2(反例论证):找一个"上了集群但其实只需要主从"或"只配了主从但主库挂了没人管"的真实案例(自己的或公开的),写出它的瓶颈性质与正确形态。
验收点:不看资料,画出第 03 节的决策图,并解释为什么"单 key 热点"不能被分片解决。
现在能解释什么:三套形态各自解决哪个维度的问题、不解决什么;选型依据是瓶颈性质而不是数据量;同一实例混装不同敏感度的数据会带来什么取舍。
下一步:第 01 章把最底下那块砖——主从复制——拆开看它是怎么跑起来的。