# doss **Repository Path**: wanyuxiang000/doss ## Basic Information - **Project Name**: doss - **Description**: mgface.com-使用go语言来实现分布式对象存储 - **Primary Language**: Go - **License**: Apache-2.0 - **Default Branch**: preview-20260819 - **Homepage**: https://www.mgface.com - **GVP Project**: No ## Statistics - **Stars**: 15 - **Forks**: 2 - **Created**: 2019-07-11 - **Last Updated**: 2026-08-19 ## Categories & Tags **Categories**: distributed-storage **Tags**: None ## README # 分布式对象存储 (DOSS) 基于 Go 的高可用对象存储。支持 Windows / Linux 本地部署,也可在 Kubernetes 中运行。 系统由三类独立进程组成:**Gateway**(S3 API 网关)、**OSD**(对象存储守护进程)、**Meta**(Raft 元数据)。客户端经 Ingress 或直连访问 Gateway;Gateway 将对象元数据写入 Meta,将纠删码分片写入 OSD。 ```mermaid flowchart TB Client["Client / SDK"] Ing["Ingress · TLS"] GW["Gateway × N
S3 子集 · 纠删码 · 2PC"] subgraph Meta["Meta · Raft"] Leader["leader
metadata / filecrc"] Follower["follower"] Leader <-->|"复制"| Follower end subgraph OSD["OSD × N"] D1["分片 + 本地盘"] D2["分片 + 本地盘"] D3["分片 + 本地盘"] end Client -->|"PUT/GET /objects/{name}
S3 /{bucket}/{key}"| Ing --> GW GW -->|"TCP"| Leader GW -->|"Reed-Solomon 分片"| D1 GW --> D2 GW --> D3 ``` --- ## 功能 | 能力 | 说明 | |------|------| | HTTP 对象上传 / 下载 / 删除 / HEAD | `PUT` / `GET` / `DELETE` / `HEAD /objects/{name}` | | 对象定位 | `GET /locate/{name}` | | 多版本 | 同名对象再次上传会递增 `version` | | 内容去重 | 按整文件 SHA256 查询 `filecrc`,命中则只增加引用计数 | | 纠删码读写 | 默认 2 数据片 + 1 校验片;写路径流式编码,读路径校验分片 hash 后 `Reconstruct` | | 两阶段写入 | OSD `/temp/{uuid}`:POST 创建、PATCH 追加、PUT 提交、失败 Abort | | DELETE + 引用计数 GC | `refcount` 降到 0 时删除分片 | | Meta 选主与复制 | HashiCorp Raft;持久化 key(`metadata` / `filecrc` / `buckets`)走 Raft | | 容量感知放置 | 故障域(zone/rack/host)尽量不重叠 + 容量加权伪随机 | | 后台修复 / 再平衡 | 失联迁片;过载节点限速再平衡;`doss_repair_backlog` | | 版本生命周期 | `DOSS_VERSION_KEEP` / `DOSS_VERSION_TTL` 回收非当前版本 | | S3 子集 | 路径风格列桶 / 列对象(Meta 有序分页)/ CRUD / HEAD / multipart / CopyObject;可选 virtual-host;预签名 URL | | 鉴权 | AccessKey 头或 AWS SigV4(含预签名);生产未配密钥/TLS 拒绝启动,可用 `--dev` | | TLS | Gateway `--tls-cert` / `--tls-key`;节点间 `CLUSTER_TLS_CERT` / `CLUSTER_TLS_KEY` | | 审计 / 配额 | JSON 审计(可选落盘);单对象大小、桶容量、按密钥 QPS | | 加密 | SSE-S3(`DOSS_SSE_KEY`)/ SSE-C;生产强制对外 TLS | | 健康检查 / 指标 | `/healthz` `/ready` `/metrics`(延迟直方图、磁盘、修复积压、Raft) | | 运维接口 | `/admin/backup` `/restore` `/inventory` `/decommission` `/status`;`/console` | | 配置文件 | `--config` JSON,命令行 / 环境变量优先 | --- ## 模块划分 仓库是多 module 结构,三类节点各自独立编译。产品名与 Go module 前缀均为 **doss**(`doss/gateway`、`doss/osd`、`doss/meta`、`doss/pkg`)。目录名按对象存储行业习惯(Ceph RGW / OSD、MinIO Gateway、JuiceFS Meta)。 | 角色 | 源码目录 | 默认端口 | 协议 | 职责 | |------|----------|----------|------|------| | S3 网关 | `gateway/` | 6000 | HTTP | 对象读写删除、纠删码、去重、选 OSD、S3 子集 | | 对象存储守护进程 | `osd/` | 5000 | HTTP | 分片落盘、2PC 暂存、本地巡检修复 | | 元数据 | `meta/` | 3000(业务)/ 10000(Raft)/ 3090(health) | 自定义 TCP + HTTP 探活 | 对象元数据、节点注册表、Raft 复制 | | 共享库 | `pkg/` | — | — | 按职责分子包:`model` / `rpc` / `place` / `httpx` / `fs` / `config` / `k8s` | | 部署 | `deploy/` | — | — | Kustomize 安装、PDB、NetworkPolicy;入口在 `deploy/ingress/` | | 文档 | `docs/` | — | — | 架构、安装、API、安全、运维 | Kubernetes 对象名与源码目录一致:Deployment/Service `gateway`,StatefulSet `osd` / `meta`(`meta-0` bootstrap)。心跳表 key 为 `gateways` / `osds` / `metas`。镜像 `wanyuxiang000/{gateway,osd,meta}:1.0.0`。 Go 版本:各 module 为 1.19。纠删码使用 [klauspost/reedsolomon](https://github.com/klauspost/reedsolomon),Meta 共识使用 [hashicorp/raft](https://github.com/hashicorp/raft)。 --- ## 三类节点 ### Gateway 启动后会:发现 Meta 的 Raft leader、以 `gateways` 向 leader 报心跳、刷新 `osds` 列表。 **上传 `PUT /objects/{name}`** 1. 请求体先落到临时文件,同时计算 SHA256(避免一开始就把整包只放在内存里)。 2. 向 Meta 查询 `filecrc[hash]`。已存在则 `incr` 引用计数。 3. 新内容按块 Reed-Solomon 拆分并流式算出校验片(`--data-shards` / `--parity-shards`,默认 2+1),每片 gzip 后按故障域 + 剩余容量选择互不相同的 OSD。 4. 对各节点**并行** 2PC:`POST /temp/{uuid}` → `PATCH` 写数据 → `PUT` 提交。任一失败则 Abort 全部,元数据提交失败则删除已提交分片。 5. 写入完整 `filecrc` 与 `metadata`(对象名、hash、大小、版本、owner、bucket)。 **下载 `GET /objects/{name}`** 1. 按对象名取 `metadata`,选用 `version` 最大的记录。 2. 用其 hash 取 `filecrc`,拉取全部分片并校验 gzip 前的 SHA256。 3. 损坏或缺失的片置空,用校验片 `Reconstruct`,再 `Join` 截到原始大小后返回。 **删除 `DELETE /objects/{name}`** 删除该对象的 metadata,对内容 hash 做 `decr`;引用计数为 0 时删除各 OSD 上的分片。 ### OSD 分片按「日期 + 节点」滚动目录存放: ```text {data-dir}/{yyyyMMdd}-{ip-port}/{objName}-{nano} ``` PUT / 2PC 提交后返回 `ShardCommit` JSON(节点、相对路径、分片 hash、下标)。`filecrc` 由 Gateway 在全部分片提交成功后统一写入 Meta。后台对照 `filecrc` 检查本地片,损坏时从其它节点拉片重建。 ### Meta 内存 `map` 按 key 分类: | Key | 含义 | 复制 | |-----|------|------| | `osds` / `gateways` / `metas` | 心跳注册表(带时间戳,Get 按 TTL 过滤) | Raft(快照不含心跳) | | `metadata` | `bucket/object` → 版本列表(有序索引供 List) | Raft | | `filecrc` | 内容 hash → 分片位置、分片 hash、引用计数 | Raft | | `buckets` | 桶列表 | Raft | | `decommission` | 排空中的 OSD 地址 | Raft | 自定义 TCP 协议(单字节操作码,连接可复用): | 操作码 | 含义 | 报文 | |--------|------|------| | `S` | Set | `S{klen} {vlen} {key}{value}` | | `G` | Get | 同上结构 | | `D` | Del | 同上 | | `J` | Raft AddVoter | `J{idlen} {addrlen} {id}{raftAddr}` | | `F` | 传输快照文件 | `F{filename} {size} {bytes}` | | `X` | 全量同步请求 | gzip + base64 的 JSON | 成功响应 `{len} {json}`,失败 `-{len} {err}`。 Raft:ServerID 为业务地址 `--listen`,传输绑定 `--raft-addr`(默认 `:10000`)。单节点或 StatefulSet `*-0` 负责 bootstrap;其它节点向已有节点的业务 TCP 发 `J` 加入。日志在 `{data-dir}/raft/raft.db`。 --- ## 对象读写路径 新文件 `PUT /objects/foo`: ```text 1. Gateway 落临时文件,计算 hash H 2. GET filecrc H → 空 3. 流式 RS 拆分 + 编码 → shard0, shard1, parity0 4. 按故障域 + 剩余容量选 3 个 OSD,并行 2PC 提交 5. SET filecrc {H → {shards, refcount=1}} 6. SET metadata {default/foo → {hash:H, version:1, size, owner}} ``` 下载 `GET /objects/foo`: ```text 1. GET metadata foo → 取最高 version 2. GET filecrc H → 按 index 排序得到分片位置 3. 拉全部分片,校验 hash,Reconstruct 后 Join 返回 ``` 同内容再传(hash 相同)只增加引用计数并追加一条 metadata。同名不同内容会写新分片并递增 version。 --- ## S3 子集与鉴权 路径风格(未匹配 `/objects/`、`/locate/`、`/healthz` 等前缀时): | 方法 | 路径 | 说明 | |------|------|------| | `GET` | `/` | 列桶 | | `PUT` | `/{bucket}` | 创建桶 | | `DELETE` | `/{bucket}` | 删除空桶(非空 409) | | `GET` | `/{bucket}?prefix=&marker=&max-keys=` | 列对象(分页,`IsTruncated` / `NextMarker`) | | `PUT` / `GET` / `DELETE` / `HEAD` | `/{bucket}/{key}` | 对象 CRUD | | `PUT` | `/{bucket}/{key}` + `x-amz-copy-source` | CopyObject | | `POST` | `/{bucket}/{key}?uploads` | CreateMultipartUpload | | `PUT` | `/{bucket}/{key}?partNumber=&uploadId=` | UploadPart | | `POST` | `/{bucket}/{key}?uploadId=` | CompleteMultipartUpload | | `DELETE` | `/{bucket}/{key}?uploadId=` | AbortMultipartUpload | | `GET` | `/{bucket}/{key}?uploadId=` | ListParts | 未指定桶时内部使用 `default`。设置 `DOSS_S3_DOMAIN` 后可用 virtual-host:`{bucket}.{domain}/{key}`。非空桶 `DELETE /{bucket}` 返回 409 `BucketNotEmpty`。 鉴权:未配置密钥时关闭(开发模式)。配置后支持: - 请求头 `X-Access-Key` + `X-Access-Secret` - `Authorization: AWS4-HMAC-SHA256 Credential=///s3/aws4_request, SignedHeaders=..., Signature=...`(canonical request + signing key) - 查询串预签名:`X-Amz-Algorithm=AWS4-HMAC-SHA256` 等;服务端可用 `PresignURL` 生成 载荷哈希默认 `UNSIGNED-PAYLOAD`(不把整个 body 读进验签)。`/healthz` `/ready` `/metrics` `/console` 始终放行。 生产:未同时配置 AccessKey 与 Gateway TLS 时拒绝启动。本地演示可不配密钥/证书(隐式开发模式并打警告),或显式 `-dev` / `DOSS_DEV_MODE=1`。强制生产检查:`DOSS_DEV_MODE=0`。 节点间 TLS(可选):`CLUSTER_TLS_CERT`、`CLUSTER_TLS_KEY`、`CLUSTER_TLS_CA`,无 CA 时可 `CLUSTER_TLS_INSECURE=1`。 其它环境变量:`DOSS_AUDIT_LOG`、`DOSS_MAX_OBJECT_BYTES`(默认 5GiB)、`DOSS_BUCKET_QUOTA_BYTES`、`DOSS_QPS`、`DOSS_SSE_KEY`、`DOSS_S3_DOMAIN`、`DOSS_HEARTBEAT_INTERVAL`、`DOSS_HEARTBEAT_TTL`、`DOSS_VERSION_KEEP`、`DOSS_VERSION_TTL`、`DOSS_REBALANCE_PER_ROUND`、`DOSS_ZONE` / `DOSS_RACK` / `DOSS_HOST`。 JSON 配置示例见 [docs/doss.example.json](docs/doss.example.json),启动加 `--config`。备份演练 [docs/backup.md](docs/backup.md),POSIX 映射 [docs/vfs.md](docs/vfs.md)。控制台:`http://127.0.0.1:6000/console`。 --- ## 本地开发 按 Meta → OSD → Gateway 顺序启动。纠删码默认 2+1,因此 OSD 至少要有 3 个。 单节点 Meta 把 `--join` 指到自己即可 bootstrap;第二台 Meta 的 `--join` 填已有节点**业务 TCP**(如 `127.0.0.1:3000`),`--raft-addr` 为本机 Raft 传输地址。 ```bash # 1. Meta cd meta go run meta.go start \ --listen 127.0.0.1:3000 \ --raft-addr 127.0.0.1:10000 \ --join 127.0.0.1:3000 \ --data-dir /path/to/metadata # 2. OSD(示例一个;实际请再启两个不同 --listen / --data-dir) cd osd go run osd.go start \ --listen 127.0.0.1:5000 \ --meta 127.0.0.1:3000 \ --data-dir /path/to/objects # 3. Gateway cd gateway go run gateway.go start \ --listen 127.0.0.1:6000 \ --meta 127.0.0.1:3000 \ --data-shards 2 \ --parity-shards 1 ``` 常用参数: | 节点 | 参数 | 含义 | |------|------|------| | 共用 | `--listen, -l` | 本节点监听地址 | | 共用 | `--config, -c` | JSON 配置(只填空缺,命令行/环境变量优先) | | Gateway / OSD | `--meta, -m` | 任意可达的 Meta 业务地址 | | Gateway | `--data-shards` / `--parity-shards` | 数据片 / 校验片 | | Gateway | `--access-key` / `--secret-key` | AccessKey / SecretKey | | Gateway | `--tls-cert` / `--tls-key` | 对外 HTTPS 证书 | | Gateway | `--dev` | 开发模式,允许无密钥无 TLS | | OSD / Meta | `--data-dir, -d` | 数据或元数据目录 | | Meta | `--join` | 已有节点业务地址,bootstrap 时填自己 | | Meta | `--raft-addr` | 本节点 Raft 传输地址 | | Meta | `--health-addr` | 健康检查 HTTP 地址,默认 `:3090` | K8s 环境不传 CLI,改为读取 `POD_NAMESPACE`、`POD_NAME`、`LISTEN` / `LISTEN_PORT`、`META` / `META_PORT`、`JOIN`、`DATA_DIR`、`DOSS_ACCESS_KEY` 等环境变量。 示例: ```bash curl -X PUT --data-binary @./demo.bin http://127.0.0.1:6000/objects/demo.bin curl -O http://127.0.0.1:6000/objects/demo.bin curl -I http://127.0.0.1:6000/objects/demo.bin curl -X DELETE http://127.0.0.1:6000/objects/demo.bin curl http://127.0.0.1:6000/locate/demo.bin curl http://127.0.0.1:6000/healthz curl http://127.0.0.1:6000/admin/status curl -X PUT --data-binary @./demo.bin http://127.0.0.1:6000/mybucket/demo.bin ``` --- ## Kubernetes 部署 面向 Kubernetes **1.21+**(建议 1.25+)。默认命名空间 `doss`。Meta / OSD 为 StatefulSet + PVC,Gateway 为 Deployment。安装入口是 `deploy/`(Kustomize)。 ```bash cd deploy ./install.sh # 实验室:NodePort + Ingress ./install.sh production # 生产:ClusterIP + NetworkPolicy + TLS Ingress ./uninstall.sh # 默认保留 PVC ``` 说明与配置项:[docs/install.md](docs/install.md)、[deploy/README.md](deploy/README.md)、[deploy/ingress/README.md](deploy/ingress/README.md)。 注意: - Gateway 的 `META` 必须指向 `meta`。 - OSD `mountPath` 与 `DATA_DIR` 均为 `/osd`。PVC `storageClassName: standard`,镜像 tag `1.0.0`。 - Meta `meta-0` bootstrap;其它副本经业务端口 `3000` join,Raft `10000`。 - 探活:Gateway/OSD `:6000/:5000` 的 `/healthz` `/ready`;Meta `:3090`。 - 只把 Gateway 接到 Ingress;不要对公网暴露 OSD / Meta。 - 密钥复制 `deploy/config/secret.example.yaml` → `secret.yaml`(已 gitignore)。 --- ## 已知限制 - GET 重建成功后整对象仍在 Gateway 内存中再写出;SSE 也是重建后再解密。 - S3 覆盖路径风格子集(含 multipart、List 分页、Copy、预签名、virtual-host);SigV4 默认 `UNSIGNED-PAYLOAD`。multipart 会话存在发起该上传的 Gateway 进程本地,多 Gateway 副本不共享。 - 节点心跳进 Raft,但快照不含节点表,靠 TTL 与后续心跳回填。 - 默认 2+1 且只有 3 个 OSD 时,挂掉 1 个无法把失联片迁到「不持有其它片」的空闲节点。 - Meta 有序索引加速 List,但各 Meta 仍复制同一份 map,不是跨节点分片。 - `/console` 与 `FileOwner=AccessKey` 不是完整 IAM;POSIX 见文档映射,无 FUSE。 完整能力与明确不做的范围见 [docs/features.md](docs/features.md)。生产差距与升级顺序见 [docs/enterprise-gap.md](docs/enterprise-gap.md)。文档索引:[docs/README.md](docs/README.md)。 --- ## 许可 [Apache License 2.0](LICENSE)