Ceph RGW Rados Bucket Index 深度解析:索引模型、版本化、事务一致性与分片重分片
2026/9/23 2:45:00 网站建设 项目流程
  • 存储
  • 分布式文件系统
  • 对象存储
  • 后端
  • 高可用

【免费下载链接】ceph

Ceph is a distributed object, block, and file storage platform

项目地址:https://gitcode.com/gh_mirrors/ce/ceph
点击查看免费下载

导读

本文基于 Ceph 开源仓库中的 doc/dev/radosgw/bucket_index.rst,系统解析 RGW(RADOS Gateway)的 Rados Bucket Index 机制。Bucket Index 是每个存储桶(Bucket)内对象列表及其元数据(size、etag、mtime 等)的索引,是支撑 S3ListObjectsV2/ListObjectVersions与 SwiftGET Container等列目录 API 的底层数据结构。读完本文,你将掌握 bucket index 的存储模型、S3 版本化下的 olh 间接寻址、读写一致性保证、索引事务协议、分片与动态重分片机制,以及基于radosgw-admin的手动运维手段。

什么是 Bucket Index

在 RGW 中,每个存储桶都维护一份bucket index(桶索引),用于存放该桶内对象列表及每个对象的相关元数据(size、etag、mtime 等),从而支撑对象列举类 API 请求,并承担部分内部记账(bookkeeping)职责。这些 API 对应 S3 的ListObjectsV2ListObjectVersions以及 Swift 的GET Container

存储载体:RADOS omap

索引条目并非存放在独立文件中,而是存放在索引对象(或按分片拆分的多个索引对象)的RADOS omap条目中。omap 是 RADOS 对象上以键值对形式存储的扩展属性集合,天然适合存放“对象名 → 元数据”这种字典结构。

Indexless 桶

存储桶也可以被创建为'indexless'(无索引)模式。此类桶没有索引,因此无法进行列目录操作。在源码中对应 src/rgw/rgw_bucket_layout.h 的BucketIndexType枚举:

enum class BucketIndexType : uint8_t { Normal, // normal hash-based sharded index layout Indexless, // no bucket index, so listing is unsupported };

可以看到Indexless的注释明确指出 "no bucket index, so listing is unsupported"。而is_layout_reshardable()等辅助函数(rgw_bucket_layout.h)也以BucketIndexType::Normal作为可重分片的前提。

索引条目的粒度

对于非版本化存储桶,bucket index 中每个对象对应一个条目。对于 S3版本化存储桶则更为复杂(详见下一节):每个对象版本和删除标记(delete marker)都对应一个索引条目。

S3 对象版本化与 olh 间接寻址

版本化存储桶的 bucket index 中,除了按对象名排序的条目外,还为同名对象的各个版本维护了一组从新到旧排序的条目,供版本化列目录使用。这解释了为何版本化桶在RGWRados::calculate_preferred_shards()(src/rgw/driver/rados/rgw_rados.cc)中会提前触发重分片——源码注释说明,每个版本化桶的第一个对象需要 4 个索引条目,之后每新增一个对象需要 2 个条目(max_objs_per_shard /= 3),因此索引增长远快于普通桶。

head 对象与版本 oid

RGW 在rgw.buckets.data池中为每个对象版本存放一个 head 对象。该 RADOS 对象的 oid 由**对象名 + 版本 id(version id)**组合而成,从而保证不同版本对应不同的 RADOS 对象。

Object Logical Head(olh)

S3 的 GET/HEAD 请求按对象名访问时,应返回该对象的“当前(current)”版本。为了支持这一点,RGW 额外保存一个'object logical head'(olh)对象,其 oid 只包含对象名(不含版本 id),作为指向当前版本 head 对象的间接层(indirection)。这段间接寻址逻辑实现在RGWRados::follow_olh()(src/rgw/driver/rados/rgw_rados.cc):

int RGWRados::follow_olh(const DoutPrefixProvider *dpp, RGWBucketInfo& bucket_info, RGWObjectCtx& obj_ctx, RGWObjState *state, const rgw_obj& olh_obj, rgw_obj *target, optional_yield y)

从实现可见其核心流程:收集 olh 对象属性中以RGW_ATTR_OLH_PENDING_PREFIX为前缀的 pending 条目(即尚未收敛的版本写入/删除记录)→ 检查并移除已过期的 pending 条目(check_pending_olh_entries/remove_olh_pending_entries)→ 若仍有 pending 条目则调用update_olh()将 olh 推进到最新版本 → 最后从RGW_ATTR_OLH_INFO中解码RGWOLHInfo,将olh.target作为当前版本目标返回。若该 olh 标记为removed(对象当前为删除标记),则设置state->is_dm = true并返回-ENOENT

保证 olh 与 bucket index 的一致性

为了维持 olh 对象与 bucket index 之间的一致性,索引为每个对象名保留一个独立的 'olh' 条目,记录对其所有版本的全部写入/删除日志。当 RGW 需要依据索引判定“当前版本”时,RGWRados::apply_olh_log()(同样位于 src/rgw/driver/rados/rgw_rados.cc)会重放(replay)这份日志,保证 olh 对象收敛到与 bucket index 一致的“当前版本”。

一致性保证(Consistency Guarantee)

RGW 在对象操作上保证读后写一致性(read-after-write consistency):一旦客户端收到某个写请求的成功响应,该写入的效果必须对后续读请求可见。

例如,S3 客户端先发PutObject覆盖一个已存在对象、紧接着发GetObject读回时,RGW不得返回旧对象内容,必须返回新对象内容,或返回其后某次写入/删除的结果。

该保证适用于所有对象写请求PutObjectDeleteObjectPutObjectAcl等)与所有对象读请求HeadObjectGetObjectListObjectsV2等)。这一强一致性约束正是后续“三步索引事务”设计(见后文)的根本动因。

Rados 对象模型

S3/Swift 对象(即 'API objects')存放在rgw.buckets.data池中,每个 API 对象由一个head 对象零个或多个 tail 对象组成。而bucket index 对象则存放在独立的rgw.buckets.index池中。

写入对象时,head 对象最后写入,作为使其对读请求可见的原子“提交(commit)”动作。将索引与数据分池、head 后写,为下一步通过事务协议保证两者一致提供了物理基础。

分片(Sharding)与重分片(Resharding)

为什么需要分片

一个桶的索引通常会被拆分为多个 RADOS 对象,称为bucket index shards(索引分片)。在 RADOS 中,对同一对象的多次写入无法并行;把索引分散到更多 RADOS 对象上,即可提高写入并行度。

对于一次对象上传,其对应的索引分片通过对象名的哈希选择:

enum class BucketHashType : uint8_t { Mod, // rjenkins hash of object name, modulo num_shards };

(见 src/rgw/rgw_bucket_layout.h)

Mod模式即“对象名的 rjenkins 哈希值对分片数取模”。这一设计保证:同一对象的所有条目(及其在 S3 版本化桶中的所有版本)必定落在同一分片上,同时一般也能让对象均匀分布到各分片——但当某些对象拥有大量版本时,均匀性会被破坏(热点分片)。

分片数量配置

新桶的默认分片数为 11,可通过以下两个途径覆盖:

配置位置配置项说明
zonegroupbucket_index_max_shards区域组级默认分片数
ceph.confrgw_override_bucket_index_max_shards全局覆盖,优先级更高

源码佐证见 src/rgw/driver/rados/rgw_bucket.cc:

} else if (cct->_conf->rgw_override_bucket_index_max_shards > 0) { ... cct->_conf->rgw_override_bucket_index_max_shards;

以及 src/rgw/driver/rados/rgw_rados.cc 中bucket_index_max_shardsrgw_override_bucket_index_max_shards的优先读取逻辑。

分片布局结构定义在 src/rgw/rgw_bucket_layout.h 的bucket_index_normal_layout

struct bucket_index_normal_layout { uint32_t num_shards = 1; // 当前分片数 uint32_t min_num_shards = 1; // 该桶布局允许的最少分片数 BucketHashType hash_type = BucketHashType::Mod; };

注意:旧桶曾用num_shards = 0表示 1,rgw::num_shards()辅助函数做了兼容归一化(rgw_bucket_layout.h)。

重分片的必要性与触发条件

任何 RADOS 对象的 omap 都有约 100,000 条目的实际上限,因此必须保证分片数量足够、使每片条目数不超过该上限。随着桶内对象数增长,可能需要reshard(重分片)以增加分片数。

重分片存在两种方式:

  1. 手动重分片:通过radosgw-admin管理命令主动触发;
  2. 动态重分片(dynamic resharding):无需管理员干预自动发生。

动态重分片的判定逻辑位于RGWRados::check_bucket_shards()(src/rgw/driver/rados/rgw_rados.cc),关键流程为:

  • 首先检查配置开关rgw_dynamic_resharding(关闭则直接返回,不触发);
  • 校验布局可重分片(is_layout_reshardable);
  • 调用calculate_preferred_shards()结合rgw_max_dynamic_shards(动态分片上限)与rgw_max_objs_per_shard(每分片目标条目数)计算期望分片数;版本化桶按 1/3 折算条目上限以提前触发;
  • 若涉及缩减分片,还受rgw_dynamic_resharding_may_reduce开关约束;
  • 判定需要重分片时,通过add_bucket_to_reshard()(rgw_rados.cc)写入重分片队列(reshard log),记录old_num_shardsnew_num_shardsinitiator = Dynamic等信息,交由后台进程执行。

动态缩减分片与延迟等待

动态重分片也可以减少分片数量。由于某些桶的对象数会快速增减,且不希望频繁重分片去“追逐”这些波动,缩减分片操作在“判定需要缩减”与“实际执行”之间引入了延迟:到执行时刻会再次检查桶,仅当桶仍确实需要缩减分片时才真正执行。

该延迟默认 5 天,可用 ceph.conf 中的rgw_dynamic_resharding_reduction_wait配置。源码实现见 src/rgw/driver/rados/rgw_reshard.cc,读取该配置后换算为等待时间窗口。

布局信息的载体

桶的索引对象布局信息存放在RGWBucketInfostruct rgw::BucketLayout中(src/rgw/rgw_bucket_layout.h)。该结构除记录当前索引布局current_index外,还包含:

  • reshardingBucketReshardState枚举(None/InProgress/InLogrecord),标记重分片是否进行中;
  • target_index:重分片操作的目标布局(可选,重分片期间存在);
  • logs:未裁剪的桶日志(bilog)布局代际历史;
  • judge_reshard_lock_time:用于判断桶是否正在重分片的锁时间戳。

真正的重分片执行逻辑位于 src/rgw/driver/rados/rgw_reshard.cc。

手动重分片:radosgw-admin 命令

radosgw-admin提供了一组完整的重分片运维命令(src/rgw/radosgw-admin/radosgw-admin.cc 的命令清单):

命令用途
radosgw-admin bucket reshard --bucket=<name> --num-shards=<N>手动对指定桶重分片
radosgw-admin bucket set-min-shards设置动态重分片会为桶考虑的最少分片数
radosgw-admin reshard add将桶加入(调度)重分片队列
radosgw-admin reshard list列出所有正在重分片或已调度的桶
radosgw-admin reshard status读取桶的重分片状态
radosgw-admin reshard process处理已调度的重分片任务
radosgw-admin reshard cancel取消桶的重分片
radosgw-admin reshard stale-instances list列出重分片遗留的 stale-instances
radosgw-admin reshard stale-instances delete清理重分片遗留的 stale-instances
radosgw-admin reshardlog list列出桶重分片日志
radosgw-admin reshardlog purge裁剪桶重分片日志

示例:将mybucket强制重分片为 32 个索引分片:

radosgw-admin bucket reshard --bucket=mybucket --num-shards=32

查看所有待重分片/进行中的桶:

radosgw-admin reshard list radosgw-admin reshard status --bucket=mybucket

注:上述命令的具体行为与输出格式以当前仓库 radosgw-admin.cc 中的命令注册为准。

索引事务(Index Transaction)

问题:两个对象无法原子更新

所有对象写入或删除都必须同步更新 bucket index 以保持一致性。但 head 对象与 bucket index 存放在不同的 RADOS 对象(分属rgw.buckets.datargw.buckets.index池)中,无法用单个 RADOS 操作原子地同时更新两者。为了满足列目录操作的一致性保证,RGW 用三步桶索引事务协调这两次对象写入:

  1. 在 bucket index 对象上**预备(Prepare)**一个事务;
  2. 写入或删除 head 对象
  3. 在 bucket index 对象上**提交(Commit)事务;若第 2 步失败则取消(Cancel)**事务。

pending 与 completed 状态

对象写入与删除可能彼此竞争,因此同一对象在某一时刻可能同时存在多个已预备但未提交的事务。RGW 将对象条目区分为两种状态:

  • pending(挂起):存在未完成事务;
  • completed(完成):不存在未完成事务。

源码实现位置

该事务协议在 src/rgw/driver/rados/rgw_rados.cc 中实现为:

  • RGWRados::Object::Write::write_meta():对象写入路径;
  • RGWRados::Object::Delete::delete_obj():对象删除路径。

而 bucket index 侧的底层操作由 OSD 侧的 class 方法实现,位于 src/cls/rgw/cls_rgw.cc:

  • rgw_bucket_prepare_op()(cls_rgw.cc):预备事务,将条目标记为 pending;
  • rgw_bucket_complete_op()(cls_rgw.cc):提交事务(或在上游失败时回滚),将条目推进到 completed。

这些方法通过cls注册接口暴露给 radosgw 调用(cls_rgw.cc 中的cls.register_cxx_method(...)注册片段),形成 "RGW 侧编排 + OSD class 侧执行" 的完整事务链路。

列目录(Listing)

列出条目与盘查 head

列目录时,RGW 会读取 bucket index 中的全部条目(包括 pending 与 completed)。对任何 pending 条目,必须先检查对应 head 对象是否存在,再决定是否将条目纳入最终列表——这保证了即使事务中断,列表也不会出现幽灵条目。

dir suggest:从列目录到索引的反馈

如果 RGW 在索引事务中途崩溃,索引条目可能卡在 pending 状态。当后续列目录遇到这些 pending 条目时,RGW 会把从 head 对象读到的信息回写给 bucket index,用于更新条目、解决陈旧事务。这条回写消息称为'dir suggest'(目录建议)——因为 bucket index 把它当作一条提示/建议来采纳。

源码实现位置

列目录功能在 src/rgw/driver/rados/rgw_rados.cc 中实现为:

  • RGWRados::Bucket::List::list_objects_ordered():有序列目录;
  • RGWRados::Bucket::List::list_objects_unordered():无序列目录;
  • RGWRados::check_disk_state():读取 head 对象并编码建议的索引变更(即生成 dir suggest 载荷)。

bucket index 侧的对应操作在 src/cls/rgw/cls_rgw.cc 中实现为:

  • rgw_bucket_list()(cls_rgw.cc):按前缀/游标等条件遍历 omap 条目;
  • rgw_dir_suggest_changes()(cls_rgw.cc):应用列目录回传的建议,解决 stale pending 事务。

有序列目录的高昂代价

由于 RGW 对象依据哈希分布到桶的各索引分片上,分片之间不存在词典序(lexical ordering),只有单个分片内部保持有序。因此有序列目录(list_objects_ordered)是一种复杂且 I/O 密集的操作:

  • 每个分片并行拉取一批条目;
  • 使用**选择排序(selection sort)**在批次间归并,产出一段有序结果;
  • 当任一分的批次耗尽时,从所有分片再次批量读取,如此循环直至完成。

这也意味着:分片数量越多,有序列目录的并行批次管理开销越大;运维时需要在写入并行度(分片多)与有序列目录成本(分片少)之间权衡,并借助动态重分片在桶规模变化时自动保持平衡。

小结

Rados Bucket Index 是 RGW 对象存储语义的基石:它以 RADOS omap 承载对象元数据索引,通过 olh 间接层支撑 S3 版本化语义,以三步索引事务在数据与索引分池的约束下满足读后写一致性,并以分片 + 动态重分片应对 omap 容量上限与写入并发需求。理解这些机制,有助于在部署、排障与容量规划时做出正确的配置决策——例如合理设置rgw_override_bucket_index_max_shards、关注rgw_max_objs_per_shardrgw_dynamic_resharding系列参数,以及熟练使用radosgw-admin的重分片运维命令。

  • 存储
  • 分布式文件系统
  • 对象存储
  • 后端
  • 高可用

【免费下载链接】ceph

Ceph is a distributed object, block, and file storage platform

项目地址:https://gitcode.com/gh_mirrors/ce/ceph
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询