# 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)