土法炼钢 · 系统与基础设施

【Ceph RADOS】RGW:索引池与数据池、S3 到 RADOS 的映射边界

文章导航

分类入口
storagedistributed
标签入口
#ceph#rgw#s3#rados#bucket-index#multipart#versioning#squid#v19.2.5

目录

RGW(RADOS Gateway,又称 radosgw)是 Ceph 的 HTTP 对象网关,实现 S3 与 Swift 兼容接口。从外部看,它是一个普通 HTTP 服务;从 RADOS 角度,它是一组访问模式比 RBD/CephFS 更复杂的 librados 客户端。

核心问题:一次 S3 PUT 在 RADOS 上触发多少 op、bucket index 为何会成为瓶颈、大对象的 manifest 如何组织、版本控制链在 RADOS 对象级别是什么样的。

本文是「Ceph / RADOS 存储内核」系列第 14 篇(共 18 篇)。→ 系列目录

篇目 核心内容
第 13 篇 · CephFS / MDS 可恢复 MDS、caps、subtree thrashing
第 14 篇 · RGW 索引/数据池、S3→RADOS 映射
第 15 篇 · 可观测 ceph status / pg dump / OSD perf 口径

版本锚定:Ceph Squid v19.2.5docs.ceph.com/en/squid/radosgw/;源码 src/rgw/ @ v19.2.5)。


一、RGW 的池布局

RGW 使用一组 RADOS 池,每个 zone 有独立的池集合。默认 zone 的典型池名与用途如下:

池名 用途
.rgw.root zone/zonegroup 全局配置、realm
{zone}.rgw.control RGW 进程间通知(watch/notify)
{zone}.rgw.meta 用户账号、bucket → owner 映射、quota
{zone}.rgw.log 操作日志、GC 日志、bi-log(multisite 同步日志)、data sync log
{zone}.rgw.buckets.index bucket index(每个 bucket shard 对应一个 RADOS 对象,OMAP 内含对象条目)
{zone}.rgw.buckets.data 对象 head 与 tail 数据
{zone}.rgw.buckets.non-ec 若数据池是 EC 池,head 对象(含小量数据与 xattr)存在此副本池

可以为不同 storage class(如 STANDARDGLACIER)配置不同数据池,RGW manifest 中记录每个 stripe 所在的 placement 与 storage class。


二、对象数据模型:head 与 tail

Squid Rados Gateway Data Layoutdocs.ceph.com/en/squid/radosgw/layout/)把 RGW 对象钉成三类信息:metadata(用户/bucket 实例)、bucket indexpayload data。payload 再拆 head / tail:

命名与查找:REST 带着 account + bucket + key 进来;RGW 用 bucket marker(bucket id,形如 default.7593.4)拼出数据 OID。Head 名是 {marker}_{key}(文档示例 default.7593.4_image.png);marker 生成后不再解析,斜杠可进 key。Index 对象默认 .dir.{marker},分片时再挂 shard 后缀。

Head:xattr 持 ACL、Content-Type、ETag、用户元数据,以及 manifest(描述各 stripe 布局)。数据可内联最多 rgw_max_chunk_size(默认 4MiB)——小对象一次 RADOS write 原子完成。EC 数据池场景下,head(含小量内联与元数据)通常落在副本池 buckets.non-ec,因 index/head 语义需要副本池能力。

Tail / shadow:manifest 指向的 stripe OID(文档示例含 __shadow_.… 形态),只装裸数据,无 S3 元数据。大对象 = 1 head + 若干 tail。

flowchart TD
  S3Put["PUT /bucket/key (10 MiB)"] --> RGW
  RGW --> HeadObj["head: meta + manifest\n+ 内联至 max_chunk\n→ non-ec / data"]
  RGW --> Tail1["tail stripe … → data 池"]
  RGW --> IdxUpdate["index OMAP_SET\n→ .dir.marker[.shard]"]

一次 PUT 的 RADOS op 量级(机制计数,非实测延迟):≤ rgw_max_chunk_size → head write + index OMAP ≈ 2 op;更大则再加 \(\lceil (size - inline)/chunk \rceil\) 次 tail write。任何「只谈 S3 PUT 一次」的延迟模型,若没把 index OMAP 算进去,都在撒谎。


三、Bucket Index:OMAP 与 reshard

单 shard 结构

Index 是挂在 .dir.{marker} 上的 OMAP(键=对象名,值=rgw_bucket_dir_entry:ETag、size、mtime、version、delete marker 等;header 里还有对象数/总字节等会计字段)。OMAP 落在 BlueStore→RocksDB;单 shard 条目冲到 \(10^5\) 量级后,LIST 的 OMAP_GET* 与 compaction 抖动是已知痛点——这是 RADOS 对象附带 KV 的代价,不是「再加一台 RGW」能消掉的。

多 shard

num_shards = N 时,按对象名哈希进不同 .dir.{marker}.{shard}。写并发分散到多 OSD;LIST 必须在 RGW 做 K 路归并,延迟随 \(N\) 上升。扩大 shard 与加速 LIST 是直接对立——选型时只能偏一侧。

Dynamic Resharding(修正机制)

Squid 文档(radosgw/dynamicresharding/):rgw_dynamic_resharding 默认 true;超过 rgw_max_objs_per_shard(默认 100000)触发;动态上限 rgw_max_dynamic_shards 默认 1999(优先选质数分片)。后台扫描入队,单线程按序执行。

关键语义(勿与「双写」混淆):resharding 短暂阻塞对该 bucket 的写(读仍可);不是长期新旧 shard 双写。可用 radosgw-admin reshard status --bucket …not-resharding / in-progressin-progress 后不能 cancel。多站点:Reef 之前 multisite 不支持 dynamic reshard;Squid 需按 zone feature 文档核对,不能默认「开了就全网自动」。

失败模式:写被堵在 reshard 窗口会导致客户端超时;队列积压会使大桶长期停留在单分片热点;旧集群 reshard 后可能残留陈旧桶实例或生命周期规则失效(文档提供了陈旧实例清理与生命周期修复命令)。把「在线 reshard」理解成对业务完全无感,与 Squid 文档中的短暂写阻塞口径不符。


四、Multipart Upload 的 RADOS 路径

S3 multipart upload 在 RADOS 上的映射:

  1. InitiateMultipartUpload:在 meta 池创建一个临时 multipart 元数据对象,返回 upload_id
  2. UploadPart(每个 part):写入数据池中独立命名的 part 对象(名包含 upload_id 与 part number)。每个 part 内部仍按 rgw_max_chunk_size 切 stripe。
  3. CompleteMultipartUpload
    • 读取所有 part 元数据。
    • 生成最终对象的 manifest,指向各 part 数据(无需复制数据,manifest 直接引用 part 对象)。
    • 写入 head 对象(manifest + ETag = MD5(所有 part MD5 的二进制拼接再 MD5))。
    • 更新 bucket index。
    • 删除临时 multipart 元数据对象(推迟到 GC)。

Multipart 完成后的对象读:RGW 按 manifest 依次读取各 part 的 tail 对象并返回给客户端,客户端无感知。若某个 part 对象在 GC 之前丢失,读取时 RGW 会返回 500 error(RADOS read 失败)。

未完成的 multipart 残留AbortMultipartUpload 或超时的 upload 会在数据池留下孤立 part 对象,需 GC 清理(radosgw-admin gc process)。可通过 radosgw-admin multipart list 查看未完成的 upload。


五、对象版本控制(Versioning)的 RADOS 模型

Version chain

开启 versioning 的 bucket 中,每次 PUT 创建一个新的版本,每个版本有唯一 version_id(12 字节随机字符串)。在 RADOS 层面:

Delete marker

执行 S3 DELETE /bucket/key(不带 version_id)时,RGW 创建一个 delete marker——一个特殊的零字节版本对象,不含数据。当前版本指向此 delete marker,GET /bucket/key 返回 404,但历史版本仍可通过 version_id 访问。

永久删除需指定 DELETE /bucket/key?versionId={version_id},此时 RGW 删除对应 head 对象与 manifest 引用的 tail 对象(加入 GC 队列)。

GC 机制

删除的对象数据不立即删除,而是加入 {zone}.rgw.log 池中的 GC 列表(gc.* RADOS 对象,OMAP 存储待清理项)。GC 进程(radosgw 内部线程)定期执行,批量发 RADOS delete op。GC 延迟可能导致磁盘空间不立即释放;若 GC 积压(停 radosgw 一段时间后重启,或删除大量小对象),可通过 radosgw-admin gc process --include-all 手动加速(会影响正常请求延迟)。


六、多站点(Multi-site)同步架构

多站点(multi-site)是 RGW 的灾备/地理分布方案。每个 zone 对应一个独立 Ceph 集群,zone 之间通过 sync agent 复制对象。

同步基于 bi-log(bilog,bucket index log):每次 bucket index 变更(PUT、DELETE、multipart complete)在 log 池追加一条 bi-log 条目。远端 zone 的 RGW sync thread 拉取 bi-log,重放对应的 GET + PUT 操作,将对象数据写入本 zone 的 RADOS。

同步是异步最终一致:主 zone 写入成功即返回客户端,副 zone 同步有延迟(秒到分钟级,取决于网络带宽与对象大小)。在副 zone 上读可能读到旧版本。这是对象存储的经典最终一致模型,与 CephFS 的 POSIX 强一致形成对比。

多站点失败场景: - bi-log 积压:若同步速度追不上写入速度,radosgw-admin sync status 会显示 behind;积压量过大时副 zone 数据严重落后。 - 网络分区:主副 zone 断开期间,副 zone 数据停止更新,恢复后自动从断点继续同步;不触发 fencing,需业务层自行处理双写与冲突。


七、用户与 Bucket 元数据路径

S3 API 的鉴权和 bucket 查找在每次请求中都经过 RGW 的元数据查询。请求路径:

  1. 鉴权:RGW 从请求头提取 Authorization(AWS Sig v4),本地查找 {zone}.rgw.meta 池中的 user 对象(user.{uid}),得到 access_key → secret_key 映射,验证签名。user 元数据对象包含用户 quota、caps 等字段。
  2. Bucket → Pool 映射bucket.{bucket_name}rgw.meta 中记录该 bucket 的 owner、创建时间、存储 placement(决定使用哪个 data 池和 index 池)、bucket_id(全局唯一,用于对象命名)。
  3. Bucket ACL / Policy:bucket 策略存储在 bucket 元数据对象的 xattr 中(user.rgw.acluser.rgw.bucket-policy)。

每次 S3 请求至少触发一次 rgw.meta 池的 RADOS read(用户元数据 + bucket 元数据)。若 RGW 进程启用了本地缓存(rgw_cache_enabled=true,默认开),重复请求直接命中内存缓存,不需要每次 RADOS read。但多个 RGW 实例的缓存一致性依赖 rgw.control 池的 watch/notify 机制:某个 RGW 更新了 bucket 元数据后,通知其他 RGW 失效对应缓存条目。

缓存一致性的失败场景:若 watch/notify 丢失(网络抖动导致 RADOS watch 断开),某个 RGW 可能持有过期的 bucket 元数据(例如 bucket policy 已更新但未失效),持续时间直到 rgw_cache_expiry_interval(默认 900 秒)过期。这在多 RGW 实例下是已知的最终一致性窗口。

八、存储类(Storage Class)与 Placement

Squid v19.2.5 支持 S3 Storage Class 语义,允许不同的存储策略(热数据 vs 冷数据)映射到不同的 RADOS 池。配置路径:

RGW manifest 中记录每个 stripe 的 placement 标签,读取时按 manifest 从对应池读取数据,存储类对客户端透明。

存储类与 S3 Lifecycle 的关系:配置 lifecycle rule 中的 Transition(将对象从 STANDARD 转为 GLACIER/COLD 类)在 RGW 中的实现是:lifecycle thread 找到符合条件的对象,将其重新写入目标存储类对应的池(实质上是一次 RGW PUT,更新 manifest 指向新池的对象),然后删除原存储类的数据对象。这个过程是带 I/O 代价的迁移,不是元数据层面的仅标签变更——在高频 lifecycle transition 场景下,集群的后台 I/O 会显著上升。

九、S3 Select 与数据过滤下推

Squid v19.2.5 支持 S3 Select API,允许客户端发送 SQL 表达式对 CSV、JSON 或 Parquet 格式的对象进行服务端过滤,只返回符合条件的行/字段,减少网络传输。实现落在 RGW 进程侧(Squid 文档 S3 Select;源码树 src/rgw/ 下相关 handler,核对 tag v19.2.5 具体文件):OSD 层面仍是完整读取对象后在 RGW 内过滤(不下推到 OSD),因此对大对象节省的是 RGW→客户端 的网络带宽,而非 OSD→RGW 的内部带宽。与真正的列式存储(如 Parquet + Pushdown)相比,仍需从 OSD 读完整对象,无法跳过不相关的列块。这是 RADOS 对象存储模型(对象不透明)与列式存储(结构化行列访问)之间的固有工程间隙。

十、谱系、争论与开放问题

谱系:RGW 的前身是 2010 年前后以独立进程形式加入 Ceph 的 REST 网关,最初只支持 S3 v1 subresource style。Bucket index 的 OMAP 实现替换了早期的 RocksDB flat key 存储,是 Firefly(0.80)版本的重大改进。Dynamic resharding(自动扩容 shard)在 Luminous(12.x)版本引入,解决了早期 bucket index 单 shard 扩展上限的痛点。

争论:Bucket index 的 RADOS OMAP 方案在顺序列举(LIST)场景有性能争议。由于多 shard 合并需要应用层 K 路归并,LIST 延迟与 shard 数成正比,而增加 shard 数又是应对大 bucket index 热点的必要手段。社区曾讨论将 index 改为 ordered B-tree 类结构或外置 KeyDB,但截至 Squid v19.2.5,主线仍使用 OMAP/RADOS 模型。与 MinIO 对比:MinIO 的 bucket index 使用本地 etcd 或内置 metadata DB,LIST 不经 RADOS,在小对象高频 LIST 场景有延迟优势,代价是放弃了 RADOS 的多副本与 CRUSH 保护。

开放问题: 1. bi-log 的一致性语义:多站点 bi-log replay 是 at-least-once 语义,若两个 zone 同时写入同一 key,合并时使用 last-write-wins(基于 mtime)。在真正双写(active-active multi-site)场景下,冲突解决策略尚无社区统一的强一致方案。 2. GC 在高吞吐删除场景的可观测性:GC 积压量没有直接的 Prometheus 指标,需要通过 radosgw-admin gc list 轮询,这在告警系统中是盲区。 3. 与 S3 Select / SQL 语义对接:Squid 已有基本的 S3 Select(CSV/JSON 过滤下推),但在 RADOS 层面 select 仍是 read whole object + filter,无列式存储优化。


参考资料

官方文档(A 级)

论文 / 奠基(A)

站内对照

上一篇:CephFS / MDS · 系列目录 · 下一篇:可观测

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .