本节摘要:单机调优总有天花板,数据继续涨、机器继续坏,就得靠多台机器协同。本节讲 Qdrant 分布式架构的两根支柱——分片解决「一台装不下、算不动」,副本解决「一台挂了不能全停」。围绕这两点,展开多数派写入、故障转移、一致性级别与扩容再平衡,说明它们在节点宕机时如何自动兜底、把宕机降级为一次短暂抖动,最后给出分片数、副本数、节点数的规划思路,帮你在创建集群时就把冗余和伸缩留够。
阅读完本节,你应当能够:
第 4.1 节讲的是怎么让一台机器更快,但再快的单机也有两个绕不过去的天花板。第一个是容量:一亿条 1024 维向量加上 HNSW 索引,物理内存总有装不下的一天,磁盘也总有写满的一天。第二个是可靠性:只要数据全在一台机器上,这台机器的电源、硬盘、网卡任何一样出问题,整个服务就跟着停摆。
这两个天花板对应两个不同的词。容量和算力的天花板,靠可扩展性来解决——数据多了就多上机器,把负载摊开。可靠性的天花板,靠高可用性来解决——机器会坏是常态,系统要在「坏了一台」的前提下还能对外服务,数据还不能丢。这两个词常常被一起提起,但它们解决的是两件不同的事。
这里先厘清一个误区:加机器不等于都叫水平扩展。垂直扩展是把单机换成更大的机器,加 CPU、加内存、加盘,简单直接,但越往上越贵,而且始终有个物理上限,最要命的是它没解决可靠性——大机器也会宕。水平扩展是增加机器数量,让数据分散到多个节点上,每一台都不必是「超级计算机」,坏一台也不影响整体。Qdrant 走的是后一条路,这也是所有现代分布式数据库的主流选择。
水平扩展听上去很美,代价是复杂度。数据一旦摊到多台机器上,你就得回答一连串新问题:数据怎么切、切完之后查询怎么路由、某台机器上的数据挂了对不对、各副本之间谁说了算。这一整节,本质上都是在回答这些问题。
分片解决「数据怎么切」。一个集合在建的时候可以指定分片数量,Qdrant 按向量 ID 的哈希把点分散到各个分片,每个分片是一个独立的存储和索引单元,可以落在不同节点上。查询时,请求会被路由到所有分片并行搜索,再把各分片的 Top-K 结果汇总排序。于是「一台机器算不动的全量数据」变成了「每台机器只算自己那一份」,容量和吞吐都随节点数近乎线性地涨。
但分片单独拿出来有致命缺陷:它把数据切散了,却没有冗余。一个分片只存一份,它所在节点一宕,这份数据就查不到了。这就是副本的用武之地。给每个分片配多个副本、分布到不同节点,主副本接写入、从副本同步数据并分担读取,任何一台机器挂了,副本都能顶上。分片解决「摊得开」,副本解决「丢不掉」,两者必须配合,缺一不可。
这张图是写入与读取两条路径的简版。写入走主副本,主副本同步给从副本,拿到多数派确认后才对客户端返回成功;读取则可以落在任意一个健康副本上,由负载均衡分发。读和写拆开,正是分布式数据库提高吞吐、分散压力的常规做法。
副本带来冗余,也带来一个新问题:各副本之间的数据不一致怎么办。主副本刚写完,从副本还没同步,这时读从副本就会读到旧数据。要不要容忍这种「短暂不一致」,就是一致性级别要回答的问题。
一个绕不开的理论是 CAP:在网络分区存在的前提下,一致性、可用性、分区容忍性三者不可兼得。分区容忍性是分布式系统躲不掉的,所以实际选择只能在「强一致」和「高可用」之间偏一头。Qdrant 在向量检索这种读多写少、且对毫秒级延迟敏感的场景里,通常偏向可用性,采用最终一致性——没有新写入后,各副本最终会收敛到一致,但中间可能短暂读到旧值。对搜索结果来说,这种「短暂旧一点」几乎无感,却换来了更大的可用性和更低的延迟。
写入端的保障则靠多数派。一个分片有三副本时,主副本把写请求同步给另外两个,至少两个副本确认落盘才向客户端报成功。这样即便有一个副本掉线或数据落后,其余多数派仍持有最新数据,宕机后也不会丢已经确认过的写入。
| 一致性级别 | 写确认条件 | 读到的数据 | 延迟与可用性 | 适用场景 |
|---|---|---|---|---|
| 同步复制 | 全部副本确认 | 最新 | 延迟高、可用性低 | 元数据、强一致要求 |
| 多数派写入 | 过半副本确认 | 通常最新 | 折中 | 关键业务数据 |
| 异步复制 | 主副本确认即返回 | 可能旧 | 延迟低、可用性高 | 可容忍短暂不一致的搜索 |
⚠️ 常见坑:把「副本多」误解成「强一致」。副本数量多只能提升冗余度和读吞吐,不改变一致性级别。如果你要的是读到的永远最新,得在读写两端都配置强一致语义,光加副本没用。
💡 关键直觉:一致性是「花钱买保证」。强一致用更高的写入延迟和更低的可用性,换来「读到的必然是最新」;最终一致把这笔钱省下来,换来低延迟和高可用,代价是偶尔读到旧值。选哪个,看业务是否真的在乎那几毫秒窗口里的新旧差异。
机器会坏,所以高可用的核心是「坏了之后系统自己接住,不用人半夜爬起来」。这套自动反应机制靠三件事接力完成。
第一件是心跳检测。节点之间周期性互发心跳,超过阈值没回应就把对方标记为疑似故障。第二件是领导者选举。集群用共识协议(Qdrant 集群内部基于 Raft 的思路)维护一个领导者,负责协调集群状态和写入;领导者一旦失联,剩余健康节点会自动重新选举出新的领导者,过程对外几乎无感。第三件是副本晋升。某个分片的主副本所在节点宕了,集群会把一个数据最新的从副本提升为新主副本,继续接写入;同时在其他健康节点上补一个副本,把副本数恢复到设定值。
这套流程的目标很朴素:把「机器宕机」这件事,从「一次事故」降级为「一次短暂抖动」。用户感知到的可能只是几毫秒到几十毫秒的重试,而不是整个服务不可用。前提是副本数大于一,且副本分布得足够分散——两个副本放同一台机器上,等于白配。
水平扩展最理想的情况是「数据涨了,加台机器就行」。现实中往往没那么顺,因为数据已经在现有节点上形成了固定分布,新节点加入后,需要把一部分分片从老节点搬到新节点,这个动作叫再平衡。
再平衡的麻烦在于它要搬真数据。搬一个分片,等于把它的向量、索引、Payload 完整复制到新节点,期间要占网络带宽和磁盘 I/O,还要和新写入的数据保持同步。数据量越大,搬得越久,期间集群性能会打折扣。更棘手的是,搬的时候不能停服务,所以这是一个「边开车边换轮胎」的活。
这给了我们一个规划上的启示:分片数最好在创建集合时就留有余量,别到数据涨爆了才临时加分片。因为分片数一改,往往牵动全局的数据重分布。副本数相对灵活,增减副本只影响冗余度和读吞吐,代价小一些。节点数的变化则依赖运维侧的调度,把分片和副本均匀铺到新节点上,避免出现「某台机器数据特别多」的热点。
| 参数 | 影响 | 调整代价 | 规划建议 |
|---|---|---|---|
| 分片数 | 并行度、单分片大小 | 高,可能触发重分布 | 创建时按目标规模预留 |
| 副本数 | 冗余度、读吞吐、写延迟 | 中,需同步数据 | 按可用性要求取二到三 |
| 节点数 | 总容量、总吞吐 | 中,依赖调度均衡 | 按数据量和 QPS 线性估算 |

这张图把「分片与副本」落到一个三节点、三分片、两副本的具体例子上。每个分片都有一个主副本(绿色)和一个备份副本(米色),且主副本与备份副本分布在不同的节点上。当节点 B 宕机,它上面的 S1 主副本失联,节点 C 上的 S1 副本随即晋升为新主副本,承接读写,服务不中断。看懂这张图,就理解了「为什么副本必须跨节点分布」——两个副本放同一台机器上,机器一坏两个一起没。
规划集群没有万能公式,但有一套从需求反推的思路。先看数据量定节点容量:单节点能舒服承载的向量数,取决于内存能否容纳它分到的那份索引与工作集。再看 QPS 定节点数:压测出单节点的吞吐上限,用目标总 QPS 除以单节点吞吐,就是节点数的下限。最后看可用性定副本数:要求「坏一台不影响服务」,副本数至少取二;要求「坏一台也不丢已确认写入」,多数派机制要求三副本更稳。
分片数的选择更讲究前瞻性。分片太少了,后期数据涨了不够摊,改分片数要重分布;分片太多了,每个分片太小,协调和路由的开销占比上升。经验做法是让每个分片的数据量落在「单节点内存舒服承载」的区间里,同时给未来的数据增长留出余量。集群规模不大时,分片数取节点数的整数倍,能让分片和副本在节点间铺得更均匀。
⚠️ 常见坑:把副本数设成 1,以为「先用着,以后再加」。副本数为一等于没有冗余,任何一台节点宕机都会丢数据或停服务,而且后期补副本要全量同步一遍,代价不低。生产环境从第一天就该把副本数设到二以上。
💡 关键直觉:可用性和扩展性都是「提前花钱」的工程。分片数、副本数、节点数这三个数,创建时多花一点心思,能省掉后面成倍的数据迁移和半夜救火。规划的目标不是「当下刚好够」,而是「坏了能接、涨了能扩」。
下一节我们看监控与故障排查——把分片副本搭起来、参数调好之后,还得有一双眼睛盯住它们,否则故障发生时你连「坏在哪台机器」都不知道。