
磁盘 util 打满,GET 的 p99 翻倍,业务 QPS 却没涨。三节点、千万级对象的集群里,这个组合基本只有一个解释:有东西在偷前台的 I/O。查一圈下来,是存储自己的后台任务,scanner 正在扫。
它不报错,只表现为前台变慢,所以第一反应往往落在业务流量和网络上。RustFS 里容易留下这种痕迹的有三条线:一致性扫描(scanner)、后台修复(heal),以及大对象 multipart 上传路径上的边界。
scanner 和 heal 出事,会装成前台故障
scanner 和 heal 是两条常驻后台任务,multipart 则是前台上传路径上的边界。scanner 定期遍历所有桶和前缀,统计 usage(桶大小、对象数这类用量数据),顺带找出副本或纠删码(把数据切成分片分散存到多块盘)分片之间的不一致。heal 负责把这些不一致悄悄修掉,补齐缺失的分片。设计上它们都是低优先级、给前台让路的角色,但跑起来的 IO 是真实的:对象到了千万级,一轮全量扫描要遍历海量前缀;heal 遇到大范围不一致,修复流量能把磁盘打满。

这两条线翻车的几种形态
scanner 侧有三种典型毛病。检查点回环:扫描进度记录出错,同一个前缀反复扫,越扫越久(#8278 给检查点加了全局 cycle fence 校验)。大前缀丢段:目录层级深、对象多时,检查点只记到父级,中断重启后整段漏扫(#8292 让检查点在大前缀下保留子节点)。usage 重复计算叠加峰值内存偏高(#8325 合并收敛脏 journal,#8346 砍掉扫描与检查点的峰值内存)。
heal 侧的毛病更直接。修复重调度没有上限,一次积压能把后台任务队列排爆(#8351 给 durable MRF 重调度设了上限);peer 节点重启后,失败的 RPC 不再重试,跨节点修复就此卡死(#8365 补上了重启后的重试);read repair 还会去扫已经移除的盘(#8135 跳过这类观测)。
这批修复从 1.0.2 起合入。老版本集群如果出现过"扫描越来越慢、heal 积压降不下去",升级后这些行为都会变。收益最明显的是对象量千万级以上的集群,#8346 的内存收敛在重负载下差距大,小集群基本无感。
大对象上传的边界在哪
multipart(分片上传:把大文件切成多个 part 并行传,服务端最后合并)是往对象存储灌大文件的标准姿势,边界也最多。官方 S3 兼容性矩阵干脆把"分片列举与查询的边界行为"标为已排除项,各家实现差异最大的就是这块。四个改动值得逐个看:#8288 限制 multipart copy 的准入和 HTTP 写缓冲,单个大请求不再能把节点内存顶爆;#8295 修了 ListParts(列举已传分片)分页的终止条件和 marker 处理,客户端不会卡在错误的分页上;#8347 修正列举响应的 key 编码,key 里的特殊字符不再把客户端解析搞挂;#8095 降低 multipart 与对象 I/O 的开销,同样的数据量走更少的路径。
分片最小 5 MiB 是 S3 的约定(末片可以小于这个值),RustFS 与之一致。变化主要在内存曲线:写缓冲受限后,大并发上传时单节点内存平很多。要留意的是老客户端:如果它自己解析过 ListParts 的 marker,升级后行为会变。大文件列举到一半就停,先查客户端版本,别先怀疑网络。
怎么判断你的后台健不健康
四步,今天就能做完。先看状态,两个命令一起跑(rustfs 是 rc alias set 里配的集群别名):
rc admin heal status rustfs
rc admin scanner status rustfs
heal 这边关注两类信号:卡住不动的任务,和持续不降的 backlog。但官方有句提醒要记牢:队列长度为零并不能证明离线磁盘已更换、每个对象都可恢复,要结合存储拓扑和就绪状态一起判读。积压从哪来也有讲究:heal 的活儿来自五个来源(autoHeal、scanner、readRepair、internal、admin),status 里的 healOperations 字段按来源分组,能看出积压是扫描发现的、读路径顺手报的,还是管理员手动起的。
然后接遥测。RustFS 遥测走 OTLP(OpenTelemetry 协议)推送,它不提供原生 Prometheus /metrics 端点,Prometheus 直连 9000 或 9001 抓不到东西,必须跑一个 OpenTelemetry Collector 接收 OTLP、在 8889 端口以 Prometheus 格式重导出,官方参考栈就是这么接的。官方可观测性文档里有现成配置,接好后重点盯三条曲线:rustfs_scanner_cycle_duration_seconds(扫描耗时)、rustfs_scanner_cycles_total(周期推进,24 小时零增长就是扫描器停滞)、rustfs_heal_queue_delay_seconds(修复排队延迟)。
接着给扫描设预算,多数时候比调速度档更直接。单周期三件套:RUSTFS_SCANNER_CYCLE_MAX_DURATION_SECS(默认 1800,单周期最多跑多少秒)、RUSTFS_SCANNER_CYCLE_MAX_OBJECTS 和 RUSTFS_SCANNER_CYCLE_MAX_DIRECTORIES(默认 0 即不限制),不想让扫描吃满 I/O 就给它上硬顶。顺带查一个高危开关:RUSTFS_SCANNER_BITROT_CYCLE_SECS 默认 2592000(30 天一轮位衰减深度扫描),设成 0/true/on 会变成每个周期都深度扫描,IO 几乎必被打满,开头那种找不到流量来源的告警,不少就是它贡献的。另外 RUSTFS_SCANNER_SPEED 是启动时读取的环境变量,改档要重启进程才生效,白天慢夜里快不会自动切;想按时间窗错峰,用 RUSTFS_SCANNER_CYCLE(周期秒数)和 RUSTFS_SCANNER_START_DELAY_SECS(首轮启动延迟)来排。全部变量见环境变量参考。
最后,升级前后各拉一轮基线,重点看扫描耗时和修复队列延迟的收敛速度。官方节点修复页把监控项列得很直白:修复会读取健康分片并写入重建数据,恢复期间盯住客户端延迟、磁盘利用率、可用容量和修复队列。
按时间排的动作清单
今天:跑一遍上面两条 status 命令,把 Collector 链路接通,在 Prometheus 里确认 rustfs_scanner_* 序列已经出现。升级前:拉一轮扫描耗时与修复队列基线;客户端如果自己解析过 ListParts 的 marker,先在 staging 验一遍分页行为。上线后观察:大并发上传压一轮单节点内存曲线;heal 队列延迟要是持续上升超过半小时,回头查(官方建议告警也是按"持续 30 分钟"给的阈值)。
官方入口:可观测性看运维可观测性文档,scanner 与 heal 的状态判读见节点修复,环境变量清单在环境变量参考,各版本改动以 GitHub Releases 为准,源码在 GitHub。
本文深入剖析RustFS集群中scanner一致性扫描、heal后台修复及multipart大对象上传三条路径引发的磁盘IO打满问题。重点说明其伪装前台故障的特性、典型翻车形态(如检查点回环、修复重调度失控、ListParts分页缺陷等),并提供健康度判断四步法:状态检查、OTLP遥测接入、扫描预算配置与基线对比。强调OpenTelemetry Collector必要性及关键环境变量调优策略。
4201

被折叠的 条评论
为什么被折叠?



