订单表过亿后,你把压力切成八份,以为容量终于线性增长。顶流场次开票时,其中一份却独自燃烧——均匀的哈希能搬数据,搬不走一个业务热点本身。
创业第 2 年 8 个月,「秒抢」累计用户约 180 万。公司 130 人,后端 25 人。订单表突破一亿行后,慢查询连续霸榜;团队按 show_id 切成 8 个分片,常规查询终于恢复。两周后,顶流场次开票,11:00 整,大盘上七根柱子安静,一根直冲顶部。
总容量没有用完,坏的是分布。8 个分片平均每个约 10,000 QPS;show_id=8842 一项就带来 49,600 QPS,并被分片键稳定送到 shard-03。其余七台机器有余量,却无法替它处理这个 key。
林工摇头:「别先加第九台。取模扩容会让大部分订单搬家,8842 搬到新家后还是一个 key。」你需要先把热点从通用分片隔离出来,再决定扩容拓扑;否则在线迁移会与开票流量争 I/O,却不改变 62% 请求汇聚到一处的事实。
A的问题是迁移成本和问题不对症。 取模从 8 变 9 会让约 8/9 的 key 改变目标,开票中搬动大部分订单会消耗网络与 I/O;热点 key 即使落到新节点,62% 的流量仍集中在一个分片。
B会让事情更糟。 七个分片本来就有余量,扩大它们的池没有收益;shard-03 已 100% CPU,更大连接池只会放入更多并发争用,让排队从应用搬到数据库。
C该先做的就是它:先把热点从共享分片摘出来。 独立通道把 show_id=8842 的爆炸半径与普通订单隔离,入口配额保护其容量;若业务允许,再用用户或订单尾号分成子桶。七个健康分片不必陪热点一起迁移。
D该做,但不是现在做。 索引能降低单次查询成本,值得复核;可眼前负载偏斜接近 5 倍,发布索引还要与在线写争资源。先隔离热点,再在安全窗口验证查询计划。
分库分表把一张大表的行映射到多个物理分片。分片键决定每一行去哪,也决定每一种查询能否直接定位。若业务流量在 key 上极不均匀,哈希函数再均匀,也只能把这个热点完整地放到某一台机器。
代入现场:总负载 80,000 QPS,show_id=8842 占 62%,因此承载它的分片至少收到 80,000 × 62% = 49,600 QPS。8 个分片平均是 80,000 ÷ 8 = 10,000 QPS,最热/平均为 49,600 ÷ 10,000 = 4.96,超过 3,明确进入热点区。
扩容还要付迁移账单。直接取模用 hash(key) % N 定位,N 改变时多数余数都会改变;一致性哈希只让新节点接管环上的一段 key 空间。对 N→N+1:
4 台扩到 5 台,取模约迁移 80%,一致性哈希约迁移 20%。一致性哈希降低的是拓扑变化带来的搬家量,并不自动保证每台负载平整。物理节点在环上位置有限,区间可能大小不一;因此要给每台配置许多虚拟节点,把大区间切碎后再分散。
分片键的账单比算法更长。按 user_id,用户订单历史是单分片查询,但一个热门场次会散到多个分片;按 show_id,场次售票统计和库存邻近,顶流场次却会压在单片。跨另一个维度的查询则要靠二级索引、映射表或 scatter-gather,读放大也随之出现。
| 选择 | 局部性收益 | 热点风险 | 补救手段 |
|---|---|---|---|
| user_id | 用户订单列表与售后查询单片完成 | 大客户或异常用户可能偏热,通常较易打散 | 热点用户独立桶、订单号路由映射 |
| show_id | 场次统计、同场订单处理集中 | 顶流场次天然形成业务热点 | show_id + bucket、热点场次独立通道 |
| 复合或映射 | 可按主访问路径定制分布 | 路由、扩容与查询逻辑更复杂 | 目录服务、二级索引、异步聚合 |
热点分片是第 5 章缓存击穿的另一副面孔:许多并发围绕同一个 key 汇聚,只是这次被击穿的不是缓存回源,而是分片容量。第 12 章会为顶流场次设计独立通道,把它从普通订单漏斗里摘出来。
看到 ▁▁▁█▁▁,先停止看集群平均。按分片、key、租户和访问模式下钻,算出最热/平均;若只是扩容而不改变热点 key 的映射方式,新机器很可能只接走冷数据。
用固定的 10,000 个 key 对比取模与一致性哈希:先看扩容迁移量,再看虚拟节点如何修平负载,最后用单个热点 key 击穿两种方案。
这些简化是故意的:模拟器的目的是隔离一个因果关系,不是预测生产数值。
实验清单里的每条现象都由 tools/simN-*-model-test.js 的断言保证,
但保证的是这个模型里的现象。
分片键要服务主访问路径。user_id 让用户订单天然聚在一起,show_id 让场次订单天然聚在一起;复合分桶则用更多路由复杂度换热点打散。三个方案都能落地,但每个都会把另一类查询变贵。
A · user_id:用户订单历史、退款与售后通常单片完成,用户分布相对容易均匀。 账单:按场次统计、核销和批量处理要跨片聚合,需二级索引或异步数仓;大客户仍可能形成局部热点。适合以用户查询和售后为主的订单系统。
B · show_id:同场订单局部性好,场次统计与批处理简单,和场次级业务天然邻近。 账单:顶流场次把读写压到单片,用户订单列表要跨片查询;热门程度越偏斜,容量越难预测。适合场次均匀、场次级处理远多于用户查询的系统。
C · show_id + bucket:保留场次维度的可定位性,同时把一个顶流场次拆到多个子分片。 账单:同场统计要聚合多个 bucket,桶数扩缩、路由表和事务边界更复杂;冷场次也可能被过度拆分。适合热点场次占比高、可接受聚合成本的秒杀通道。
「秒抢」的选择:普通订单主表采用 A,优先保障用户订单、退款和售后;顶流开票走 C 的独立热点通道,而不是让普通表都为极端场次付复杂度。理由是用户查询是长期主路径,热点场次则应被明确隔离;第 12 章会把这条独立通道放进完整秒杀架构。
回放 11:00 的正确剧本——先隔离正在燃烧的 key,再处理查询成本,扩容迁移留到流量稳定之后:
隔离热点。入口识别 show_id=8842,切到独立限流队列与临时热点库;shard-03 到达量从 49,600 降到 18,200 QPS,普通订单错误率开始回落。
场次内打散。新订单按 user_id 尾号拆成 8 个 bucket,并保持订单号到 bucket 的路由映射;热点通道最热/平均从 4.96 降到 1.42。
恢复普通分片。限制迁移与后台统计,给 shard-03 排空慢查询;其 CPU 从 100% 回到 58%,订单 P99 从 6.4s 降到 390ms。
计划性扩容。流量回落后评估一致性哈希与虚拟节点,用影子路由核对 4→5 的迁移范围;扩容不再与开票请求争同一条 I/O 通道。
六个分片的负载形状呈 ▁▁▁█▁▁,集群平均正常,错误集中在一个业务 key。最可能是?
A。单柱独高且绑定特定 key,是 No.10 的形状。D 很诱人,因为入口突发也会抬高负载,但均匀突发通常让多片一起上升;题中只有一片燃烧,证据指向分布偏斜。
从 4 台扩到 5 台,取模与一致性哈希的迁移比例量级分别约是多少?
B。取模约 4/5=80%,一致性哈希约 1/5=20%。A 是诱人的错误项,它把“新增一台占五分之一”直接套给取模,却忽略了模数改变会重排多数余数。
总负载 90,000 QPS,某 key 占 40%。它所在分片至少承担多少 QPS?
C。90,000 × 0.4 = 36,000。D 很诱人,因为把 40% 看成了 40,000,但百分比必须乘总量;B 则像是把总量平均分成四份,忽略题目给出的 key 占比。
订单按 user_id 分片后,哪类查询最可能需要额外的跨片聚合或二级索引?
B。同一场次的用户分散在多个 user_id 分片上,场次统计需要跨片或额外索引。A 是最诱人的错误项,但它正是 user_id 分片的局部性收益:一个用户的订单可直接定位到单片。