关于milvus:我们又重写了一个关键服务

3次阅读

共计 2981 个字符,预计需要花费 8 分钟才能阅读完成。

01

QueryCoord 组件介绍

QueryCoord 是 Milvus 中查问集群的核心调度节点,在用户将一个 Collection Load 到内存中时,QueryCoord 负责将该 Collection 的 Segment 调度到 QueryNode 集群中,以反对后续的查问。

QueryCoord 最外围的操作有 4 种:

  • Load:将资源加载到 QueryNode 中
  • Release:将资源从 QueryNode 开释
  • Handoff:应用新的 Segment C 替换旧的 Segment A,B
  • Balance:在 QueryNodes 之间挪动 Segment

此外,在 Milvus v2.1.0 咱们退出了内存多正本的性能,通过减少 Segment 在内存中的正本数量来晋升性能,进步可用性。

02

为什么必须重构

在 Milvus v2.1.x 中,咱们遇到了很多与 QueryCoord 相干的问题,其中不少问题无奈在现有的设计上彻底解决,也有不少问题因为此前的设计耦合,在修复时很容易引入其它问题。
此前,有两类问题是最为常见,也最为辣手的:

  1. QueryCoord 在某个操作之后长时间无响应
  2. QueryCoord 有时无奈通过重启复原服务

咱们在 Milvus v2.1.x 的开发过程中破费了大量的工夫去解决无响应相干的问题。这个问题并不只是代码的问题,从基本来说是 QueryCoord 试图提供的接口语义过于弱小,以至于根本无法简略实现咱们冀望的语义。

根本原因

此前的 QueryCoord,试图为每一个接口都提供最终实现的语义,只有申请达到 QueryCoord,即便后续呈现谬误,QueryCoord 也会试图最终将这些申请实现。

此外,QueryCoord 会记录每个 Segment 所处的节点地位,并将这一信息长久化。当产生 Balance 之类的操作时,依据 RPC 是否胜利来决定是否要批改记录的地位信息。

咱们很快意识到这一做法是无奈做到精确的,因为 RPC 会有 false failure,QueryCoord 被动跟踪资源地位,并以 RPC 后果作为资源地位变动的根据,是必定会产生资源透露的。

为了达成这种最终实现的语义,QueryCoord 抉择实现了一个齐全串行的 scheduler,将每一种申请视作一类 task,能够认为这个 task scheduler 就是一个 WAL applier,而申请就是 WAL 中的 record,QueryCoord 会将所有 task 长久化,并在重启时重放这些 task。

这一套机制像由若干小齿轮连接起来的精细器件,容错率是很低的:

  • 因为齐全串行执行,当某一个申请无奈执行,或执行慢时,后续的所有申请都会被阻塞
  • 因为长久化了所有的 task,一个无奈实现的申请会继续阻塞整个零碎,即便重启也无奈复原
  • 一个有副作用的申请失败时(如 Load),无奈保障接口的原子性

以 Load 申请为例,如果在 Load 了局部 Segment 后,申请无奈继续执行。

在这一套机制下就无奈做出正确的解决。如果多次重试,则会让后续申请的阻塞更长时间;如果间接抛弃申请,则可能无奈开释曾经 Load 胜利的 Segment。

03

重构思路

QueryCoord 的实质是一个资源调度核心,咱们须要把 Segment 和 Channel 调度到 QueryNode 集群中来反对查问。

同时也要很好的反对内存多正本,在正本数量与预设值不同时能进行相应的高低线操作。新的 QueryCoord 设计参考了 Placement Driver 等业内成熟零碎,咱们有一些根本的出发点:

  • QueryCoord 必须感知实在的资源散布状况
  • 资源高低线操作是否实现不能以 RPC 是否胜利作为判断条件
  • 启动时依赖的长久化信息须要尽量少
  • 须要一个组件一直依据散布对资源进行高低线调整

从这些点登程,重构后的 QueryCoord 作出了上面的调整:

心跳机制

依赖 RPC 的胜利与否去判断资源的高低线是否胜利是不可行的,RPC 存在 false failure,因而可能会导致资源泄露。

集群中资源的散布状况,最实在的起源就是节点自身报告本人持有哪些资源,因而必须退出一套心跳机制,在 QueryCoord 与 QueryNode 之间同步资源散布状况。

在退出心跳机制后,QueryCoord 不再去长久化资源的散布信息,而是在启动时询问所有的 QueryNode 来复原出散布信息,这样能够防止脏数据对重启带来影响。

同时,依赖心跳汇报的资源散布信息来判断资源的高低线是否实现,是最精确的,在 Balance 这个操作中,咱们须要对一个 Segment 进行先上后下,以保障 Balance 过程中不会影响服务的可用性,通过心跳机制,咱们能够精确地判断上线操作是否真的曾经实现,来决定是否进行下线操作。

摈弃最终实现语义

从零碎本身的角度来看,最终实现语义是很难保障的,这一性质要求长久化所有申请,并在重启后进行重放。一些申请并不能保障总是可执行的,例如一个加载 Segment 的申请,可能在重启后这个 Segment 曾经被 Compact,从而无奈加载。

从用户的角度来看,当服务不可用时,用户通常冀望能够通过重启去清理掉一些脏数据,让服务可能从新工作。摈弃最终实现的语义之后,QueryCoord 只对接口提供原子性保障,不再长久化申请,防止复原时被一些无奈实现的申请阻塞。

在做出下面的批改后,咱们也不再须要一个全局串行的 scheduler,而是将申请的并发粒度升高到了 Collection 级别,并且须要进入 scheduler 的申请只有 Load/Release 两类。能够极大进步 QueryCoord 的响应速度。

资源散布查看

在一个分布式的环境中,任何网络申请都有失败的可能。一个对一批资源进行解决的申请甚至可能是局部胜利的,咱们心愿零碎具备更高的容错率,在任何状况下都有可能将资源正本数量调整到预设值的能力。

因而在新的 QueryCoord 中,咱们退出了若干的资源查看器(Checker):

  • Balance Checker:查看集群的负载状况,并作出适当的资源调整,均衡集群负载
  • Channel Checker:查看集群中各个 Channel 的散布状况,保障 Channel 的正本数量不多不少
  • Segment Checker:查看集群中各个 Segment 的散布状况,保障 Segment 的正本数量不多不少

在 Release 的状况下,查看器可能保障即便在 Release 实现存在 Bug 的状况下,也能够把泄露的资源开释掉。

04

资源调度零碎的设计感悟

得益于 Milvus 的架构设计,在这次重构中咱们简直重写了整个 QueryCoord,但仍然可能无缝的替换掉原来的 QueryCoord。在新的 QueryCoord 上线后,Milvus 零碎:

  • Query 集群更加强壮,稳固
  • 彻底解决 Load/Release 等申请无响应的问题
  • 保障了 Query 相干服务在重启后可能复原
  • QueryCoord 更加容易保护,排查问题更加容易

而我集体也从这次重构中播种良多,在资源调度零碎的设计上,与代码实现上取得了许多感悟:

  • 散布信息作为调度的输出,必须是实在的,也就是各节点上报的信息汇总
  • 简略,清晰的语义比看上去弱小的简单语义更弱小,简略粗犷往往意味着高容错
  • 原子性是一个十分无力的性质,零碎在各类操作有了原子性保障后会更容易保护,在并发的状况下,为了保障原子性有时须要很小心

在 QueryCoord 的后续演进中,咱们会持续强化 QueryCoord 的可用性与 Query 集群的性能。

正文完
 0