# 简述

我们的不少合作伙伴曾大量、深度使用过 lotus 的封装组件,也不乏自行定制的经历。

使用过程中,他们遇到不少痛点,也总结出许多经验。我们将这些思考汇总起来,尝试提供一套与 lotus 封装组件思路不完全相同、以简洁、效率和灵活为最优先考虑,但同样具备竞争力的集群方案,供广大的 SP (Storage Provider,存储供应商)选择。

# 设计思路

Damocles 主要包含 damocles-managerdamocles-worker 两个组件,其功能大致与 lotus-minerlotus-seal-worker 的组合相当。

Damocles 着眼于实现一套满足:

  • 面向大量同质化计算设备,降低硬件需求类型、实例数量和部署复杂度
  • 尽可能针对不同环节、不同类型的异常,安全地完成“自愈”,减少人工介入、提供生产效率
  • 依托云服务、外包服务及其他可共享的基础设施,降低门槛,削减不必要的成本

等目标的高效封装集群方案。

当然,明确且针对性强的设计意图可能导致这套方案并不普适于所有用户。例如,如果使用者已经拥有大量针对封装的不同阶段单独配置、形成搭配的设备,那么 Damocles 方案可能并不能有效地提升。具体情形,可以在阅读完后续文档后,由用户自行判断。

# lotus 封装组件的模式

在介绍 Damocles 的具体设计思路之前,我们有必要回顾一下 louts 相关组件的工作模式。

lotus 的封装组件是一套标准的中心化调度集群:

  • lotus-miner 是中心调度服务,对任务进行分配、管理;
  • lotus-seal-worker 是封装执行体,负责完成分配到其之上的具体任务,并反馈给 lotus-miner
  • lotus-miner 作为发起方,与 lotus-seal-worker 之间通过 RPC 交互。

这套设计优点十分明显:

  • lotus-seal-worker 是几乎无状态的工作进程,理论上很方便进行横向扩容;
  • lotus-miner 可以灵活地将任务在 lotus-worker 之间进行调度。

但是这套机制也存在另一面:

  1. lotus-miner 作为调度核心:

    • 承担着繁重的、不同类型的逻辑,如任务调度和管理,与链进行交互等
    • 保留着需要同时兼容本地模式和远程模式的历史负担

    导致内部逻辑(状态机)复杂、异常处理难以细化。

  2. lotus-seal-worker 被完全设计为无状态的模式。

继而产生了一些问题,例如:

  1. 由于各个阶段之间对硬件资源的要求迥异,为了保证资源的合理分配,通常会将 lotus-seal-worker 设置成每个实例支持一个阶段任务的模式,在同一机器上部署多个不同的实例,通过操作系统级别的资源控制(cgroup、docker、numa 等)进行隔离。

    这导致在大规模集群中, lotus-seal-worker 数量众多,编排和管理都较为复杂。

  2. 任何中心化任务调度机制都很有可能需要考虑计算和存储资源的匹配、亲和性等。

    我们说 lotus-seal-worker 几乎无状态,但是事实上,扇区封装过程中存在着 临时文件 这样一种特殊的“上下文”。对于这类“上下文”,lotus-miner 长期以来简单地对其进行整体搬移。这导致了巨大的网络带宽开销,以及一系列随之产生的网络、存储异常的可能性。

    直到 PR 7453 (opens new window) 提出“存储分组”概念之后,亲和性问题才初步解决,但这一方案给 worker 的编排又增加了额外的复杂度。

  3. 大规模集群中存在着大量可能导致任务异常的因素,包括但不限于:计算错误、存储失效、网络抖动。而 lotus-miner 内部缺乏有效感知异常根因的手段,使得其难以根据具体原因选择不同的异常恢复机制,只能统一粗粒度地处理或等待人工介入。

# Damocles 的设计思路

基于我们的目标和过去的经验,Damocles 在设计之初就确立了几点思路:

  • 简化逻辑
  • 优化流程
  • 隔离异常
  • 降低部署复杂度
  • 灵活替换功能组件

下面我们会具体进行阐述。

# 简化逻辑

我们重新规划了上下游组件之间的责任划分:

  • damocles-manager 负责:
    • 扇区封装过程中与链进行交互的部分,如 TicketSeed 的获取,消息的独立或聚合提交等
    • 扇区封装任务的状态记录,如当前阶段、异常及原因等
    • 已完成扇区的管理和维持,如响应 WindowPoST
  • damocles-worker 负责:
    • 管理各个阶段计算资源的分配
    • 使用预先规划好的本地存储空间完成扇区封装的完整过程

通过这样的划分,使得 damocles-manager 只需被动地接收必须传递的信息,不必实现一套复杂的感知和调度系统;同时 damocles-worker 直接管理各类本地异常,便于执行不同的处理策略。

这样,两者的业务逻辑都不会频繁出现分支,相对便于理解及修改。

# 优化流程

lotus 的封装流程中,最大的问题就是上文提到的,对封装过程中的临时文件的处理。此外还存在一些不尽如人意的点,例如:

  • 订单的等待逻辑可能会使计算资源闲置
  • 存储资源和计算资源的强制一对一匹配
  • 部分阶段,内部多个环节对资源的需求度不一致
  • 将与链的交互和扇区封装计算捆绑,带来的存储空间释放不及时等

等。这些都会到封装的整体效率产生影响。

# 隔离异常

一方面,主要的异常处理被移动到了 damocles-worker 一层,且在与 damocles-manager 交互的接口设计中,就尽早考虑了区分网络异常和逻辑异常,从而使得 damocles-worker 比较容易感知到异常的具体原因。

另一方面,我们将封装过程中可能产生的错误类型归为四个级别,并设定了不同的处理方式:

  • temp(orary),临时: 这个级别的错误通常明确属于临时性的,或者我们知道重试不会带来负面影响的。 对于这类错误,worker 会自动尝试重试(以 recover_interval 为间隔,最多 max_retries 次)。 当重试次数超过设定的上限时,会自动升级到 perm 级别。 RPC 错误是比较典型的临时错误类型。

  • perm(anent),持续: 这个级别的错误通常出现在 sealing 的过程中,无法简单判断是否可以安全重试,或一旦修复会有较大的收益(如不必重新完成 pre commit phase1)。 这类错误会阻塞 worker 线程,直到人工介入完成处理。

  • crit(tical),严重: 严重级别的错误在各个方面都与持续级别的错误比较相似。 比较显著的区别是,严重级别的错误通常明确来自运行的环境而非 sealing 的过程。 如可甄别的本地文件系统异常、本地持久化数据库异常等都归入此类。 严重级别的错误同样也会阻塞 worker 线程直到人工介入。

  • abort,终止: 遇到这个级别的错误,worker 会直接放弃当前的 sealing 进度,尝试重新开始一个新的流程。

这使得 damocles-worker 能够在更多不需要人工介入的异常场景下自行恢复,同时也给予将来用户定制异常处理策略提供了空间。

# 降低部署复杂度

damocles-worker 降低部署复杂度的努力主要体现在三个方面:

  • 降低实例数
  • 减少差异化的配置
  • 降低硬件失效,尤其是存储失效影响的范围

damocles-worker 是以一台设备一个实例的部署方式为目标设计的,同时内部融入了精简但足够的资源控制和隔离方式。使用者可以根据实际情况决定资源的分配策略以期达到最高的使用效率。同时,扇区粒度的任务隔离也可以做到确保部分硬件的失效不会蔓延影响到其他扇区任务。

damocles-manager 被设计为一个实例可以为多个不同的 SP 服务,同时得益于 venus 系列链服务组件的设计,可以达成一些不太常见的运行模式:

  • SP 联合体
  • 使用云厂商设备的多算力集群协同
  • SP 共享算力设备下的动态调配

# 灵活替换功能组件

Damocles 内的大量功能组件都是按照先抽象接口,再具体实现的路径设计的。这样做可以确保我们仅对必要的部分进行约束,保留不同实现并存的可能性,从而能够更容易地提供对诸如:

  • 定制化或闭源的封装算法执行器

  • 共享的订单数据存储

  • 部分阶段外包服务

等功能的支持。

# 总结

我们并不将 Damocles 定义为一套全能的封装集群方案,我们希望它是在特定的场景下,满足对简洁、高效、灵活有需求的使用者的一种选择。

同时我们期待有更多的参与者为其引入更丰富的功能组件实现。

我们认为扩大“特定场景”的覆盖范围有助于扩大 Damocles 的受众,但也不会贸然允诺对于 Damocles 的全部功能需求,尤其是那些对其自身特质有影响的部分。