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.5(
docs.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(如
STANDARD、GLACIER)配置不同数据池,RGW
manifest 中记录每个 stripe 所在的 placement 与 storage
class。
二、对象数据模型:head 与 tail
Squid Rados Gateway Data
Layout(docs.ceph.com/en/squid/radosgw/layout/)把
RGW 对象钉成三类信息:metadata(用户/bucket
实例)、bucket index、payload
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-progress。in-progress 后不能
cancel。多站点:Reef 之前 multisite 不支持 dynamic
reshard;Squid 需按 zone feature
文档核对,不能默认「开了就全网自动」。
失败模式:写被堵在 reshard 窗口会导致客户端超时;队列积压会使大桶长期停留在单分片热点;旧集群 reshard 后可能残留陈旧桶实例或生命周期规则失效(文档提供了陈旧实例清理与生命周期修复命令)。把「在线 reshard」理解成对业务完全无感,与 Squid 文档中的短暂写阻塞口径不符。
四、Multipart Upload 的 RADOS 路径
S3 multipart upload 在 RADOS 上的映射:
- InitiateMultipartUpload:在
meta池创建一个临时 multipart 元数据对象,返回upload_id。 - UploadPart(每个
part):写入数据池中独立命名的 part 对象(名包含
upload_id与 part number)。每个 part 内部仍按rgw_max_chunk_size切 stripe。 - 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 层面:
- Head 对象名包含 version_id
后缀(
{bucket_id}_{key}_{version_id})。 - 最新版本的 head 对象同时存在一个不带 version 后缀的「current version pointer」对象(或通过 index current pointer 机制)。
- Bucket index 中每个 key 有一条当前版本条目,以及通过
x-amz-version-id索引的历史版本条目。
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 的元数据查询。请求路径:
- 鉴权:RGW 从请求头提取
Authorization(AWS Sig v4),本地查找{zone}.rgw.meta池中的 user 对象(user.{uid}),得到 access_key → secret_key 映射,验证签名。user 元数据对象包含用户 quota、caps 等字段。 - Bucket → Pool
映射:
bucket.{bucket_name}在rgw.meta中记录该 bucket 的 owner、创建时间、存储 placement(决定使用哪个 data 池和 index 池)、bucket_id(全局唯一,用于对象命名)。 - Bucket ACL / Policy:bucket 策略存储在
bucket 元数据对象的 xattr
中(
user.rgw.acl、user.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 池。配置路径:
zonegroup配置 placement targets(命名为default-placement、cold等)。- 每个 placement target 指向不同的 data 池(可以是副本池 vs EC 池,SSD vs HDD 磁盘类型)。
- 用户 PUT 时通过
x-amz-storage-class请求头指定存储类,RGW 据此选择 placement。
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 级)
- Ceph Squid — RADOS
Gateway
(
docs.ceph.com/en/squid/radosgw/) - Ceph Squid — Data
Layout — head/tail、marker、
.dir.*OMAP - Ceph Squid — Dynamic Bucket Index Resharding — 写阻塞窗口、阈值与上限
- Ceph Squid — Multi-site — bi-log 与同步架构
- 源码:
src/rgw/(rgw_rados.cc、rgw_bucket.cc、rgw_gc.cc)@ v19.2.5
论文 / 奠基(A)
- Weil, S. A. et al. (2007). RADOS: A Scalable, Reliable Storage Service for Petabyte-scale Storage Clusters. PDSW ’07(对象服务语义;RGW 是其上的 HTTP/S3 适配,不改变 RADOS 不透明对象模型——这正是 S3 Select 难以下推到 OSD 的根)。
站内对照
- MinIO(storage/48) — S3 兼容对象存储对照
- 第 7 篇 · BlueStore 架构(OMAP 存储 bucket index 的底层实现:BlueStore → RocksDB column family)
→ 上一篇:CephFS / MDS · 系列目录 · 下一篇:可观测
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Ceph RADOS】RADOS 内核全景:五轴坐标系与 18 篇路线
补齐站内 distributed/38 之上的 RADOS 内核层缺口;定义 Map 传播、通信、一致性、本地存储、接入代价五条坐标系,给出 18 篇阅读路线与系列边界。
【Ceph RADOS】MON/MGR 与 Map:quorum、epoch 传播与客户端可见性
拆解 Ceph MON Paxos quorum、OSDMap/PGMap 结构与 epoch 传播机制;说明 MGR 主备模型与模块框架;分析客户端 epoch 缓存与可见性窗口的边界条件。
【Ceph RADOS】PG 与 Peering:PrimaryLogPG、PG log 与状态机
拆解 PG 命名与生命周期、PrimaryLogPG 的日志式复制设计、pg_log_entry_t 格式、last_epoch_started 与 last_epoch_clean 的语义差异,以及 peering 状态机从 Reset 到 Active 的完整路径。
【Ceph RADOS】客户端写路径:从 librados 到副本应答与 stale read 边界
追踪一次 RADOS 写从 librados IoCtx 构建 MOSDOp、Objecter 路由、Primary 的 do_op() 执行、副本 MOSDRepOp 应答链,到客户端可读性边界与 stale read 触发条件的完整路径。