RustFS Tier Stats 契约解析:GET /admin/v3/tier-stats 的存量与滚动活动度量体系

发布时间:2026/9/10 17:08:37
RustFS Tier Stats 契约解析:GET /admin/v3/tier-stats 的存量与滚动活动度量体系 RustFS Tier Stats 契约解析GET /admin/v3/tier-stats 的存量与滚动活动度量体系【免费下载链接】rustfs2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting migration and coexistence with other S3-compatible platforms such as MinIO and Ceph.项目地址: https://gitcode.com/GitHub_Trending/rus/rustfs本文依据仓库 docs/architecture/tier-stats-contract.md 编写围绕 RustFS 管理接口GET /rustfs/admin/v3/tier-stats的响应契约展开讲清楚存储存量inventory与滚动活动rolling activity两套互不替代的度量、版本 2 响应体的每个字段语义、多节点滚动环的合并算法以及远程 tier 请求的埋点规范。读完你将能够正确解释该接口的每一个返回值并理解在 RustFS 中新增 tier 计量来源或接入远程 tier 指标时应当遵循的边界。一、这个契约要防止什么样的缺陷tier-stats-contract.md开篇就给出了它的使用场景当你需要修改GET /rustfs/admin/v3/tier-stats的返回值、新增一个 tier 计量来源accounting source或者把某个指标接到远程 tier 请求上时本文档就是必须遵守的契约。文档同时声明了事实来源source of truth清单rustfs/src/admin/handlers/tier.rsGetTierInfo、tier_stats_bodycrates/ecstore/src/services/notification_sys.rsClusterTierDailyStatscrates/ecstore/src/bucket/lifecycle/tier_last_day_stats.rsLastDayTierStatscrates/ecstore/src/services/tier/warm_backend.rsMeteredWarmBackendcrates/obs/src/metrics/schema/tier.rs该契约存在的核心动机是防止一类典型的度量混淆把两种互不相关的数字互相报告。理解这两者是读懂整个接口的前提。二、两种度量永远不要混为一谈一个 tier 携带两个互不相关的数值把其中一个当作另一个上报正是这份契约要杜绝的缺陷存储存量Stored inventory——当前有多少字节、多少对象、多少版本存在于该 tier 中。它由扫描器scanner产生持久化在 data usage 快照DataUsageInfo::tier_stats中本身已经是集群范围的cluster-wide数值。它是一个水平量level而不是速率。从源码看管理端通过 rustfs/src/admin/handlers/tier.rs 中的tier_inventory()调用load_admin_data_usage_from_backend_cached读取持久化的 usage 快照其字段类型TierStats来自rustfs_data_usagecrate。滚动活动Rolling activity——集群在过去 24 小时内完成了多少次进入该 tier 的转换transition。每个节点在内存中维护一个 24 桶bin的环形数组TransitionState::add_lastday_stats并且只统计自己完成的转换。它是一个速率窗口rate window而不是水平量并且重启即丢失保存在内存中不落盘。两者不能互相替代一个空的滚动窗口不代表tier 是空的可能 tier 里堆积了大量数据只是最近 24 小时没有转换发生一个数据满满的 tier不代表最近有活动可能早已停止写入。从实现上看这一两个独立来源的架构体现在tier_stats_body()中存储存量来自持久化扫描快照滚动活动来自各节点内存环的聚合任何一个来源失败都不会让另一个来源的字段消失而是由各自的状态字段单独声明。滚动环的底层实现24 桶环形数组LastDayTierStats 是滚动活动度量的核心数据结构pub const TIER_DAILY_STATS_BINS: usize 24; pub struct LastDayTierStats { bins: [TierStats; 24], updated_at: OffsetDateTime, }注释明确写道滚动的一天中每小时一个桶。桶索引就是 UTC 小时因此该数组是一个被写入者向前推进的环ring而不是一个队列。 关键行为add_stats()写入前先调用forward_to()把环推进到当前时间超过 24 小时未更新则整体清零since 24时self.bins [TierStats::default(); 24]否则把中间经过的桶逐小时清零total()将 24 个桶求和得到该节点过去 24 小时的活动总量to_wire()/from_wire()是跨节点 RPC 的互换形式TierDailyStatsWire它把桶数组与updated_at一起发送接收方用from_wire()校验并重建环。宽度不对不是 24 桶或时间戳无法表示都会返回InvalidData错误而不是当作零样本合并——一个损坏的环是损坏的 peer 输入不是零样本。该文件的单元测试覆盖了这些语义wire_round_trip_preserves_the_ring_and_its_clock、a_ring_of_the_wrong_width_is_rejected、an_unrepresentable_clock_is_rejected、merge_sums_two_nodes_that_transitioned_in_the_same_hour、merge_ages_out_a_peer_ring_older_than_a_day。三、响应契约contractVersion 与两个版本contractVersion命名了响应体的形状body shape。常量定义在 tier.rsconst TIER_STATS_CONTRACT_VERSION: u32 2; const TIER_STATS_FORMAT_LEGACY: str legacy;版本 1legacy不再扩展版本 1 是一个裸露的{TIER: {total_size: ...}}映射里面只有应答进程自身的滚动计数器响应体中没有任何东西能区分单节点与集群、区分速率与水平量。它仍然可以通过?formatlegacy提供给钉死在该格式上的调用方但不再扩展。值得注意的细节tier_stats_wants_legacy_format()对任何未识别的format值直接拒绝返回InvalidArgument而不是静默地以当前格式应答——一个请求了它能解析的格式的调用方绝不能收到一个不同的 200 响应。版本 2默认响应体版本 2 是默认响应体对应的结构体为TierStatsBodyV2序列化后形如{ contractVersion: 2, inventory: { status: accounted, updatedAt: 2026-09-09T10:00:00Z, detail: null }, activity: { status: complete, nodesReporting: 4, nodesExpected: 4, unavailableNodes: [] }, tiers: [ { name: WARM, type: s3, inventory: { totalSize: 1048576, numVersions: 2, numObjects: 10 }, transitionsLast24h: { totalSize: 524288, numVersions: 1, numObjects: 3 }, transitionsUpdatedAt: 2026-09-09T10:00:00Z } ] }各字段语义如下inventory.status取值accounted、not-accounted或unavailable只有accounted状态下每个 tier 的inventory值才会出现缺失的 per-tier 计量表示 not accounted绝不是 zero——因为扫描器只在 tier 存在之后才按 tier 对对象分类如果缺省被报告成 0将无法与集群尚未被扫描区分开来。这一点在 crates/data-usage/src/data_usage.rs 的文档注释中也有对应说明unavailable表示对象存储未初始化或 usage 快照读取失败tier_inventory()的Err分支。activity.status取值complete或partial并携带nodesReporting、nodesExpected以及unavailableNodes无法被问到、超时、或返回了当前构建拒绝合并的环的节点。实现上ClusterTierDailyStats::is_complete()定义为unavailable_nodes.is_empty() nodes_reporting nodes_expected见 notification_sys.rs。早于TierDailyStatsRPC 的 peer会应答UNIMPLEMENTED它会被报告为 unavailable而不是被报告为零活动——这样就不会把旧节点没有数据误读成该节点确实零活动。type字段只有当名字是一个已配置的远程 tier 时才出现。一个携带统计值却没有配置的 tier 名要么是扫描器为本地存储类local storage class做的计量要么是自快照以来已被删除的 tier。对应结构体TierInfoBody中#[serde(rename type, skip_serializing_if Option::is_none)]的tier_type字段。计数对象的字段名totalSize、numVersions、numObjects——这是 admin 客户端从madmintier stats 形状中期望的 camelCase 拼写而不是版本 1 泄漏出来的 Rust 字段名。结构体上使用#[serde(rename_all camelCase)]实现。文档特别强调这统一了拼写并不代表该 envelope 与某个特定madmin版本字节级兼容客户端侧的检查属于发行版希望支持的客户端。组装规则assemble_tier_stats_body()将已配置的 tier、存储存量、滚动活动三个来源的名字做并集BTreeSet因此一个还没有数据的已配置 tier 必须出现而一个携带数据但没有配置的 tier 也不能仅仅因为无法归类就被丢弃。同时支持?tierNAME过滤参数过滤是大小写不敏感的eq_ignore_ascii_case。单节点场景的语义cluster_tier_daily_stats()中有一个精心设计的特例如果当前进程没有通知系统notification system那么这个进程就是它能代表的整个集群于是它把自己的环报告为完整的单成员结果nodes_reporting: 1, nodes_expected: 1而不是报告一个它无法证明的集群总量。四、为什么合并环不是求和总量这是契约中最关键的一个算法语义节点在各自的 per-peer 截止时间下被并发询问每个应答用LastDayTierStats::merge合并而不是相加。pub fn merge(self, m: LastDayTierStats) - LastDayTierStats { let mut cl self.clone(); let mut cm m; let mut merged LastDayTierStats::default(); if cl.updated_at.unix_timestamp() cm.updated_at.unix_timestamp() { cm.forward_to(mut cl.updated_at); merged.updated_at cl.updated_at; } else { cl.forward_to(mut cm.updated_at); merged.updated_at cm.updated_at; } for (i, _) in cl.bins.iter().enumerate() { merged.bins[i] cl.bins[i].add(cm.bins[i]); } merged }合并先把较旧的环向前推进到较新的环的时钟forward_to因此一个昨天就停止转换的节点只贡献仍然处于滚动一天内的那些小时。如果直接相加原始总量那些已过期的小时会随着节点保持在线而永远存活。聚合侧的合并逻辑见 notification_sys.rs 的merge_tier_daily_stats()对每个 tier若已存在则existing.merge(stats)否则直接插入。节点探测使用timeout(TIER_DAILY_STATS_PROBE_TIMEOUT, client.tier_daily_stats())包裹并用join_all并发执行保证一个被黑洞的成员不会让 admin 请求一直挂起。双重计数在源头被阻止而不是在聚合器add_lastday_stats在提交commit该转换的节点上、每个已提交转换只运行一次。因此一个在另一个节点上重试的转换只被计数一次——由最终提交它的那个节点计数。五、Tier 请求指标两个接缝、封闭标签集rustfs_tier_requests_success和rustfs_tier_requests_failure两个计数器在每个远程 tier 请求必经的两个接缝处更新MeteredWarmBackend——包装new_warm_backend构建的每个后端MeteredTransitionCandidateReconciler——包装new_transition_candidate_reconciler构建的独立的恢复探测句柄。因此新的 provider 通过构造即被计数a new provider is therefore counted by construction。相关实现见 warm_backend.rsMeteredWarmBackend::record()调用global_metrics().record_tier_request(operation, outcome)。两个刻意不埋点的接缝文档明确列出了故意委托、不设计数器的两个位置因为给它们计数会报告从未发出的请求validate——其 trait 默认实现在除一个后端外的所有后端上都不执行远程请求应答Unsupported的probe_transition_candidate——这与上面的 trait 默认是同一回事。封闭的标签集标签集被 crates/scanner-metrics/src/metrics.rs 中的两个枚举封闭TierRequestOperationput、get、remove、probe、in_use共 5 个值ALL常量列出全部TierRequestOutcomesuccess、backend_error、timeout、cancelled共 4 个值。两个枚举的注释都强调集合是有意封闭的这些值会成为指标标签所以新增操作是有意的 schema 变更而不是调用点可以凭空发明的东西。tier 名、endpoint 和对象 key 永远不能成为标签原因有二endpoint 在其 userinfo 形式中携带凭据https://user:passhost泄漏到标签里就是凭据泄漏对象 key 无上界会让时间序列数量随 key 数量无限增长。这体现在 crates/obs/src/metrics/schema/tier.rs 的指标描述符上TIER_REQUESTS_SUCCESS_MDoperation单标签Remote tier requests the backend acknowledged, by operationTIER_REQUESTS_FAILURE_MDoperationoutcome双标签Remote tier requests that did not complete, by operation and outcome。文档注释补充标签集由记录点使用的 operation/outcome 枚举固定因此序列数量在构造上就是有界的不会随 tier 名、endpoint 或对象 key 增长。timeout 与 cancelled 的判定timeout和cancelled只从std::io::ErrorKind识别绝不由错误消息识别——这样一条提及 endpoint 的消息就无法进入标签。见TierRequestOutcome::from_errorpub fn from_error(err: std::io::Error) - Self { match err.kind() { std::io::ErrorKind::TimedOut Self::Timeout, std::io::ErrorKind::Interrupted Self::Cancelled, _ Self::BackendError, } }文档同时给出一个诚实的边界说明在转换客户端长出有界截止时间之前rustfs/backlog#2204实际上很少失败会携带TimedOut所以大多数失败会落入backend_error。这个分类是后续工作扩展的接缝而不是宣称超时已经可区分——即当前分类体系是扩展点不是对现状的过度承诺。outcome 只分类请求本身最后一个语义要点outcome 只对远程请求本身分类。一个远程 PUT 成功、但本地提交失败的转换在这里是success——因为远程服务确实执行了请求把它记为 tier 失败会把泄漏的远程对象隐藏在表面上的后端故障之后。本地提交失败应该通过 ILM 任务事件指标ILM task-event metrics来观察。六、如何在 RustFS 中接入新的 tier 计量来源综合以上契约当你要在 RustFS 中新增一个 tier 计量来源或接入远程 tier 请求指标时需要遵循以下检查清单确认你动的是哪种量如果新增的是存量来源它必须语义上是 level、最好是集群范围的如扫描器快照并写入DataUsageInfo::tier_stats如果是活动来源它必须是 rate window并且要声明清楚谁统计、统计什么时间段。响应体版本不要改动版本 1legacy 不扩展改动默认响应体意味着更新TIER_STATS_CONTRACT_VERSION并同步更新本文档契约与相关测试。状态字段优先于数值无法提供某个来源时用对应的status声明not-accounted/unavailable/partial永远不要用 0 来掩盖拿不到数据。新 provider 的计数在MeteredWarmBackend或MeteredTransitionCandidateReconciler两个接缝内包装即可通过构造被计数不要在validate或应答Unsupported的探测上埋点。标签纪律只能使用TierRequestOperation/TierRequestOutcome枚举值作为标签禁止把 tier 名、endpoint、对象 key 放入标签错误分类一律走ErrorKind不解析消息文本。outcome 只描述请求远程成功即success本地提交失败属于 ILM 事件指标不要混入 tier 请求指标。七、验证与测试契约的每一条语义在仓库中都有对应的测试佐证tier_last_day_stats.rs 内置测试验证环的往返序列化、宽度校验、时间戳校验、同小时合并与过期环淘汰tier.rs 内置测试验证filter_tier_stats的全量返回与大小写不敏感过滤、resolve_tier_name的路径/查询参数解析优先级、tier_mutation_error的 reload/save 错误映射、以及XMinioAdminTierBackendInUse/XMinioAdminTierBackendNotEmpty的 wire 契约metrics.rs 内置测试验证from_error对TimedOut/Interrupted/ 普通错误的分类、标签值as_label以及计数槽位的分配5 × 4 20个AtomicU64槽notification_sys.rs 中ClusterTierDailyStats的complete/partial判定与merge_tier_daily_stats的合并逻辑均有测试覆盖。八、总结RustFS 的 tier-stats 契约围绕三条主线设计存量与活动两套度量互不替代、环的合并而非总量相加保证滚动窗口语义正确、两个埋点接缝加封闭标签集保证指标可信且基数有界。理解这份契约既能正确解读GET /rustfs/admin/v3/tier-stats的每个字段也能在扩展 RustFS tier 能力时避免把速率当水平量、把单节点当集群、把拿不到数据当零、把远程成功本地失败当后端故障这四类经典度量事故。相关文件索引契约文档docs/architecture/tier-stats-contract.md管理端实现rustfs/src/admin/handlers/tier.rs节点滚动环crates/ecstore/src/bucket/lifecycle/tier_last_day_stats.rs集群聚合crates/ecstore/src/services/notification_sys.rs埋点后端crates/ecstore/src/services/tier/warm_backend.rs标签枚举crates/scanner-metrics/src/metrics.rs指标描述符crates/obs/src/metrics/schema/tier.rs【免费下载链接】rustfs2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting migration and coexistence with other S3-compatible platforms such as MinIO and Ceph.项目地址: https://gitcode.com/GitHub_Trending/rus/rustfs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询