KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

08 · 各量级的方案选型:什么量级上什么,什么量级别瞎上 — keel 龙骨

这一章回答:知道自己在第几档之后,具体该上什么方案——以及更重要的,哪些方案在这个量级属于"瞎上"。

这一章回答:知道自己在第几档之后,具体该上什么方案——以及更重要的,哪些方案在这个量级属于"瞎上"。

上一章给了量级的心智模型和换算方法。这一章把它变成决策。前六章讲的每一个机制(异步、限流、熔断、分布式锁、消息队列、最终一致)都是好东西,但它们各自是为某一档的某个痛点设计的;把它们用在没遇到那个痛点的量级上,付出的是复杂度,换回的是零收益甚至负收益。

一、问题现场:一场"每句话都对"的架构评审

先别往下读:这四项技术每一个都"很专业",为什么组合起来是负收益?

因为这套系统是千级 QPS、痛点在同步阻塞和慢 SQL——而 Kafka 解决的是百万级吞吐与回放、微服务解决的是团队协作与独立发布、Redis Cluster 解决的是单实例容量与高可用、分库分表解决的是单库写入与数据量上限。这四件事没有一件是它的痛点。 复杂度提前支付了,收益没有发生,还新增了网络跳数与运维面——所以 P99 反而变差。

选型的第一性问题不是"这个技术好不好",而是"我的痛点是不是它解决的那一个"。

二、第一性原则:先定位瓶颈,再选方案

选型的顺序必须是单向的,不能跳步:

① 算在途并发(07 章的 L = λ × W)
      ↓
② 定档:百 / 千 / 万 / 十万
      ↓
③ 找这一档的主要瓶颈(是池?是长任务?是连接?是单机上限?)
      ↓
④ 选针对该瓶颈的方案
      ↓
⑤ 压测验证:盯在途并发、队列长度、P99

最常见的错误是从 ④ 直接开始——听说某个方案"更高级",或者"大厂都在用",就跳过 ①②③。判断一个提议是否靠谱,只问一句:它替代了我自己哪段容易出错的代码、解决了我哪个实测到的瓶颈?答不上来,它就是多余的复杂度。

三、四档方案地图

下面每一档都按同一格式给:典型场景 → 瓶颈在哪 → 该上什么 → 不该瞎上什么 → 一个类比。

档位 1|千级以内(几百 ~ 2000 在途):普通短任务

档位 2|千级 ~ 万级,含长任务(AI 服务典型)

这一档最需要的一个判断:长任务还分两种,处理方式完全不同。

  • IO 型长任务(等模型返回、等第三方 API):协程 + 队列就够了,一个进程能挂成千上万个在等的任务。
  • CPU 型长任务(本地推理、向量化、压缩、大文件解析):协程救不了你。看实验 4——8 个 400ms 的 CPU 任务放进协程里跑,实测 3.20s(理想并行约 0.4s),因为它们完全串行;换成进程池(8 workers)是 2.01s(这段还含进程启动开销,Linux 下用 fork 会比这个好看得多)。

结论:CPU 型长任务必须走进程池或独立服务,加协程数只会让事件循环被卡死。

档位 3|万级(2 千 ~ 2 万在途):多类任务混合

实验 5 就是为这一档做的:短任务(10 个/秒,每个 50ms)稳定运行,t=2s 时涌入 30 个长任务(每个 2s)。对比"共用一个池"与"长/短分开":

                      长任务涌入前              长任务涌入后
共享池 (W=8)          P50   70.8ms            P50 4027.8ms
                      P99   78.4ms            P99 5996.0ms
隔离池 (长W=6/短W=2)   P50   70.8ms            P50   70.3ms
                      P99   78.5ms            P99   78.7ms

请务必看清这个对比的前提:两种方案的总容量都是 8(共享 8,隔离是 6+2)。没有多加任何一个并发位,只是把它们分开,短任务的 P99 就从 5996ms 变成 78.7ms——差了 76 倍。

这就是"队头阻塞"(head-of-line blocking):在共享池里,一个 2s 的长任务会把一个 50ms 的短任务堵在后面;而你没有任何理由让它们排同一条队。这一档的选型核心不是"再加多少容量",而是切分。

档位 4|十万级(> 10 万):先分清是哪一种十万

这是最容易误判的一档,因为"十万"有两种完全不同的成因:

成因 典型场景 真正的瓶颈 该走的路
极高 QPS 的短请求 大促、抢票、热点信息流 单机吞吐上限、热点数据、DB 写 无状态水平扩展 + LB;分片/分区;多级缓存;全局限流与配额;热点 key 打散
海量长连接 IM、推送、IoT、实时协作 连接维持(fd、内存、心跳),不是计算 连接网关独立部署;心跳与空闲连接治理;连接层与计算层分开扩容

四、"不该瞎上"清单(反模式)

这一节可以单独当评审 checklist 用。左列是常见的"看起来很专业"的提议,右列是它真正该出现的时机。

反模式 常见于 真实代价 什么时候才真的该上
千级 QPS 上 Kafka "异步化"被当成年终 KPI 运维成本、语义复杂度、调试成本 需要分区并行/回放/百万级吞吐时
为解决阻塞而上微服务 一处慢导致全站慢 网络跳数、分布式调试、发布复杂度 团队边界与独立发布成为主要矛盾时
长任务场景加大连接池 "连接不够所以慢" 占更多资源,DB 侧更差 扩容前先确认瓶颈真的是连接数
所有任务共用一个池 图省事 队头阻塞(实验 5:P99 差 76 倍) 从不该——除非任务同质且都很短
能单实例解决的并发也上分布式锁 "分布式"听起来更安全 锁服务成为新故障点、死锁难查 真的跨实例竞争同一份数据时(04 章)
还不知道瓶颈就扩实例 压力一来先加机器 钱花了,排队依旧(07 章现场) 已定位到容量不足且已削峰之后
万级之前就分库分表 "提前规划" 事务与查询复杂度爆炸 单库写/数据量真的到上限时
用"大家都这么用"代替痛点论证 架构评审 复杂度先付、收益后不到 从不——本课程的一贯纪律

一句话记住这张表:复杂度也是一种成本,而且它到期必付。

五、决策流程(一张图)

flowchart TD
    A["① 先算在途并发 L = λ × W"] --> B{"② L 在哪一档?"}
    B -->|"百级"| C["先查慢 SQL 与缺失超时<br/>不要动架构"]
    B -->|"千级"| D{"是长任务吗?"}
    B -->|"万级"| E{"多类任务混跑?"}
    B -->|"十万级"| F{"成因是哪一种?"}
    D -->|"否:普通短任务"| D1["异步化 + 连接池校准<br/>+ 超时/索引/缓存"]
    D -->|"是:AI / 导出类"| D2["长任务入队 + 独立 worker<br/>+ 削峰 + 准入限流<br/>CPU 型另走进程池"]
    E -->|"否"| E1["按长任务方案处理"]
    E -->|"是"| E2["按类型隔离池/队列<br/>+ 优先级 + 分别限流"]
    F -->|"极高 QPS 短请求"| F1["无状态水平扩展 + LB<br/>+ 分片 + 多级缓存 + 全局限流"]
    F -->|"海量长连接"| F2["独立连接网关 + 心跳治理<br/>+ 连接层与计算层分开扩容"]
    C --> G["⑤ 压测验证:盯在途并发、队列长度、P99"]
    D1 --> G
    D2 --> G
    E1 --> G
    E2 --> G
    F1 --> G
    F2 --> G
    G --> H{"瓶颈是否转移?"}
    H -->|"是"| B
    H -->|"否"| I["收工:写下这轮的瓶颈与方案依据"]

注意最后那个回环:解决了一个瓶颈,压力会转移到下一个组件,所以要回到 ② 重新定档。容量工作不是一次性项目,是一个循环——这也是为什么"盯指标"比"选方案"更重要。

六、动手:可观察结果

产出 判断标准
一份选型论证表 对你系统里每一个中间件写两行:它解决的痛点是什么、拿掉它会怎样。写不出前者的,列入待删清单
一次隔离前后对比 复现实验 5(共享池 vs 隔离池),给出你自己系统的 P99 对比数字
一份反模式自查 用第四节的清单逐条过一遍,至少找出一条"我们现在就有、但其实不该上"的项
一次定档复算 假设流量翻 10 倍,重算在途并发与定档,写下"下一个瓶颈会转移到哪"

完成标志:你能对任何一个新技术提议说出"它解决的是第几档的什么痛点,我们现在不在这个档",而不是"这个我没用过/这个很火"。

七、故障注入

注入方式 观察什么 期望的正确行为
在共享池里投一批长任务(复现实验 5) 短任务 P99 是否被拖到秒级 若已隔离:短任务 P99 基本不变;未隔离:应复现数十倍劣化
把队列的消费者全部停掉,只留生产 队列长度与接口延迟 有水位上限与准入限流:到顶后拒绝新任务;无保护:队列无限增长、内存告警
给 CPU 型长任务只加协程数不加进程 事件循环延迟、其它接口 P99 应该没有改善(实验 4:CPU 任务在协程里是串行的)
把限流阈值设成"所有类型共用" 长任务与短任务的通过率 短任务被长任务挤掉,或长任务被短任务饿死——说明阈值必须分类

八、生产边界(替身 → 替换点)

教学替身 真实替换点 注意
asyncio.sleep 模拟长任务 真实模型调用 / 真实批处理 真实任务还有额度限制、失败重试、部分成功语义
Semaphore 模拟并发池 worker 并发度 / 队列消费者数 真实环境是"实例数 × 每实例并发度",还要算上 LB 与超时
单机进程池 独立计算服务 / 专用推理服务 跨实例后需要分布式任务状态与全局配额
本机实测数字 你的目标环境实测 本机是 Windows 开发机;Linux 的 fork、线程栈、调度都不同

九、自测题

  1. 一个千级 QPS、痛点在同步阻塞和慢 SQL 的系统,上 Kafka 为什么是负收益?正确的第一步是什么?
  2. 长任务和普通任务在"该上什么"上最大的分歧点是什么?为什么加大连接池对长任务无效?
  3. 实验 5 里两种方案的总容量都是 8,为什么短任务 P99 差了 76 倍?这个结论在你的系统里对应哪个改动?
  4. CPU 型长任务为什么加协程数没用?你该换成什么?
  5. "十万并发"有两种成因,分别是什么?它们各自的第一瓶颈有什么不同?
  6. 你的系统里有没有某个中间件,说不出"它解决了哪个实测到的瓶颈"?如果有,它为什么还在?

现在能解释什么

读完这一章,你应该能:

进入 keel 阅读