RustFS 后台服务清点指南:On-Demand Migration 后台 Worker 的期望来源、状态表面与副作用审计
本文以 RustFS 仓库中的 docs/architecture/background-services-inventory.md 为骨架,围绕 On-Demand Migration(按需迁移,ODM)模块的后台 Worker 展开。它回答一个具体问题:当你要新增、移动或审计一个后台 Worker 时,如何在"一行记录"内说清楚它的期望来源(desired source)、当前状态输入(current-status inputs)、状态表面(status surface)和副作用(side effects)。读完本文,你将掌握 RustFS 后台服务清点清单的规则、ODM 三大后台服务的逐行剖析,以及如何用契约词汇表为其他服务补全清点条目。
清点清单的定位:它回答什么问题
background-services-inventory.md 的定位非常明确:它是按服务维护的行级清单,而 background-controller-contract.md 是定义词汇表与状态模型的契约页。二者分工如下:
- 契约页拥有词汇表(desired / current / status / reconcile / side effects)和状态模型,并给出各服务的耦合注意事项;
- 本清单页拥有每个服务的具体行数据——即契约词汇表所定义的那五个维度,针对每个被审计的服务各填一行。
文档强调了一个容易踩坑的边界:Scope 只覆盖"已被审计"的服务。目前清单只收录了 On-Demand Migration 的 Worker(对应 rustfs/backlog#2147);scanner、heal、lifecycle、replication、notification、capacity、config-reload 等服务目前只在契约页的 "Coupling Notes" 中描述,尚未进入逐行清点。
这里有一条非常重要的默认规则:"清单中没有该服务的行"只表示"尚未被清点",绝不等于"该服务没有副作用"。审计一个服务之前,你无法从"没有行"推断出"没有副作用"。
词汇表:五个维度的含义
契约页定义了清点行所依托的五个术语。理解它们,才能读懂清单中每一列:
| 术语 | 含义 | 边界 |
|---|---|---|
| Desired(期望) | 来自环境变量、持久化配置、模块开关、feature flag、bucket 配置或 admin 配置的静态意图 | 只读;收集期望状态时绝不规范化或改写配置 |
| Current(当前) | 观察到的本地运行时状态:configured、disabled、running、degraded、stopping、unknown | 只读;绝不通过会产生存储或网络副作用的探针去推断 |
| Status(状态) | 可机器检查的快照:计数器、worker 数、队列压力、上次周期、上次错误、取消来源、关闭句柄形态 | 无副作用;表面缺失时上报为 unknown,绝不猜测 |
| Reconcile(调和) | 对 desired、current、status 的比较,产出一个计划 | 已发布的计划只做报告;唯一允许的 worker 变更是 none |
| Side effects(副作用) | 写操作、删除、队列准入、目标激活、外部 I/O、指标上报、就绪发布、对等信号、配置重载扇出 | 任何 controller 接触服务之前必须先声明 |
状态模型方面,快照使用代码能证明的最窄状态:NotConfigured(无有效期望来源)、Disabled(存在来源但显式禁用)、Starting、Running(按现有运行时状态确实在运行,而非仅仅配置为启用)、Degraded、Stopping、Stopped、Unknown(无安全状态表面,优先于猜测)。
清点清单的硬性规则
清单页明确指出,每一行都必须遵守从契约页继承的规则:
- 状态收集是无副作用的:状态收集绝不启动、停止、缩放或唤醒某个 worker,绝不写入存储数据、对象元数据、目标状态、队列条目或持久化配置,绝不发布就绪或对等重载信号;
- 缺失表面上报为
unknown,而不是猜测; - 副作用在 controller 接触服务之前声明——先声明,后操作;
- 取消来源与关闭句柄形态,要和期望的 enabled/disabled 状态分开报告;
- 对同一快照重复调用
reconcile必须返回同一计划; - scanner、heal、lifecycle、replication 的状态不得隐藏其队列与准入耦合。
On-Demand Migration:三个后台服务逐行剖析
清单当前收录了 On-Demand Migration 的三个后台服务。ODM 的整体行为、配置与排障在 docs/operations/on-demand-migration.md 中描述,这里只做逐行的清点式记录。
Write-back pull pipeline(写回拉取管线)
这是 ODM 的核心 Worker:负责把源对象拉下来并写成本地对象。
| 维度 | 内容 |
|---|---|
| 期望来源 | 桶的 on-demand-migration.json 配置(enabled、policy.max_concurrent_pulls、pull_queue_capacity、multipart_part_size_bytes、bandwidth_limit_bytes_per_sec),叠加进程级开关 RUSTFS_ON_DEMAND_MIGRATION_ENABLED |
| 当前状态输入 | rustfs/src/on_demand_migration/sys.rs 中的每桶运行时状态:该桶的状态是否已安装、其 source client 是否构建成功、取消令牌(cancellation token)、队列深度、在途拉取许可(in-flight pull permits) |
| 状态表面 | GET /rustfs/admin/v3/on-demand-migration/{bucket}/status(返回 inflight_pulls、queue_depth、counters.pulled_*、counters.pull_failures_total)以及 rustfs_on_demand_migration_* 指标系列 |
| 副作用 | 对源发起 GET / HEAD / GetObjectTagging 流量;通过内部 put 路径执行本地对象写入,因此会消耗桶配额、套用桶默认 SSE、版本控制、Object Lock 默认保留,并触发 ObjectCreated 通知和出站复制调度 |
源码佐证:实现位于 rustfs/src/on_demand_migration/pull.rs,本地写入被委托给应用层的 OdmWriteBack trait 对象(实现位于 rustfs/src/app/object/on_demand_migration_put.rs),由二进制在启动时通过 OnDemandMigrationSys::set_write_back 注入。从源码结构看,pull.rs 定义了 PullQueue(有界队列,容量即 pull_queue_capacity)、dispatch(dispatcher 循环)、commit_inline(inline 提交)和 spawn_pump(泵任务)。几个硬编码常量直接对应文档中的行为描述:PULL_MAX_RETRIES = 3,PULL_RETRY_BASE_DELAYS = [1s, 4s, 16s],MAX_MULTIPART_PARTS = 10_000。
Backfill job(回填任务)
只做读穿(read-through)时,只有客户端实际读过的对象会被迁移。回填任务则遍历源桶的 listing,把其余对象全部通过写回管线拉取到本地。
| 维度 | 内容 |
|---|---|
| 期望来源 | 一次 admin start 请求加上桶配置;当配置的 updated_at 变化或配置被删除时,任务即失效 |
| 当前状态输入 | 桶元数据前缀下持久化的 checkpoint(.rustfs.sys/buckets/<bucket>/on-demand-migration-backfill.json)、其 state 字段、owner lease |
| 状态表面 | 桶状态端点中的 backfill 段,以及 rustfs_on_demand_migration_backfill_* 指标系列 |
| 副作用 | 对源发起 ListObjectsV2 分页;向写回管线做队列准入(因此继承写回管线的全部副作用);checkpoint 写入 |
源码佐证:实现在 rustfs/src/on_demand_migration/backfill.rs(对应 rustfs/backlog#2159)。checkpoint 相关的常量非常具体:BACKFILL_CHECKPOINT_FILE = "on-demand-migration-backfill.json"、BACKFILL_LEASE = 60s(每次保存续约)、BACKFILL_SAVE_INTERVAL = 10s、BACKFILL_SAVE_EVERY_KEYS = 1000、BACKFILL_LIST_PAGE_SIZE = 1000。checkpoint 通过 If-Match 比较并交换(CAS)写入,保证并发取消或接管不会被覆盖;continuation_token 只有在某页排队的拉取全部成功后才前移,失败后停留在失败页,崩溃恢复不会跳过失败的拉取(已存在的键会被跳过)。BackfillState 枚举包含 pending、running、paused、cancelled、completed、completed_with_failures、failed 七种状态。另外,失败键只以 xxh3 哈希记录(key_hash),对象键只出现在 trace 级日志中。
Backfill recovery loop(回填恢复循环)
负责在节点重启或 owner lease 过期后接管未完成的回填任务。
| 维度 | 内容 |
|---|---|
| 期望来源 | 持久化 checkpoints 中所有 state = running 的集合;在每个节点上运行 |
| 当前状态输入 | checkpoint owner lease 是否过期 |
| 状态表面 | 接管通过同一 backfill status 段报告;一次接管会发出一个 warn 级 lease 事件 |
| 副作用 | 认领 lease 并恢复回填任务(继承其副作用);扫描 checkpoints 本身是只读的 |
源码佐证:注册点位于 rustfs/src/startup_background.rs 的 init_on_demand_migration_backfill_runtime,它构建 SysBackfillContexts 与 BackfillRunner::for_local_node,并通过 install_global_backfill_runner 发布进程级 runner;恢复循环由 spawn_backfill_recovery_loop 挂到存储层的后台取消令牌上。backfill.rs 中的 run_backfill_recovery_loop 以 BACKFILL_RECOVERY_INTERVAL = 60s 为周期扫描,若有接管被推迟(锁忙或无状态)则缩短到 5s 重试;takeover_due 判定接管条件:lease 已过期、owner 为本节点(重启后必然已死),或完全无 owner。开始与接管通过命名空间锁 odm-backfill/<bucket> 在集群范围内串行化。
关键设计:pull pipeline 没有自己的独立循环
清单文档特别强调了一个容易被误解的设计事实:拉取管线本身没有独立的循环。这与回填恢复循环不同——恢复循环是一个真正的周期性循环,而拉取管线是事件驱动的:
- 桶的队列 dispatcher 是惰性启动的:在第一个后台拉取到来时才启动,当桶状态被重建或移除时取消;
- 每个 inline pull 在一个超出请求生命周期的任务中提交:客户端断开连接不会截断已存储的对象。这是因为
commit_inline在独立任务中持续从源 draining 并写入本地,客户端中断不影响写回; - Worker 不会重新读取开关或配置:无论是
RUSTFS_ON_DEMAND_MIGRATION_ENABLED开关还是桶配置,都不是由 Worker 周期性地去轮询的。唯一的期望状态路径是 bucket-metadata publish hook 重建状态——配置一变,publish hook 就触发状态重建,旧的 dispatcher 被取消,新的状态携带新配置生效。
源码佐证:sys.rs 中 OnDemandMigrationSys 的生命周期跟随桶元数据缓存,通过 BUCKET_CONFIG_PUBLISH_HOOK 注册的发布钩子驱动;钩子在每条缓存安装路径(初始加载、admin 更新、对等重载、刷新循环、惰性加载)上都会触发。变更检测按配置值(PartialEq)比较而非只比较 updated_at,且桶的 incarnation 也参与比较——即使配置完全相同,重建桶也必须取消旧工作。由于客户端构建是异步的(TLS 材料可能从磁盘读取),publish 同步移除旧状态并 spawn apply 执行安装,用每调用一代号(generation number)保证慢的旧安装永远不会覆盖新安装。
状态表面与观测:从端点字段到指标系列
虽然清单行只概括了状态表面,但落到实际观测时,GET /rustfs/admin/v3/on-demand-migration/{bucket}/status 的返回结构(实现于 rustfs/src/admin/handlers/on_demand_migration.rs)可以拆解为与清单各维度一一对应:
- 配置视图:
configured(是否有配置)、enabled(配置自身的开关)、module_enabled(进程级开关)、provider、endpoint_host、updated_at; - 运行时快照:
breaker.state、counters(requests_total、pulled_bytes_total、pulled_objects_total、pull_failures_total、source_latency)、last_source_error(class与at)、inflight_pulls、queue_depth、served_by_source_ratio、backfill。
注意两个恒为 null 的字段并非 bug:breaker.opened_at(运行时持有的是单调时钟而不是墙钟)和 served_by_source_ratio(没有按桶划分的 GET 总数可供求比值,诚实的 null 好过编造的 0)。当桶在本节点没有活动状态时(模块关闭或尚无任何读取),运行时字段为 null,而 provider、endpoint_host 仍反映已保存的配置。
指标侧,所有序列都按桶打上 rustfs_on_demand_migration_* 前缀,且只在配置了源的桶上出现:requests_total(按 bucket、op、outcome 打标)、pulled_bytes_total、pulled_objects_total(按 path 区分 inline / background / backfill)、pull_failures_total(按 reason 区分 source_not_found、queue_full、etag_mismatch、quota 等)、inflight_pulls、queue_depth、source_latency_seconds_distribution、breaker_state(0=closed、1=half-open、2=open)。回填任务另有 rustfs_on_demand_migration_backfill_* 系列。
如何为其他后台服务补全清点行
清单文档的核心使用场景是"新增、移动或审计后台 Worker"。以现有三行为模板,为其他服务补行的步骤如下(每一步都可以用契约页的 Coupling Notes 验证):
- 确定 Desired source:该服务的期望来自哪里?环境变量、持久化配置、模块开关、feature flag、bucket 配置还是 admin 请求?注意配置的哪个字段变化会使期望失效(如 backfill 的
updated_at); - 确定 Current-status inputs:本节点上哪些运行时状态能证明"它现在在做什么"?状态是否安装、客户端是否构建、令牌是否有效、队列多深、在途多少;
- 定义 Status surface:状态端点哪个段、哪些指标系列承载可机器检查的快照?记住规则——无副作用、缺失报
unknown; - 声明 Side effects:在 controller 接触它之前,把所有写操作、队列准入、外部 I/O、通知与复制调度列全。
契约页的 Coupling Notes 已经给出了待清点服务的关键事实来源,例如:scanner 由 init_data_scanner(rustfs/src/startup_lifecycle.rs)启动并隐含触发 heal 入队;heal/AHM 拥有自己的取消令牌(rustfs/src/startup_background.rs 的 create_ahm_services_cancel_token);replication 有"关通道停 worker"与"取消令牌停 worker"两套关闭契约;lifecycle 过期/迁移/过期分片清理由 ECStore::init 绑定运行时令牌;动态配置重载是 admin 触发的扇出而非循环。这些描述正是未来为这些服务写清点行时"期望来源与副作用"两列的原料。
后续阅读
- background-controller-contract.md:清点清单所依赖的词汇表、状态模型与耦合注意事项;
- docs/operations/on-demand-migration.md:ODM 的操作行为、配置参考、请求语义、保护机制与排障;
- rustfs/src/on_demand_migration/pull.rs、rustfs/src/on_demand_migration/backfill.rs、rustfs/src/on_demand_migration/sys.rs:三大后台服务的源码实现;
- rustfs/src/startup_background.rs:回填恢复循环的注册点;
- rustfs/src/admin/handlers/on_demand_migration.rs:admin 状态端点的字段契约。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0632
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00