RGW 对象多版本功能系统架构与代码解析
基于 Ceph Reef (17.x)/Squid (18.x) 版本源码分析,核心逻辑与其他版本基本一致。
目录
1. 多版本功能系统架构总览
1.1 功能概述
Ceph RGW 的对象多版本功能遵循 S3 Object Versioning 规范,允许用户在桶中保留、检索和恢复对象的不同版本。这为数据保护提供了强大支持,特别是在配合 Object Lock 使用时可以实现对象不可变存储。
1.2 系统架构图
sql
┌─────────────────────────────────────────────────────────────────────┐
│ Client (S3/Swift API) │
│ PUT/GET/DELETE Object with Versioning Headers │
└──────────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ RGW Gateway 进程 │
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌────────────────────┐ │
│ │ Request │──▶│ Versioning │──▶│ Versioning Ops │ │
│ │ Handler │ │ Manager │ │ (Put/Get/Delete) │ │
│ └──────────────┘ └──────────────────┘ └─────────┬──────────┘ │
│ │ │
│ ┌─────────────────────────▼──────────┐ │
│ │ RGWRados::Object (Versioning) │ │
│ │ ┌───────────────────────────────┐ │ │
│ │ │ - put_obj() 带版本控制 │ │ │
│ │ │ - get_obj() 指定版本 │ │ │
│ │ │ - delete_obj() 版本删除 │ │ │
│ │ └──────────────┬────────────────┘ │ │
│ └─────────────────┼──────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────▼──────────────────┐│
│ │ Bucket Index Versioning Operations ││
│ │ ┌─────────────────────────────────────────────────────────┐ ││
│ │ │ cls_bucket_list_op / cls_bucket_put_op 等 RADOS 类 │ ││
│ │ │ - 版本化桶索引操作 │ ││
│ │ │ - 维护版本元数据 │ ││
│ │ └──────────────────────────────┬──────────────────────────┘ ││
│ └─────────────────────────────────┼──────────────────────────────┘│
└─────────────────────────────────┼───────────────────────────────────┘
│ librados 操作
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Ceph RADOS 集群 │
│ │
│ └── pool: default.rgw.buckets.index ← 桶索引(含版本信息) │
│ └── 对象: .dir.{bucket_id}.{shard_id} │
│ └── OMAP: 对象名 → 版本ID → 元数据 │
│ │
│ └── pool: default.rgw.buckets.data ← 实际数据对象 │
│ └── 对象: bucket_id/obj_name.version_id │
└─────────────────────────────────────────────────────────────────────┘
1.3 核心组件说明
| 组件 | 功能 | 代码位置 |
|---|---|---|
| Versioning Manager | 管理桶版本状态,处理版本化操作 | src/rgw/rgw_bucket_versioning.cc |
| RGWRados::Object | 处理版本化对象的 CRUD 操作 | src/rgw/rgw_rados.cc |
| Bucket Index | 存储版本化元数据,维护版本历史 | src/cls/rgw/cls_rgw.cc |
| RADOS Class | 实现版本化桶索引的原子操作 | src/cls/rgw/cls_rgw.cc |
2. 多版本数据结构与存储格式
2.1 版本元数据结构
cpp
// 桶索引中的版本条目结构
struct rgw_bucket_dir_entry {
std::string key; // 对象名
uint64_t size; // 对象大小
utime_t mtime; // 修改时间
std::string etag; // ETag
std::string content_type; // 内容类型
std::vector<rgw_bucket_dir_entry_meta> custom_attrs; // 自定义元数据
// 版本控制相关字段
std::string version_id; // 版本ID
bool is_delete_marker{false}; // 是否为删除标记
std::string latest_version_id; // 最新版本ID
void encode(bufferlist& bl) const {
ENCODE_START(1, 1, bl);
encode(key, bl);
encode(size, bl);
encode(mtime, bl);
encode(etag, bl);
encode(content_type, bl);
encode(custom_attrs, bl);
encode(version_id, bl);
encode(is_delete_marker, bl);
encode(latest_version_id, bl);
ENCODE_FINISH(bl);
}
};
// 桶版本状态
struct rgw_bucket_versioning_status {
VersioningStatus status; // Suspended/Enabled
bool mfa_delete{false}; // 是否需要MFA删除
void encode(bufferlist& bl) const {
ENCODE_START(1, 1, bl);
encode((uint32_t)status, bl);
encode(mfa_delete, bl);
ENCODE_FINISH(bl);
}
};
2.2 OMAP 存储格式
javascript
Pool: default.rgw.buckets.index
Object: .dir.{bucket_id}.{shard_id}
OMAP Key: 对象名
OMAP Value: 序列化的桶目录条目(包含版本信息)
// 版本化桶索引的 OMAP 布局
"example.txt" → {
"key": "example.txt",
"size": 1024,
"version_id": "v1.0",
"is_delete_marker": false,
"latest_version_id": "v2.0",
// ... 其他元数据
}
3. 核心流程图
3.1 启用版本控制流程
3.2 版本化对象上传流程
3.3 版本化对象删除流程
3.4 版本列表获取流程
4. 关键代码解析
4.1 版本控制管理器:src/rgw/rgw_bucket_versioning.cc
cpp
class RGWBucketVersioning {
CephContext *cct;
RGWRados *store;
public:
RGWBucketVersioning(CephContext *_cct, RGWRados *_store)
: cct(_cct), store(_store) {}
// 启用/禁用版本控制
int set_versioning(const DoutPrefixProvider *dpp,
const rgw_bucket& bucket,
VersioningStatus status,
bool mfa_delete,
optional_yield y) {
// 1. 验证权限
int ret = check_permissions(dpp, bucket, y);
if (ret < 0) return ret;
// 2. 获取桶元数据
RGWBucketInfo bucket_info;
ret = store->get_bucket_info(dpp, bucket, bucket_info, nullptr, y);
if (ret < 0) return ret;
// 3. 更新版本状态
bucket_info.versioning_status = status;
bucket_info.mfa_delete = mfa_delete;
// 4. 保存桶元数据
return store->put_bucket_instance_info(dpp, bucket_info, false, y);
}
// 获取版本状态
int get_versioning(const DoutPrefixProvider *dpp,
const rgw_bucket& bucket,
VersioningStatus *status,
bool *mfa_delete,
optional_yield y) {
RGWBucketInfo bucket_info;
int ret = store->get_bucket_info(dpp, bucket, bucket_info, nullptr, y);
if (ret < 0) return ret;
if (status) *status = bucket_info.versioning_status;
if (mfa_delete) *mfa_delete = bucket_info.mfa_delete;
return 0;
}
// 检查是否启用版本控制
bool is_versioning_enabled(const rgw_bucket& bucket) {
RGWBucketInfo bucket_info;
// 快速检查,不获取完整信息
return bucket_info.versioning_status == VersioningStatus::Enabled;
}
};
4.2 版本化对象操作:src/rgw/rgw_rados.cc
cpp
// 版本化对象上传
int RGWRados::Object::Write::put_obj(const DoutPrefixProvider *dpp,
RGWObjectCtx& obj_ctx,
const char *data,
size_t len,
map<string, bufferlist>& attrs,
rgw_obj& target_obj,
bool exclusive,
const real_time& mtime,
map<string, bufferlist>* pattrs,
rgw_obj_manifest *manifest,
const char *if_match,
const char *if_nomatch,
const real_time *src_mtime,
const real_time *mtime,
const char *owner,
const char *tag,
uint64_t olh_epoch,
uint64_t ver_epoch) {
// 1. 检查版本状态
if (bucket_info.versioning_status == VersioningStatus::Enabled) {
// 2. 生成新版本ID
string version_id = generate_version_id();
// 3. 构造版本化对象名
rgw_obj versioned_obj = target_obj;
versioned_obj.key.set_name(target_obj.key.name + "." + version_id);
// 4. 上传版本化对象
int ret = put_obj_versioned(dpp, obj_ctx, data, len, attrs,
versioned_obj, exclusive, mtime,
pattrs, manifest, ...);
if (ret < 0) return ret;
// 5. 更新桶索引
return update_bucket_index_versioned(dpp, bucket, target_obj,
version_id, versioned_obj);
}
// 非版本化处理
return put_obj_non_versioned(dpp, obj_ctx, data, len, attrs,
target_obj, exclusive, mtime, ...);
}
// 版本化对象删除
int RGWRados::Object::Delete::delete_obj(const DoutPrefixProvider *dpp,
RGWObjectCtx& obj_ctx,
RGWObjVersionTracker *objv_tracker,
int *http_code) {
// 1. 检查版本状态
if (bucket_info.versioning_status == VersioningStatus::Enabled) {
// 2. 检查是否指定版本
if (!version_id.empty()) {
// 删除指定版本
return delete_version_specific(dpp, obj_ctx, version_id,
objv_tracker, http_code);
} else {
// 创建删除标记
return create_delete_marker(dpp, obj_ctx, objv_tracker, http_code);
}
}
// 非版本化删除
return delete_obj_non_versioned(dpp, obj_ctx, objv_tracker, http_code);
}
// 版本列表获取
int RGWRados::list_bucket_versions(const DoutPrefixProvider *dpp,
const rgw_bucket& bucket,
RGWBucketList& results,
map<string, bool> *common_prefixes,
size_t max,
string& next_marker,
bool *is_truncated,
bool list_all_versions) {
// 1. 验证版本控制已启用
if (bucket_info.versioning_status != VersioningStatus::Enabled) {
return -EINVAL; // InvalidRequest
}
// 2. 解析请求参数
// prefix, delimiter, max-keys, version-id-marker 等
// 3. 遍历桶索引,收集版本条目
vector<rgw_bucket_dir_entry> entries;
int ret = list_bucket_versioned(dpp, bucket, prefix, delimiter,
max, next_marker, entries,
is_truncated, list_all_versions);
if (ret < 0) return ret;
// 4. 构建响应
results.set(entries, common_prefixes);
return 0;
}
4.3 桶索引版本化操作:src/cls/rgw/cls_rgw.cc
cpp
// 添加版本条目到桶索引
static int cls_rgw_bucket_put_versioned(cls_method_context_t hctx,
bufferlist *in, bufferlist *out) {
// 1. 解码输入参数
cls_rgw_bucket_put_versioned_op op;
try {
op.decode(*in);
} catch (buffer::error& err) {
return -EINVAL;
}
// 2. 获取桶索引对象
bufferlist bl;
int ret = cls_cxx_getxattr(hctx, "bucket.dir", &bl);
if (ret < 0) return ret;
// 3. 解码当前桶目录
RGWBucketDirectory bucket_dir;
try {
bucket_dir.decode(bl);
} catch (buffer::error& err) {
return -EIO;
}
// 4. 添加版本条目
rgw_bucket_dir_entry entry = op.entry;
entry.version_id = generate_version_id();
bucket_dir.entries[entry.key] = entry;
// 5. 更新最新版本指针
bucket_dir.latest_version_ids[entry.key] = entry.version_id;
// 6. 编码并保存桶目录
bufferlist updated_bl;
bucket_dir.encode(updated_bl);
ret = cls_cxx_setxattr(hctx, "bucket.dir", &updated_bl);
return ret;
}
// 获取版本列表
static int cls_rgw_bucket_list_versions(cls_method_context_t hctx,
bufferlist *in, bufferlist *out) {
// 1. 解码输入参数
cls_rgw_bucket_list_versions_op op;
try {
op.decode(*in);
} catch (buffer::error& err) {
return -EINVAL;
}
// 2. 获取桶索引对象
bufferlist bl;
int ret = cls_cxx_getxattr(hctx, "bucket.dir", &bl);
if (ret < 0) return ret;
// 3. 解码桶目录
RGWBucketDirectory bucket_dir;
try {
bucket_dir.decode(bl);
} catch (buffer::error& err) {
return -EIO;
}
// 4. 过滤版本条目
vector<rgw_bucket_dir_entry> version_entries;
for (auto& entry : bucket_dir.entries) {
if (op.prefix.empty() || entry.first.compare(0, op.prefix.size(), op.prefix) == 0) {
version_entries.push_back(entry.second);
}
}
// 5. 排序(按版本时间戳)
std::sort(version_entries.begin(), version_entries.end(),
[](const rgw_bucket_dir_entry& a, const rgw_bucket_dir_entry& b) {
return a.mtime < b.mtime;
});
// 6. 截取结果
if (version_entries.size() > op.max) {
version_entries.resize(op.max);
*is_truncated = true;
}
// 7. 编码响应
cls_rgw_bucket_list_versions_ret ret;
ret.entries = version_entries;
ret.encode(*out);
return 0;
}
5. 配置与运维
5.1 启用版本控制
bash
# 1. 启用桶版本控制
aws s3api put-bucket-versioning --bucket my-bucket --versioning-configuration Status=Enabled
# 2. 验证版本控制状态
aws s3api get-bucket-versioning --bucket my-bucket
# 3. 查看版本列表
aws s3api list-object-versions --bucket my-bucket
# 4. 上传新版本对象
aws s3api put-object --bucket my-bucket --key example.txt --body new-content.txt
# 5. 获取特定版本对象
aws s3api get-object --bucket my-bucket --key example.txt --version-id v1.0
# 6. 删除特定版本
aws s3api delete-object --bucket my-bucket --key example.txt --version-id v1.0
5.2 配置参数
| 参数 | 默认值 | 说明 |
|---|---|---|
rgw_versioning_enabled |
false | 是否全局启用版本控制 |
rgw_versioning_max_versions |
1000 | 每个对象最大保留版本数 |
rgw_versioning_retention_days |
30 | 版本保留天数 |
rgw_versioning_mfa_delete |
false | 是否需要MFA删除 |
5.3 监控与日志
bash
# 查看版本控制相关日志
grep -i "versioning" /var/log/ceph/ceph-client.rgw.*.log
# 监控版本数量
radosgw-admin bucket stats --bucket=my-bucket | jq '.num_versions'
# 查看特定对象的所有版本
radosgw-admin object versions --bucket=my-bucket --object=example.txt
5.4 故障排查
🔧 常见问题与解决方案 1. **版本控制未生效** - 检查桶状态:`aws s3api get-bucket-versioning --bucket=my-bucket` - 确认请求包含正确头部:`x-amz-versioning-status: Enabled` 2. **版本列表为空** - 检查版本控制是否已启用 - 确认对象有多个版本:`aws s3api list-object-versions --bucket=my-bucket` 3. **删除标记未创建** - 检查权限是否足够 - 确认版本控制状态为 `Enabled` 4. **版本数量过多导致性能问题** - 调整 `rgw_versioning_max_versions` - 使用生命周期规则清理旧版本:`aws s3api put-bucket-lifecycle-configuration` --- ## 附:关键源码文件索引 | 文件 | 内容 | |---|---| | `src/rgw/rgw_bucket_versioning.h/.cc` | 版本控制管理器,处理桶级别版本状态 | | `src/rgw/rgw_rados.cc` | 版本化对象 CRUD 操作实现 | | `src/cls/rgw/cls_rgw.cc` | RADOS 类方法,实现桶索引的版本化操作 | | `src/rgw/rgw_op.h` | 定义版本化操作类(如 `RGWPutObj`) | | `src/rgw/rgw_rest_s3.cc` | S3 API 层面处理版本化请求 | --- *本文档可保存为 `ceph-rgw-versioning-analysis.md`。如需某一特定流程(如多站点版本同步、生命周期与版本控制交互)的详细解析,可继续展开。*