三套存储系统数据孤岛怎么破?PowerFS协议无关数据层+Filer Raft强一致元数据一次解决

POSIX 文件系统一套、KV 引擎一套、S3 对象存储一套------企业里三套存储系统并存,数据靠搬运互通,元数据三份各自维护,跨协议可见性按分钟计。本文讲清楚 PowerFS 如何用 协议无关的 Needle 数据层 + Filer Raft 强一致元数据层,让 POSIX / KV / S3 三种协议共享同一份元数据、同一池数据,从根上消除数据孤岛。

一、引言:数据孤岛是怎么产生的

企业存储的典型现状是「按协议买系统」:

  • 应用要 open/read/write?→ 买一套 POSIX 文件系统(Lustre / GPFS / NFS)。
  • AI 训练要读数据集?→ 买一套 S3 对象存储。
  • 低延迟模型参数?→ 自研一套 KV 引擎。

三套系统各自维护一份元数据,结果就是:

痛点 表现
元数据割裂 同一份数据在三套系统里有三个名字、三套属性
数据搬运 POSIX 写完要 sync 到 S3,S3 上传完要回灌 KV
跨协议延迟 分钟级最终一致,AI 训练读到旧数据
运维爆炸 三套监控、三套扩缩容、三套故障排查

根因只有一个:没有一个统一的、强一致的元数据层把三种协议拢到一起。PowerFS 的解法是 Filer Raft------一份元数据,三种协议共享。

二、PowerFS 四组件架构:协议无关的数据底座

PowerFS 把存储拆成四个解耦组件,元数据与数据彻底分层:

复制代码
┌──────────────────────────────────────────────────────────────────┐
│                          客户端层(三种协议入口)                    │
│   ┌────────────┐   ┌────────────┐   ┌────────────┐              │
│   │  POSIX/FUSE │   │  S3 Gateway │   │  KV Client  │             │
│   │ powerfs-fuse│   │ powerfs-s3  │   │ powerfs-cli │             │
│   └─────┬──────┘   └─────┬──────┘   └─────┬──────┘              │
│         │ MetadataClient   │ S3Handler      │ KvCacheProvider      │
└─────────┼─────────────────┼────────────────┼─────────────────────┘
          │                 │                │
┌─────────▼─────────────────▼────────────────▼─────────────────────┐
│        Filer 层(Raft 强一致,统一元数据------协议无关的核心)          │
│   ┌──────────────────────────────────────────────────────────┐   │
│   │  按 bucket 分片,每 bucket 独立 Raft 组(3 节点 quorum)     │   │
│   │  POSIX/KV/S3 三协议共享同一份 ShardCommand + RocksDB        │   │
│   │  - 写:Raft propose → quorum 复制 → apply                   │   │
│   │  - 读:Leader Lease Read(任期内本地读,无 read index)       │   │
│   │  - Invalidate 推送:跨协议缓存失效                           │   │
│   └────────────────────────┬─────────────────────────────────┘   │
└────────────────────────────┼─────────────────────────────────────┘
                             │
┌────────────────────────────▼─────────────────────────────────────┐
│        Master 层(Raft 调度,不碰文件系统元数据)                    │
│   VolumeAssigner / ClusterScheduler / file_key 块预分配            │
└────────────────────────────┬─────────────────────────────────────┘
                             │
┌────────────────────────────▼─────────────────────────────────────┐
│        Volume 层(Needle 协议无关数据存储------三种协议同一池)         │
│   Needle O(1) 寻址 / Stripe 64MB Lease / EC 纠删码                │
└──────────────────────────────────────────────────────────────────┘

四组件的核心分工:

  1. Master :Raft 集群,只做 Volume 分配、拓扑、心跳,不管理文件系统元数据(已删除 DirectoryTree 死代码)。
  2. Filer :Raft 集群,按 bucket 分片,统一管理所有元数据,是三协议归一的支点。
  3. Volume Server:Needle 格式数据存储,Stripe Lease 强一致,三种协议的数据落在同一池。
  4. POSIX客户端(FUSE/KERNEL)/ S3 / KV 客户端:通过统一 trait 调 Filer,自身只做协议适配与缓存。其中 POSIX 客户端分两种形态:FUSE 为用户态客户端,部署灵活,适合开发/测试/普通生产环境;KERNEL 为内核客户端,跳过 FUSE 内核→用户态切换,更低延迟,适合 HPC 极致性能场景。

三、Filer Raft:一份元数据,三协议共享

1. 为什么不是 CRDT

PowerFS 早期版本用过 OR-Set CRDT 弱一致元数据,试图靠 CRDT 自动收敛来「免去协调」。实践证明这条路在存储系统里是错的:

  • CRDT 只能最终一致,open() 后可能读到旧的 size/chunks,导致跨客户端数据损坏。
  • delta sync、.conflicts/ 目录、三级合并把代码复杂度推到不可维护。
  • 弱一致模型无法支撑 POSIX fsync 后立即可见的硬性要求。

V3 已经完全删除 OR-Set CRDT、delta sync、.conflicts/、三级合并等所有弱一致代码,转向 Filer Raft 强一致。代价是写多一次 Raft RTT,换来的是线性一致、跨协议立即可见------这正是消除数据孤岛的关键。

2. 按 bucket 分片,每 bucket 独立 Raft 组

Filer 不是「一个大 Raft」覆盖所有元数据,而是按 bucket(顶级目录)分片,每个 bucket 跑一个独立的 Raft 组:

复制代码
bucket: /hpc-ckpt   →  Raft Group 1(filer-1, filer-2, filer-3)
bucket: /ai-dataset  →  Raft Group 2(filer-1, filer-2, filer-3)
bucket: /kv-cache    →  Raft Group 3(filer-1, filer-2, filer-3)

好处:

  • 水平扩展:bucket 之间互不阻塞,元数据 OPS 随 bucket 数线性增长。
  • 故障隔离:一个 Raft 组的 leader 切换不影响其他 bucket。
  • 跨 shard 边界返回 EXDEV:类似 Linux mount boundary,语义清晰,避免跨组分布式事务的复杂性。

3. 统一元数据接口:MetadataClient trait

无论客户端是 POSIX、S3 还是 KV,最终都通过 MetadataClient trait 访问 Filer(来自 powerfs-fuse-core/src/metadata_client.rs):

rust 复制代码
/// 强一致元数据操作接口。
/// - 写操作:Leader 提交 Raft log 后返回
/// - 读操作:Leader Lease Read(不经 read index)
/// 调用方负责传入正确的 shard_id(按 bucket 分片)。
pub trait MetadataClient: Send + Sync {
    fn lookup(&self, parent_ino: u64, name: &str, shard_id: u64)
        -> Pin<Box<dyn Future<Output = Result<MetadataAttr>> + Send + '_>>;

    fn mkdir(&self, parent_ino: u64, name: &str, mode: u32,
             uid: u32, gid: u32, shard_id: u64)
        -> Pin<Box<dyn Future<Output = Result<MetadataAttr>> + Send + '_>>;

    fn create(&self, parent_ino: u64, name: &str, mode: u32,
              uid: u32, gid: u32, shard_id: u64,
              fid_info: Option<(u64, u64, u64)>)
        -> Pin<Box<dyn Future<Output = Result<MetadataAttr>> + Send + '_>>;

    fn rename(&self, parent_ino: u64, name: &str,
              new_parent_ino: u64, new_name: &str, shard_id: u64)
        -> Pin<Box<dyn Future<Output = Result<MetadataAttr>> + Send + '_>>;

    fn readdir(&self, ino: u64, offset: u64, count: u32, shard_id: u64)
        -> Pin<Box<dyn Future<Output = Result<Vec<MetadataDirEntry>>> + Send + '_>>;

    fn getattr(&self, ino: u64, shard_id: u64)
        -> Pin<Box<dyn Future<Output = Result<MetadataAttr>> + Send + '_>>;

    fn setattr(&self, ino: u64, params: &SetattrParams, shard_id: u64)
        -> Pin<Box<dyn Future<Output = Result<MetadataAttr>> + Send + '_>>;

    // ... unlink / rmdir / symlink / readlink / link / statfs
}

这个 trait 是协议无关的:POSIX客户端(FUSE/KERNEL)、S3 Gateway、KV 引擎都依赖它,因此三种协议看到的是同一份强一致元数据视图

4. 写路径:Raft propose → apply

Filer 端一次 create 的完整路径(来自 powerfs-filer/src/meta_shard_manager.rs):

rust 复制代码
pub async fn create_file_with_shard(
    &self,
    parent_inode: u64,
    name: &str,
    shard_id: ShardId,
) -> Result<u64, String> {
    let shard_store = self.shard_stores.read().unwrap()
        .get(&shard_id)
        .ok_or_else(|| format!("shard {} not found", shard_id.0))?
        .clone();

    let inode = self.generate_inode();

    let cmd = ShardCommand::CreateFile {
        parent_inode,
        name: name.to_string(),
        inode,
    };

    // 提交 Raft:leader 复制到 quorum 后才返回
    self.raft_group_manager
        .propose(shard_id, cmd.serialize())
        .await?;

    // 轮询本地 apply(apply 在 commit 之后,写入 RocksDB)
    let mut retries = 0;
    while retries < 50 {
        if shard_store.get_inode(inode).is_some() {
            return Ok(inode);
        }
        tokio::time::sleep(Duration::from_millis(10)).await;
        retries += 1;
    }
    Err("timeout waiting for apply".to_string())
}

ShardCommand 覆盖全部元数据变更(来自 powerfs-filer/src/raft_group_manager.rs),其中 UpdateInodeSizeChunks 是跨协议可见性的关键------close() 时把 size + chunks 走 Raft 强一致同步,S3 和 FUSE 立刻能读到最新数据:

rust 复制代码
pub enum ShardCommand {
    CreateFile { parent_inode: u64, name: String, inode: u64 },
    CreateDirectory { parent_inode: u64, name: String, inode: u64 },
    Rename { old_parent_inode: u64, old_name: String,
             new_parent_inode: u64, new_name: String },
    PutObject { parent_inode: u64, name: String, inode: u64, size: u64,
                fid: String, volume_id: u64, etag: String },
    UpdateInodeSizeChunks {
        inode: u64,
        size: u64,
        chunks: Vec<StoredFileChunk>,
    },
    SetAttr { inode: u64, size: Option<u64>, mode: Option<u64>,
              uid: Option<u64>, gid: Option<u64> },
    CreateSymlink { parent_inode: u64, name: String, inode: u64, target: String },
    // ...
}

5. 读路径:Leader Lease Read

读操作不走 read index,而是利用 Leader Lease Read:leader 在自己任期内(已被 quorum 授予 lease)直接本地读,省掉一次 RTT。这保证三种协议读到的都是线性一致视图,不会出现「S3 刚上传、FUSE 读不到」的孤岛现象。

Raft 配置采用生产级参数(来自 RaftGroup::new):

rust 复制代码
let mut cfg = Config {
    id,
    election_tick: 30,      // tick 50ms × 30 = [1.5s, 3s) 选举超时
    heartbeat_tick: 5,
    max_size_per_msg: 1 << 20,
    max_inflight_msgs: 256,
    check_quorum: !peers.is_empty(),  // 防旧 leader 脑裂
    pre_vote: true,                    // 防多节点同时选举 split vote
    ..Default::default()
};

四、协议无关的数据层:Needle O(1) 寻址

三种协议的数据都落在同一池 Volume Server 上,靠 Needle 格式实现协议无关的 O(1) 寻址:

  • file_key:文件级标识,由 Master 按 1000/batch 走 Raft 预分配。
  • needle_id = file_key + chunk_idx:块级寻址,无需扫描。
  • Volume 路由 :Master 维护 volume_id → VolumeServer 路由表,三种协议共用。
  • Stripe Lease (64MB):数据层强一致锁,写者独占,与协议无关。

FileChunk 结构(来自 powerfs-common/src/traits.rs)就是协议无关的数据定位凭证:

rust 复制代码
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileChunk {
    pub offset: u64,
    pub size: u64,
    /// 块级存储键(volume server 上的 needle_id)
    /// file_key 是文件级;每个 chunk 的 needle_id = file_key + chunk_idx
    pub needle_id: u64,
    /// 该 chunk 所在的 volume(支持 stripe 模式)
    pub volume_id: u64,
    pub crc32: u32,
    pub mtime: u64,
}

无论是 FUSE read、S3 GetObject 还是 KV get_block,最终都解析成 (volume_id, needle_id, offset, size) 去 Volume Server 取数据。同一份数据,三种协议都能读,无需搬运。

五、三协议如何共享同一份元数据

协议 二进制 元数据入口 数据访问
POSIX/FUSE powerfs-fuse MetadataClient trait → Filer RPC Volume read_blob/write_blob
对象存储 powerfs-s3 Master LockManager + Filer 元数据 Volume Needle
KV powerfs-cli kv KvCacheProvider → Filer KV Cache Volume block

关键点:

  • S3 上传立即可见PutObject 是一条 ShardCommand,Raft commit 后 FUSE 端 readdir 立刻看到,没有跨协议延迟。
  • FUSE 写完 S3 可读close()UpdateInodeSizeChunks,size+chunks 全网强一致,S3 GetObject 立即读到。
  • Invalidate 跨协议失效:Filer 元数据变更时,对所有订阅的客户端(无论协议)推送 Invalidate,缓存 100ms 内失效。

六、实际部署:四组件一条命令启动

每个组件独立二进制,统一 -c <config> 配置:

bash 复制代码
# Master(Raft 调度,3 节点)
powerfs-master -c config/master-1.toml
powerfs-master -c config/master-2.toml
powerfs-master -c config/master-3.toml

# Filer(Raft 元数据核心,3 节点,按 bucket 分片)
powerfs-filer -c config/filer-1.toml
powerfs-filer -c config/filer-2.toml
powerfs-filer -c config/filer-3.toml

# Volume Server(Needle 数据池,可按需扩容)
powerfs-volume -c config/volume-1.toml
powerfs-volume -c config/volume-2.toml
powerfs-volume -c config/volume-3.toml

# 三种协议入口
powerfs-fuse -c config/fuse-1.toml     # POSIX 挂载
powerfs-s3   -c config/s3.toml         # S3 Gateway
powerfs-cli  kv put mykey value         # KV 访问

扩容时按瓶颈加节点:元数据瓶颈加 Filer,数据瓶颈加 Volume Server,三协议共享,无需为某个协议单独扩容。

七、对比:三套堆叠 vs PowerFS 一套

维度 三套堆叠方案 PowerFS 协议无关
元数据份数 3 份(POSIX/KV/S3 各一) 1 份(Filer Raft)
跨协议可见性 分钟级最终一致 秒级(Raft commit + Invalidate)
数据搬运 必须(sync 工具链) 无需(同一 Needle 池)
一致性模型 各家不一 Filer Raft 强一致 + Volume Lease
元数据扩展 单点瓶颈 按 bucket 分片水平扩展
运维 3 套监控 1 套(powerfs-monitor

八、场景案例

某 AI 平台同时对接三类业务:

  • 训练平台 :用 powerfs-s3 上传训练数据集,元数据走 Filer。
  • 推理服务 :用 powerfs-fuse 挂载 POSIX 读取同一批数据集,open() 强制 getattr 拉最新 size,上传即可读
  • 特征平台 :用 powerfs-cli kv 写模型参数,KvCacheProvider 背后仍是 Filer Raft,参数写入对 POSIX 端立刻可见。
  • 故障切换 :某 Filer 节点宕机,Raft 1.5-3s 选出新 leader,pre_vote 防脑裂,三协议元数据零丢失。

三种协议,一份元数据,一个数据池------数据孤岛从架构层面被消灭。

九、保留的设计与未来演进

转向 Filer Raft 强一致的同时,PowerFS 保留了核心设计:

  • Needle 格式 O(1) 寻址------不变,协议无关的数据底座。
  • SPDK NVMe / RDMA / GPU Direct 硬件加速 ------不变,Transport trait 已为 RDMA 预留 BatchTransport
  • Rust 原生无 GC 无抖动------不变。
  • Collection 多租户隔离------不变。
  • EC 纠删码数据冗余------不变。

协议无关的数据层 + Filer Raft 强一致元数据层,让 PowerFS 用一套系统承载 POSIX / KV / S3 三种协议,彻底终结数据孤岛。


项目地址:https://github.com/powerfs/powerfs

欢迎 Star、Fork、提交 PR!

相关推荐
分布式存储与RustFS5 天前
对象存储正在吞掉数据湖:S3 Tables 把 Iceberg 收编成存储的原生能力
iceberg·对象存储·s3·分布式对象存储·rustfs·minio平替·s3 tables
分布式存储与RustFS6 天前
对象存储账单为什么比预期高 3 倍?S3 的 5 个隐性成本坑,以及怎么堵?
对象存储·分布式对象存储·rustfs·ai基础设施·ai存储·minio平替·开源对象存储
seacracker19 天前
HPC存储I/O抖动无解?PowerFS基于CRDT弱一致架构,彻底实现前台零抖动
架构·分布式存储·powerfs·hpc存储·i/o零抖动
seacracker1 个月前
Ceph 太重运维成本高?轻量统一存储 PowerFS 对比测评(HPC/AI 场景首选)
运维·人工智能·ceph·ai存储·统一存储
分布式存储与RustFS2 个月前
Apache Iceberg数据湖轻量化搭建:基于Rust开源存储方案
开源·apache·iceberg·rustfs·ai存储·ai memory·s3 table
分布式存储与RustFS2 个月前
RustFS S3 Table 开源后,我重新梳理了一下 Iceberg 数据湖的选型思路
人工智能·开源·minio·dpu·rustfs·ai存储·s3 table
johnny2332 个月前
S3命令行工具:rclone、s3cmd、s5cmd、mc、S3Copy
s3
亚林瓜子2 个月前
AWS S3日志桶常用过期文件生命周期策略
云计算·生命周期·aws·s3·过期·glacier
大数据在线3 个月前
千亿企业级存储市场,产品逻辑变了
人工智能·浪潮信息·智能体·ai存储·a9000