项目中文名:吞吞 英文名:oss 项目宠物:吞吞(TunTun)——一只把对象块吞进桶里的小家伙。
S3 兼容的自研分布式对象存储(MinIO 的等价实现)。支持多盘纠删码 8+4、跨节点 gRPC mesh、版本控制、生命周期、桶策略与审计、SSE 三模式与对象锁、STS 临时凭据、事件通知/Kafka、S3 Select、桶复制与多站点复制、访问点/Object Lambda、Batch/Inventory/tier、sync 桶与 3-voter Raft 控制面。aws-cli/rclone 的默认大文件路径(≥8 MiB 自动 multipart)与断点习惯直接可用,浏览器控制台可分块上传并管理。
功能
| 领域 | 能力 |
|---|---|
| 存储内核 | 多盘纠删码 8+4、跨节点 gRPC mesh、quorum 提交、xl.meta 版本链、小对象内联、多盘自愈、全对象 GET 流式直出、分片并行写、列举键缓存 |
| S3 API | 对象/桶/版本/标签、Multipart、partNumber、Range、Copy 指令、分块上传与断线续传 |
| 生命周期 | 过期、非当前版本、转换规则,Storage Class、分层归档与 Restore |
| 安全 | SSE-S3/KMS/C、密钥库、对象锁与保留期、STS、桶策略与审计(异步批量队列)、TLS/mTLS、子账户三档权限、Argon2id、账户软/硬字节与对象数配额 |
| 数据流 | 事件通知/Kafka、S3 Select、桶复制(Prefix/Tag/And 过滤)、多站点复制 |
| 控制面 | 访问点、Object Lambda、Batch Operations、Inventory、作业引擎与终态保留(JobPurge)、恢复后续状态/调度租约复制(feature 7)、硬配额/VPC 端点命令(feature 8)、复制用量账本(feature 9)、控制台页面 |
| 强一致 | sync 桶、多站点([site] + [raft])、3-voter Raft(witness)、能力级别 1..9 |
| 运维 | fsck / heal / scrub / clean / rebalance / cluster-shrink plan/apply/verify/commit/rollback |
| 客户端 | aws-cli / rclone;React 控制台(分块、断线续传、秒传) |
架构
数据流(事件通知 / S3 Select / 桶复制):
多站点异步复制(控制面 changelog + 对象 outbox):
控制面(访问点与 OLAP / 作业与 Batch / Inventory / 分层与 Restore / 控制台页面):
| crate / 目录 | 职责 |
|---|---|
crates/xl-store |
存储内核:盘布局与键校验(__XLDIR__ 展平)、xl.meta 编解码(版本链 / 加密记录 / 锁记录 / 标签 / storage_class / tier)、提交协议、EC 编解码、多盘分发与 quorum、对象/桶锁与 flock、密钥库(kms.rs)与块对齐信封加密(crypto.rs) |
crates/xl-s3 |
s3s 的 S3 trait 实现:DTO 翻译、错误映射、SigV4 认证(AK → SK 查对象控制根 access_keys 热视图)、SSE 与对象锁头解析/响应装配;含访问点路由(ap.rs)、Object Lambda(olap.rs)、Restore/生命周期(restore.rs/lifecycle.rs)、TierGc/删桶清理契约 |
crates/xl-proto |
节点间 gRPC 协议:oss.proto 消息与 service 定义(prost + tonic-build 全量生成) |
crates/xl-client |
mesh 的 tonic 实现:TonicMesh 客户端(懒连接 / 超时 / token 拦截器 + traceparent 注入)与服务端 handler(令牌校验,只操作本节点本地槽) |
crates/oss-server |
ecat + axum 组装:控制台 API(JWT)、aetherupload 接线、S3 fallback、运维 CLI;含作业引擎(jobs.rs)、Batch(job_actions*.rs)、访问点/OLAP(access_points_api.rs)、Inventory(inventory*.rs)、分层/恢复(tier*.rs)与对象控制根装配 |
web |
控制台前端:React 19 + antd + Vite,Playwright e2e;含访问点/作业/分层/清单/通知/审计/设置页面 |
磁盘布局速记(多盘):每个盘各持一份完整命名空间——<盘>/<桶>/<展平键>/ 下是xl.meta(该盘副本)与版本目录 <uuid>/part.1.<shard>(落到本盘的那些分片;单盘形态即全部分片)。
提交永远是「数据先到位 → meta 翻转」:单盘 = 同盘 rename;多盘 = 各盘 meta 两阶段提交
(全盘 xl.meta.new 准备 ≥ quorum 才逐盘转正;读取最新副本胜)。小对象(< 128 KiB)内联进xl.meta、不落数据文件。启用加密后数据文件存的是密文分片(先加密后 EC),
包裹的 DEK 与 key_id 记在 xl.meta 内(元数据本身不含密钥明文)。临时区在每盘.oss.sys/{tmp, trash, locks},盘身份标识 drive-id 防插错盘。
控制面元数据(用户/密钥/桶/上传记录/秒传索引)在节点本地对象控制根(.oss.sys/control/),运行态文件在 .oss.sys/local/;不再使用 SQLite。
技术栈:Rust 2024 · ecat 7.0 · s3s 0.17 · aetherupload-rust 3.0.0 · axum 0.8 · 对象控制根(COW B+tree)· serde + rmp-serde。
请求周期
两条入口(S3 直传、浏览器分块上传)在 xl-store 的提交协议处汇合:
暂存 → 同盘 rename 数据就位 → xl.meta 覆盖即提交;提交之前的一切对客户端不可见。
EC 写/读均已分块流式:内存峰值 ≈ 块缓冲(MiB 级),与对象大小无关(96 MiB 对象
区间读实测 VmHWM 107 MiB)。无 Range 的全对象 GET 直接流式输出(加密对象按 unit 解密,
至多缓存一个 unit);远端 EC 分片按 OSS_SHARD_READ_BATCH_BYTES(默认 1 MiB,上限 16 MiB)
批量范围读取,减少 RPC 次数。Range/partNumber 仍有界缓冲,上限由 OSS_MAX_BUFFERED_GET_BYTES
控制(默认 512 MiB,0=不限),EC 数据盘缺失时的整体重建回退同样受该上限约束。
单次列举最多收集 OSS_LIST_MAX_KEYS 个键(默认 100 万,0=不限),大桶请用 prefix 翻页;
首次列举后每节点按桶缓存键集(OSS_KEY_CACHE_MAX_KEYS/OSS_KEY_CACHE_MAX_TOTAL_KEYS
默认各 200 万,OSS_KEY_CACHE_TTL_SECS 默认 300 秒,0 关闭周期重建;超限回退逐页扫描)。
审计日志默认保留 90 天(OSS_AUDIT_RETENTION_DAYS=0 关闭);过期秒传索引每小时清理。
审计写入经有界异步队列(单 worker 每 100 ms 或满 256 行批量落库);队列满时丢弃该行并累计
丢弃计数(首次及每 1000 条告警一次)——审计是 best-effort,不阻塞也不改变请求结果。jobs/job_tasks 运行态随 Raft 快照进入 m8.runstate(硬上限 64 MiB);终态作业默认
保留 30 天(OSS_JOB_RETENTION_DAYS 修改,0 关闭,上限 3650),每小时由 leader 经
feature level 6 的 JobPurge 命令分批清理,queued/running/preempted 永不删除。
启用 feature 6 前需全集群升级:旧节点 advertise ≤5,effective level 停在 5,JobPurge
在 append 前被拒(单向门,激活后不可回滚)。feature level 7 的恢复后续状态/调度租约命令
同样需全集群升级;旧节点 advertise ≤6,effective 停在 6,相关命令在 append 前被拒。
账户硬配额配置 API 在 standalone 与 Raft(feature level 8)均可用;Raft 模式需全集群先升级到 level 8,旧节点在 append 前被拒。配额执行在限额写入后生效。默认执行仍是各节点本地 account_usage 缓存(跨节点有界陈旧;对端不可达时按已知下界拒绝、未知增量 fail-open);需要不超卖时启用严格模式(feature level 9,见下节)。
严格硬配额(feature 9)
[quota].strict = true(或 OSS_ACCOUNT_QUOTA_STRICT=1)开启 opt-in 严格模式:xl-s3 的 Put/CompleteMultipartUpload/Copy 在写前经 Raft AccountUsageReserve 预留,删除与失败路径用 AccountUsageRelease/AccountUsageAdjust 释放或校准;判定只读复制后的 account_usage_ledger 表,各节点看到同一份用量。非严格模式行为不变。
- 启用门槛:Raft 模式要求
[raft].feature_level = 9(当前MAX_FEATURE_LEVEL),且全 peer 生效 level ≥9 后命令才可 append;leader 对低级别节点在 append 前拒绝。OSSCTL3 快照的ControlData新增account_usage_ledger行;把 level <9 的快照装到本机已有账本行的库会拒绝安装,避免整表替换静默丢用量。 - 保守边界:账本命令非幂等,append 结果不明时写入 fail-closed 且不重试;首次触达某账户时由 leader 本地
account_usage缓存播种(最旧 60s),随后由命令收敛;未知长度 Put 先预留 0 字节、写后校准,并发写可短暂超过配额;版本定向删除与 SSE-C 删除不即时释放,可能持续多记直至人工对账;预留后、数据写前崩溃会留下已计预留。 - 结果:总体保守——不会因账本少记而持续超卖,但会多记/阻塞,直到人工对账;未知长度 Put 的并发窗口是唯一短暂超额例外。
对象生命周期
读路径只信 xl.meta:无有效 meta 的目录永不被服务;崩溃只会留下不可见的残骸
(孤儿版本目录 / trash 残留 / 未完成的 multipart 暂存区)。服务启动时自检清 tmp/trash,oss-server clean <days> 按 mtime 回收其余残骸。
桶级生命周期规则负责过期删除、非当前版本清理、Storage Class 转换与分层 Restore;归档对象需
Restore 后读取。
安装
源码运行
1 | # 1. 起服务(首次启动会自动建数据目录、暂存目录、桶目录与管理员) |
oss.toml默认listen = "127.0.0.1:9000"(仅本机)与占位jwt_secret,仅限本机开发;
若把 listen 改成非回环地址,占位密钥会拒绝启动——对外部署前必须换成随机jwt_secret。
Docker
单机(oss-server + 控制台 nginx):
1 | docker compose up --build # 控制台 http://localhost:8080 · S3/API http://localhost:9000 |
v1.32.0 移除静态
[cluster]/[stretch]拓扑。 旧的docker-compose.cluster.yml与docker/cluster/n{1..4}/oss.toml示例已删除(其配置会被新版本拒绝);多站点/Raft 部署使用[site]+[raft](对象控制根 +<raft.db_path>.d/),见安装文档与割接 runbook。
- 配置:单机
docker/oss.toml。 jwt_secret/[site].token/[raft].cluster_token/ 管理员口令未填时首次启动自动生成,落盘./docker/secrets/generated.env(容器内权限 0600、属主oss,目录 0700);轮换 = 删除
该文件重启。查看口令:docker compose exec oss cat /secrets/generated.env;宿主机上仅 root 可读:sudo cat docker/secrets/generated.env。- 端口默认只绑
127.0.0.1(9000 S3/API、8080 控制台)。需要对外暴露时,去掉 compose 端口
映射里的127.0.0.1:前缀,并自行加 TLS/反向代理与访问控制;容器间仍按服务名oss:9000互通,不受宿主绑定影响。 - 控制台镜像沿用
nginx:alpine默认以 root 主进程监听 :80;如需非 root,需换非特权基础
镜像/端口并同步 compose 与 healthcheck(本次未改)。 - 数据/暂存在具名卷(
docker compose down -v才清);集群每节点独立卷。
控制台
1 | # 1. 起后端(同「源码运行」) |
完整的 TLS、集群、强一致与生产部署说明见安装文档。
使用方法
1 | export AWS_ACCESS_KEY_ID=... |
- 大文件自动走 multipart;Range、
x-amz-meta-*、Storage Class 与对象标签按 S3 语义处理。 - 浏览器控制台覆盖桶、对象、用户、访问密钥、权限、访问点、作业、Inventory、分层与 Raft 集群状态。
- 版本控制、生命周期、加密、对象锁、STS、事件通知、复制、强一致与运维命令详见使用文档。
- 完整 API 与协议边界见 API 文档。
升级与回滚
本版数据面升级按停写蓝绿执行,不做滚动升级:新二进制在原数据目录启动,由运维切换流量;旧二进制回退只在没有新格式写入时成立。
- 升级前跑只读检查:用
scripts/mpu-drain.sh确认并中止在途 MPU,并保存/health/cluster的data_plane_compatible与data_wire_version。 - 停客户端流量并停旧服务,确认没有写入者。
- 冷备每块数据盘与控制状态(
.oss.sys与<raft.db_path>.d/),保留到验收完成。 - 用新二进制在原数据目录启动;检查
/health/ready与/health/cluster:data_plane_compatible: true,且所有 peerdata_wire_version >= 2。 - 切流;备份继续保留。
- 回滚 = 停新二进制、还原冷备、起旧二进制;仅当未发生新格式/CMK 写入时有效,否则按下方 CMK 迁移处理或保持新二进制。
v1.32.0 的 SQLite 移除是单向割接(与上面的数据面蓝绿不是一回事)。 停全部节点(Raft voter/witness 一起),冷备 oss.db、raft.db 与数据盘并记录 sha256;从 GitHub Release v1.31.0 下载归档的 oss-server-v1.31.0-linux-x86_64,核对 sha256 fc2ea96fd0eb1777724da7b9ad07b4ad6520da7373bc9780099d8a211560b1ae;每节点先 --dry-run 后 --apply 执行 control-migrate --from <oss.db> --state-dir <data_dir> --raft-db <raft.db> --json,确认源库字节未变、报告计数/哈希一致、对象根与 <raft.db_path>.d/ 已生成;再启动 v1.32 二进制并做 auth/console/S3/site/snapshot 冒烟。首次新写入后没有反向写入器,回滚只能在首次写入前恢复备份并启动旧/归档二进制;之后只能恢复备份并接受割接后写入丢失。完整步骤见 img/oss/superpowers/runbooks/2026-11-05-object-control-cutover.md。
只读排空(Level C)。 顶层 read_only = true 让节点从客户端写路径摘除:写请求返回 503 并带 x-oss-read-only: 1;GET/HEAD/OPTIONS、精确匹配的 POST /api/v1/login、健康/指标与 Raft RPC 保持可用,/health/cluster 暴露 read_only。旧二进制没有此开关,混版窗口内必须把旧节点从客户端 LB 移除,不能依赖只读开关约束旧节点。
JWT 密钥轮换。 jwt_previous_secrets 接受旧签名密钥数组(每个 ≥32 字节、互不重复且不同于 jwt_secret),多密钥验证共享黑名单与过期检查。轮换顺序:把当前密钥加入 jwt_previous_secrets → 全部节点重启 → 切换 jwt_secret → 全部节点重启 → 12 小时后移除旧密钥。登录限流与审计仍是节点本地,不随轮换复制。
升级工具自检。 scripts/mpu-drain.sh 与 scripts/single-to-cluster.sh 提供离线自检(scripts/tests/mpu-drain-selfcheck.sh、scripts/tests/single-to-cluster-selfcheck.sh)。
CMK 激活是不归点。 DARE 的 AAD 绑定 key_id,只改元数据无法让 pre-CMK 二进制读 CMK 数据;仓库不提供明文/不安全降级。oss-server cmk rewrap 默认只在同一个 CMK 内把旧版本重加密到该 CMK 当前版本;v1.28.0 起加 --to-legacy sse-s3|sse-kms --apply 可把引用该 CMK 的对象逐版本全量重加密为 pre-CMK provider(sse-s3 的 file key,或 sse-kms 默认环当前 k<n>),完成后旧 CMK 引用归零,pre-CMK 二进制可读。迁移在扫描/写入前 fail-closed:仍有在途 MPU、桶默认加密指向该 CMK、或目标 provider 未配置都会被拒绝;每个版本独立重加密,单版本失败不影响其他版本;迁移绝不自动删除 CMK。若已有 CMK 对象而必须回到 pre-CMK 二进制:先停写,逐节点跑 oss-server cmk scan --name <cmk> --json 核对 references/mpu_references/bucket_defaults,Complete/Abort 所有 MPU 并把桶默认加密切到目标 provider,再执行 oss-server cmk rewrap --name <cmk> --to-legacy <sse-s3|sse-kms> --apply;多站点/Raft([site]/[raft])部署的离线 rewrap 会拒绝(分片放置需在线拓扑),当前不支持在线 legacy 迁移。全部节点引用归零后可 oss-server cmk delete --name <cmk> --yes(集群需在全站逐节点核实后加 --force),或迁移到新集群(如 scripts/single-to-cluster.sh 的离线重新灌入,只迁当前版本)。绝不能删除 CMK 来「强制」降级;meta-only 降级仍不可能,迁移完成前回滚只能退回仍能读 CMK 的新二进制。
跨版本 MPU。 跨节点 UploadPart 转发支持 v1.16+(wire 1 有界回退)与本版 wire 2 流式;≤v1.15 没有 MPU RPC,无法支持。升级/降级前必须 Complete/Abort/排空所有在途 MPU,并核对 /health/cluster 的 data_wire_version >= 2 与 data_plane_compatible: true。scripts/mpu-drain.sh 列出/中止在途 MPU(只处理未完成上传,绝不删除已提交对象)。
Raft 日志格式(v1.27 引入,v1.28 加固)。 新版本把 ≥1024 B 的日志 entry 压缩为 z1:,并把连续 ≥256 条压成 raft_log_segment;raft_meta.raft_log_format 记录格式(缺失/1 = 旧格式,2 = 压缩/分段;更高或无法解析的值 fail-closed)。v1.27 建库但尚无标记的库,open 时若发现 z1: 行或分段即补写 2;v1.28 起每次 append 在事务内最多做两段有界压缩(最新连续段 + 一段更旧区间),旧区间不会永久停滞。旧二进制无法读取 2 格式日志,因此新二进制一旦写入就不能回退——升级仍按停写蓝绿执行,回滚只在新格式/CMK 写入前成立。
访问点端点身份。 OSS_AP_ENDPOINT_VERIFIER 支持四种验证器:trusted-proxy 按可信直连网段 + 声明头;attested-proxy 要求可信网段内的终止代理用 HS256 JWT 签署 vpce/vpc/account(x-oss-endpoint-attestation,默认 60 秒有效),需要 OSS_AP_ATTESTATION_SECRET(≥32 字节)与 OSS_AP_TRUSTED_PEER_CIDRS;mtls 把 rustls 握手已验证的客户端叶证书 SHA-256 直接映射到端点,配置 OSS_AP_MTLS_ENDPOINTS=<sha256>=<vpce>[=<vpc>[=<account>]];...;aws-api 用 SigV4 调用 EC2 DescribeNetworkInterfaces/DescribeVpcEndpoints,按直连源 IP 解析 ENI → VPC endpoint(要求 State=available 且唯一命中),结果按源 IP + 60 秒 TTL 缓存,API 错误 fail-closed。aws-api 需 AWS_REGION(或 AWS_DEFAULT_REGION)、AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY(可选 AWS_SESSION_TOKEN、OSS_AP_VPC_ID、OSS_AP_AWS_ACCOUNT_ID),并要求 NLB/网关保留客户端源 IP;否则映射失败并拒绝。验证后仍执行端点注册表 registered/state/VPC/account 检查。真实 AWS PrivateLink 端到端验收未在本环境执行(测试仅用本地 mock 覆盖签名、解析、状态与缓存),上线前需在真实 AWS 账号验收。
© 2026 erik.xyz · 以 Apache-2.0 许可发布
视觉资产授权 / Visual Assets License
本项目的项目宠物、图片、图标、表情(包括 img/oss/pet-expressions/ 等目录下的
SVG / PNG)不是开源资产,版权归作者所有。未经书面许可不得复制、修改、再分发或
用于商业用途;商业使用需取得商业授权,联系 erik@erik.xyz。详见
ASSETS-LICENSE.md。
代码部分仍遵循仓库原有
LICENSE;第三方素材(如明确标注的 Go Gopher 等)仍遵循其原许可证。
项目宠物表情形态 · 吞吞
12 种彩色动态形态:笑 / 欢乐 / 哭 / 沮丧 / 惊恐 / 愤怒 / 撒娇 / 卖萌 / 讨好 / 敲打 / 捂脸 / 吐舌头。每个 SVG 自带表情、情绪特效与整体动效,单张文件见 img/oss/pet-expressions/。

