Ceph RGW 对象上传完整流程解析:请求解析 → 执行 → 落盘
下面从 一条 PUT /bucket/object 请求进入 RGW 开始,完整梳理对象上传流程,并标注每个阶段在 RGW 源码/架构中体现的设计模式。
1. 全景流程图
2. 请求接收与解析阶段
RGW 的请求处理可以概括为:接收 → 解析 → 验证 → 路由 → 执行 → 响应 。在架构上,RGWFrontend、RGWRESTMgr、RGWHandler 是这个阶段的核心组件。
2.1 HTTP Frontend 接收连接
RGW 支持多种前端,例如 Civetweb、Beast 等。前端负责监听端口、读取 socket、解析 HTTP 协议层内容,然后把请求交给 RGW 内部处理逻辑。 对于一次对象上传:
http
PUT /mybucket/myobject HTTP/1.1
Host: rgw.example.com
Content-MD5: ...
Content-Type: application/octet-stream
x-amz-meta-owner: alice
Authorization: AWS4-HMAC-SHA256 ...
RGW 会解析:
- HTTP 方法:
PUT - URL:
bucket=mybucket,object=myobject - 公共请求头:如
Content-Type - S3 特有头:如
Content-MD5、x-amz-meta-* - 查询参数:如
uploadId、partNumber - 请求体:大对象通常不会全部读进内存,而是作为流式输入处理
设计模式体现:
这一层常体现 装饰器模式。RGW 会把底层 socket 封装成 I/O 对象,然后叠加缓冲、分块传输编码、HTTP 解析、限速等过滤器能力,而不是把所有逻辑写进一个巨大的 socket 处理类。
3. REST 路由与 Handler 分发
RGW 需要同时支持 S3、Swift、Admin API 等协议风格。RGWRESTMgr 会根据 URL、Authorization 头、路径风格判断使用哪类 Handler,例如:
RGWHandlerREST_S3RGWHandlerREST_SwiftRGWHandler_Admin简化后的逻辑如下:
cpp
RGWHandler* RGWRESTMgr::get_handler(req_state* s) {
if (is_s3_request(s)) {
return new RGWHandlerREST_S3();
} else if (is_swift_request(s)) {
return new RGWHandlerREST_Swift();
}
return nullptr;
}
设计模式体现:
这里体现了 工厂模式 / 简单工厂。调用方不直接 new 具体协议处理器,而是通过 REST 管理器统一创建。
4. 认证与授权阶段
对于 S3 对象上传,RGW 会解析 Authorization 头,常见包括:
- AWS Signature V2
- AWS Signature V4
- 匿名访问
- OpenStack Keystone 认证
- 预签名 URL 简化模型:
cpp
class AuthEngine {
public:
virtual int authenticate(req_state* s) const = 0;
virtual ~AuthEngine() = default;
};
class AuthV4 : public AuthEngine {
public:
int authenticate(req_state* s) const override {
// 校验 AWS4 签名
return 0;
}
};
class AuthV2 : public AuthEngine {
public:
int authenticate(req_state* s) const override {
// 校验 S3 v2 签名
return 0;
}
};
class AuthStrategy {
std::vector<std::unique_ptr<AuthEngine>> engines;
public:
int authenticate(req_state* s) const {
for (const auto& e : engines) {
if (e->authenticate(s) == 0)
return 0;
}
return -EPERM;
}
};
认证完成后,RGW 还要检查:
- 用户是否有 bucket 写权限
- bucket policy 是否允许
- object ACL 是否限制写入
- 是否触发版本控制、对象锁、加密等策略
设计模式体现:
这是典型的 策略模式 。不同认证算法实现同一个接口,RGW 根据请求特征选择或串联策略。
如果多个认证引擎按优先级依次尝试,也带有 责任链模式 的影子。
5. 创建 RGWOp:从 HTTP 请求到业务操作对象
RGW 不会在路由层直接写死"上传对象应该调用哪个函数",而是把每个 REST 操作抽象成一个 RGWOp 对象。 例如对于 S3 PUT 对象,对应的核心类通常是 RGWPutObj。搜索资料中也提到,PUT 上传的核心处理流程最终落在 RGWPutObj::execute() 中。 简化逻辑如下:
cpp
RGWOp* RGWHandlerREST_S3::get_obj_op() {
switch (s->op) {
case OP_GET:
return new RGWGetObj_ObjStore_S3();
case OP_PUT:
return new RGWPutObj_ObjStore_S3();
case OP_DELETE:
return new RGWDeleteObj_ObjStore_S3();
default:
return nullptr;
}
}
随后 Handler 会初始化这个 Op:
cpp
RGWOp* op = get_obj_op();
if (!op) {
return -ERR_METHOD_NOT_ALLOWED;
}
op->init(store, s, this);
op->pre_processing();
op->execute();
op->complete();
设计模式体现:
这里是 RGW 中非常经典的 工厂模式 + 模板方法模式。
- 工厂模式 :由 Handler 根据请求方法创建具体
RGWOp。- 模板方法模式 :
RGWOp基类定义了通用生命周期,如初始化、权限校验、执行、完成、错误响应;子类只实现自己的execute()细节。
6. 对象上传执行阶段:数据面核心流程
6.1 RGW 对象不是简单等于一个 RADOS 对象
一个 S3/RGW 对象在底层可能对应一个或多个 RADOS 对象。RGW 使用三个关键概念管理映射关系:
| 概念 | 含义 |
|---|---|
rgw_max_chunk_size |
RGW 下发到 RADOS 的单个 I/O 大小,也决定 head 对象大小 |
rgw_obj_stripe_size |
条带大小,后续数据按此大小切分成多个 RADOS 对象 |
| manifest | 描述用户对象与底层 RADOS 对象的映射关系 |
数据通常写入类似 {zone}.rgw.buckets.data 的存储池中。 |
7. 普通整体上传流程
假设上传一个对象:
http
PUT /bucket/object
Content-Length: 20M
7.1 对象大小小于等于 chunk size
如果对象小于等于 rgw_max_chunk_size,则通常只生成一个 RADOS 对象,即 head 对象。 该对象中包含:
- 用户数据
- 对象元数据
- xattrs
- manifest OID 形式可以近似理解为:
text
{bucket_id}_{object_name}
7.2 对象大小大于 chunk size
如果对象大于 chunk size,则拆分为:
- head 对象:保存前 chunk 数据和元数据
- shadow / tail 对象:保存剩余数据 搜索资料中提到,大于 chunk size 的对象会被拆成 head、多个中间对象和 tail 对象。shadow 对象的名字中通常会引入随机串,避免并发上传同名对象时冲突。 简化形式:
text
head: {bucket_id}_{object_name}
shadow: {bucket_id}_shadow_{random}_{stripe_id}
7.3 写入顺序:先 shadow,后 head,最后 index
RGW 上传对象时,典型顺序是:
- 写 shadow / tail 对象
- 写 head 对象
- 更新 bucket index 这个顺序非常关键。资料中分析指出,RGW 会先生成随机串作为 shadow 对象 OID 的一部分,保证多个会话即使上传同名对象,在 shadow 阶段也不会冲突。随后最后写 head,让 head 成为对象可见性的提交点。 伪代码如下:
cpp
int RGWRados::put_object(...) {
RGWObjManifest manifest;
// 1. 先写非 head 部分
while (off < data_len) {
write_shadow_object(...);
manifest.append_stripe(...);
}
// 2. 写 head 对象,附带 xattrs + manifest
write_head_object(
head_oid,
first_chunk_data,
xattrs,
manifest
);
// 3. 更新 bucket index
update_bucket_index(...);
}
设计模式体现:
这里的
manifest是一个典型的 组合/聚合型元数据结构 。如果从工程模式角度看,构建 manifest 的过程类似 Builder 思想:逐步追加 stripe、偏移、RADOS 对象信息,最后由 head 对象统一提交。
8. 分段上传流程:Multipart Upload
大文件常用 S3 Multipart Upload,也就是 MPU。它分为三步:
8.1 初始化 MPU
客户端发送:
http
POST /bucket/object?uploads
RGW 会生成一个 uploadId,并记录 MPU 元数据。
8.2 上传每个 Part
客户端可以并行上传:
http
PUT /bucket/object?partNumber=1&uploadId=xxx
PUT /bucket/object?partNumber=2&uploadId=xxx
每个 Part 在底层会被写成临时 RADOS 对象。若单个 Part 大于条带大小,也会继续拆分。 搜索资料中提到,每个分段的第一个对象通常是 _multipart_...,其余为 _shadow_...。
8.3 Complete MPU:原子提交元数据
当所有 Part 上传完成后,客户端发送:
http
POST /bucket/object?uploadId=xxx
RGW 不会把已有数据重新拷贝一遍,而是:
- 读取所有 Part 信息
- 组装完整 manifest
- 写最终 head 对象
- head 对象的 manifest 指向各个 Part 对应的 RADOS 对象 资料中明确指出,MPU Complete 是一个近似原子的元数据提交操作,最终 head 对象本身可能不包含用户数据,而主要包含 manifest。 简化如下:
cpp
int RGWRados::complete_mpu(...) {
RGWObjManifest manifest;
for (part : parts) {
manifest.append(part.stripe_info);
}
write_head_object(
final_head_oid,
no_data_or_empty_data,
xattrs,
manifest
);
update_bucket_index(...);
}
设计模式体现:
MPU Complete 很适合用 命令模式 / 事务提交语义 来理解:
- 每个上传 Part 是独立的子操作;
- Complete 汇总这些子操作,一次性提交最终可见状态;
- head 对象是提交点。
9. 元数据、索引与响应阶段
对象数据写完后,RGW 还需要维护控制面状态。
9.1 对象元数据
对象元数据主要包括:
- Owner
- ACL
- ETag
- Content-Type
- 用户自定义元数据
- manifest
- 版本信息
- 加密信息
- 对象锁信息 这些信息通常保存在 head 对象的 xattrs 或 OMAP 中。
9.2 Bucket Index
bucket index 用于支持 LIST objects、bucket 统计、版本管理、生命周期等能力。上传对象完成后,RGW 会更新 bucket index,使该对象可以被列举和检索。 资料中提到,bucket index 是 RGW 对象可列举能力的关键,并且支持动态分片以扩展单个 bucket 的对象数量。
9.3 响应客户端
PUT 成功后,RGW 通常返回:
http
HTTP/1.1 200 OK
ETag: "..."
x-amz-version-id: ...
10. 核心对象布局总结
以下表格总结普通上传与分段上传的主要 RADOS 对象形态:
| 场景 | RADOS 对象 | 作用 |
|---|---|---|
| 小对象整体上传 | head 对象 | 保存数据 + 元数据 + manifest |
| 大对象整体上传 | head 对象 | 保存前 chunk 数据、元数据、manifest |
| 大对象整体上传 | shadow 对象 | 保存后续 stripe 数据 |
| MPU 上传 Part | multipart 对象 | 保存某个 Part 的第一个 stripe |
| MPU 上传 Part | shadow 对象 | 保存某个 Part 的后续 stripe |
| MPU Complete | final head 对象 | 保存最终 manifest,指向所有 Part 数据 |
11. RGW 上传流程中的设计模式全景表
| 流程节点 | 设计模式 | 在 RGW 中的体现 |
|---|---|---|
| HTTP I/O 处理 | 装饰器模式 | 对 socket 叠加缓冲、chunked、HTTP 解析、限速等能力 |
| 协议识别与 Handler 创建 | 工厂模式 / 简单工厂 | 根据 S3/Swift/Admin 创建对应 Handler |
| RGWOp 创建 | 工厂模式 | 根据 PUT/GET/DELETE 创建 RGWPutObj、RGWGetObj 等 |
| RGWOp 生命周期 | 模板方法模式 | 基类定义 init / execute / complete 流程,子类实现细节 |
| 认证 | 策略模式 | S3 v2、S3 v4、Keystone、匿名认证实现统一接口 |
| 多认证引擎尝试 | 责任链模式 / 组合策略 | 依次尝试多个认证方式 |
| 访问控制 | 策略模式 | ACL、bucket policy、用户策略统一判断 |
| librados 访问 | 适配器模式 / 门面模式 | RGWRados / rgw::sal::Store 封装底层 librados API |
| manifest 构建 | Builder 思想 / 组合模式 | 逐步追加 stripe、offset、RADOS 对象信息 |
| MPU Complete | 命令模式 / 事务提交语义 | 汇总所有 Part,一次性提交 head manifest |
| 多 RGW 元数据一致性周边 | 观察者模式思想 | 依赖 RADOS watch/notify 或 bilog 等机制同步缓存/日志 |
12. 一个更完整的简化代码视图
以下代码是架构示意,不是 Ceph 源码逐行还原,但能帮助面试表达:
cpp
class RGWOp {
public:
virtual ~RGWOp() = default;
void run() {
init_processing(); // 模板方法固定流程
verify_permission();
execute(); // 子类实现
complete();
}
protected:
virtual int init_processing() = 0;
virtual int verify_permission() = 0;
virtual int execute() = 0;
virtual void complete() = 0;
};
class RGWPutObj : public RGWOp {
rgw::sal::Store* store;
req_state* s;
protected:
int init_processing() override {
// 解析 Content-MD5、Content-Type、x-amz-meta-* 等
return 0;
}
int verify_permission() override {
// 检查 bucket policy、ACL、用户权限
return 0;
}
int execute() override {
// 核心上传流程
return store->put_object(
s->bucket,
s->object,
s->in_stream,
s->attrs,
s->manifest
);
}
void complete() override {
// 响应 ETag、version_id 等
}
};
class RGWHandlerREST_S3 {
public:
RGWOp* get_op(req_state* s) {
switch (s->op) {
case OP_PUT:
return new RGWPutObj(); // 工厂模式
default:
return nullptr;
}
}
};
class RadosStore : public rgw::sal::Store {
RGWRados rados;
public:
int put_object(...) override {
// 适配器/门面:把 RGW 语义转换为 RADOS 语义
return rados.put(...);
}
};
13. 面试总结话术
"一条 RGW 对象上传请求,表面上是一个 HTTP PUT,背后会经历请求解析、协议路由、认证授权、RGWOp 创建、数据落盘、元数据和 bucket index 更新几个阶段。
RGW 的核心设计非常清晰:前端负责 HTTP 协议,Handler 负责协议级路由,RGWOp 负责具体业务动作,Store/RGWRados 负责屏蔽底层 RADOS。对象不是简单写成一个 RADOS 对象,而是根据 chunk 和 stripe 切成 head/shadow 等多个 RADOS 对象,并用 manifest 描述映射。
设计模式上,Handler 创建 Op 是工厂模式,RGWOp 生命周期是模板方法,认证是策略模式,librados 封装是适配器/门面,I/O 处理链是装饰器,Multipart Complete 则体现了原子提交和命令式事务语义。"