
简介面向高性能计算场景的分布式对象存储系统研究文献适合分布式系统研究者、存储工程师及高校相关专业学生阅读。该文针对通用分布式文件系统数据组织低效、访问语义冗余的问题提出面向HPC的分布式对象存储系统COSS详细介绍数据访问与数据管理分离、分布式全局对象数据组织、基于内存的元数据管理等设计要点并给出与Lustre系统的对比实验读/写聚合带宽分别提升22.5%和50.4%文件创建与删除性能达2.15倍和5.13倍。论文发表于《计算机工程》2017年第43卷第8期作者单位为江南计算技术研究所。资源为1个PDF文件约350KB内容完整便于下载后直接引用或精读。已有112人学习适合作为分布式存储方向的参考文献也可为相关课题设计提供思路参考。1. 一篇 HPC 存储论文的实战价值分布式对象存储系统 COSS 凭什么把元数据性能做到 Lustre 的 5 倍给超算搭过存储的人大概都见过这个画面Lustre 客户端一多聚合带宽不升反降创建和删除文件的速度慢得让人怀疑机器是不是坏了。这篇《一种面向高性能计算的分布式对象存储系统》论文里提出的 COSS用扁平化对象组织加 Redis 内存元数据把文件创建性能做到 Lustre 的 2.15 倍删除性能做到 5.13 倍读/写聚合带宽分别提升 22.5% 和 50.4%。对做分布式存储、分布式开发的人来说这篇论文值得精读——2017 年的设计今天在超算存储方向依然是很好的参考文献。2. 为什么 POSIX 语义在超算存储上不够用COSS 的架构选型逻辑2.1 传统分布式文件系统的三个天花板目录树、VFS、集中共享Lustre、GPFS、PVFS、Panasas 这些通用分布式共享文件系统设计目标都是完整兼容 POSIX功能完备但系统复杂度也高。在超算环境下它们的瓶颈集中在三个地方。第一是目录树结构。文件数量一多目录树就变大变深维护庞大目录结构带来巨大的元数据管理开销每次文件操作都要多次与元数据服务交互。这个交互次数不是固定的目录越深路径解析涉及的目录项越多MDS 压力成倍增长。论文里说得直接元数据性能瓶颈就是这么来的。第二是访问语义冗余。POSIX 接口分两类一类是 open、read、write、close 这些数据访问接口一类是 readdir、unlink、rename、link 这些数据管理接口。超算计算节点上的应用程序绝大多数时间只用第一类接口第二类很少触发。传统文件系统把所有接口捆绑在一起实现复杂不说关键路径上的代码也被拖累。最典型的场景是并行程序每步都要 open/close接口路径上每多一个判断放大到几千个进程就是明显延迟。第三是内核 VFS 紧耦合。现有 HPC 文件系统在接口描述、数据块大小、缓存策略、数据一致性这些参数上都受限于内核 VFS 的约束没法动态配置。论文里点了一个实际问题无法适应分级存储和新型存储模式的快速发展。内核态的修改周期长、风险大存储团队想针对 SSD 做缓存策略调整往往要等内核版本升级。还有一个工程上的共性问题集中式共享存储架构高并发下所有计算节点都打到同一批存储节点上I/O 请求冲突概率高。日记里做带宽测试时Lustre 在客户端规模扩大后聚合带宽反而下降就是 I/O 竞争的直接表现。2.2 COSS 的节点角色拆分元数据、服务、I/O 各管一段COSS 整体架构把角色拆得很清楚部署在计算节点上的客户端负责转发 I/O 请求服务端解析请求后交给本地文件系统或远程 I/O 节点处理元数据服务单独存在数据管理工具独立成模块。看论文里的实验环境配置COSS 用 10 台机器构建存储环境其中 1 个元数据节点、1 个服务节点、8 个 I/O 节点Lustre 用 9 台机器构建1 个 MDS 加 8 个 OSS其余 32 台作计算节点这个对比规模是对等的。节点角色职责部署说明元数据节点存储全系统对象元数据信息跑 Redis 内存数据库提供元数据操作服务服务节点运行数据管理工具文件查找、备份、迁移、删除的入口I/O 节点承接计算节点数据读写分布式本地存储底层落盘到本地文件系统计算节点运行 COSS 客户端转发应用程序 I/O 请求到对应服务端高速网络节点间数据传输支持 Ethernet 与 InfiniBand内部走 RDMA这里值得注意元数据节点只有 1 个但实验测出来元数据性能还是远高于 Lustre原因在于元数据全走内存 Redis单机吞吐足够扛住测试规模的并发。实际部署时元数据节点是所有操作的汇聚点它的网络带宽和内存容量决定了整个系统的元数据上限。COSS 的内部数据传输支持 InfiniBand 网络 RDMA高带宽低延迟。做存储的人都知道InfiniBand 最值钱的地方不只是带宽而是 RDMA 把网络传输从 CPU 卸载出来这对大规模并发 I/O 场景是实打实的收益。2.3 数据访问平面与数据管理平面的分离精简语义背后的设计COSS 最有参考价值的设计是把传统文件访问语义拆成数据访问和数据管理两个平面。客户端只保留数据访问接口应用程序拿到的还是 POSIX 风格的文件访问方式完全透明而数据管理服务跑在单独的服务节点上对外提供一套数据管理工具。这样做的效果是计算节点上的接口路径轻了管理操作也不会干扰数据访问。这个设计和后来的很多存储系统思路一致。Lustre 的 DNE、Ceph 的 MON/OSD 分离本质上都是把控制流和数据流分开。但 COSS 做得更彻底的地方在于数据管理工具直接访问内存数据库获取文件信息不需要像传统文件系统那样通过 inode 遍历磁盘。论文里有个很直观的对比——目录遍历测试文件数达到百万量级时Lustre 的 ls 操作非常耗时因为需要和 MDS 交互获取文件列表还要多次访问 OST 拿大小信息而 COSS 的 ls 集成在数据管理工具中一次内存数据库查询就拿到全部结果。对于想复现这套架构的团队最小部署的角色划分可以参照下面的清单# 最小部署4 台节点示例 # node01: metadata 节点安装 Redis监听 6379 # node02: service 节点部署数据管理工具与策略引擎 # node03-node06: I/O 节点配置本地 SSD 存储 # 计算节点安装 COSS client挂载后与原文件访问方式保持一致 # node01 元数据服务初始化 redis-server --port 6379 --appendonly yes --maxmemory 32gb # node03-node06 启动 I/O 服务模块注册到元数据节点 coss-io --metadata-host node01 --local-store /data/ssd --rdma-port 9400从工程角度说这种角色拆分让每个节点的工作负载更单纯。元数据节点不用碰数据盘I/O 节点不用管目录树出问题时定位边界也清晰。存储系统的故障排查最麻烦的就是职责混杂导致的相互影响。3. 用户态 I/O 栈与扁平化对象组织把对象存储落到 HPC 的四个关键实现3.1 完全用户态的堆栈式 I/O 设计为什么绕开内核 VFSCOSS 的 I/O 软件栈完全在用户层实现采用堆栈式模块设计功能扩展方便灵活。这个选择和内核 VFS 松耦合最大的意义在于灵活性数据块大小、缓存策略、数据一致性这些参数都可以动态设置不受内核约束。对比常见的用户态文件系统方案 FUSECOSS 走的是不同路线。FUSE 通过内核模块转发请求虽然开发方便但有内核态切换开销COSS 是完全自研的用户态存储模块多个 I/O 节点的存储空间通过统一存储模块聚合实现对对象的全局访问。用户态实现带来两个直接收益一是接口扩展不用碰内核二是缓存策略可以针对应用场景定制。但这套设计也有代价——没有内核 page cache 兜底。内核文件系统天然有成熟的缓存和回写机制用户态全得自己实现。论文里提到缓存管理、数据管理方面具有极大灵活性反过来理解就是缓存一致性、并发控制、崩溃恢复这些工作存储团队要自己承担。这是分布式开发里典型的取舍灵活性和工作量成正比。软件栈从下往上大致是本地存储适配层、对象组织层、请求路由层、客户端接口层。应用层通过 POSIX 形式接口访问底层数据分布逻辑对应用完全透明。这种分层结构在代码组织上有个好处每一层都可以单独测试I/O 路径出问题时按层排查。3.2 对象路径哈希取模扁平化组织怎么算分布COSS 的底层数据存储采用对象存储的扁平化组织方式在客户端挂载点下设置一级目录类似于 S3 的 bucket、Swift 的容器概念目录数量可配置。对象根据路径名通过哈希取模均匀分布到各个目录。这个分布计算的实现很简单核心就是一致性哈希的简化版import hashlib def select_container(object_path: str, container_count: int 256) - str: 根据对象路径名哈希取模选择目标一级目录 :param object_path: 对象完整路径如 /mnt/coss/user/data/part_0001 :param container_count: 一级目录数量可配置 :return: 目录名如 bucket_0123 digest hashlib.md5(object_path.encode(utf-8)).hexdigest() index int(digest, 16) % container_count return fbucket_{index:04d} # 示例两个不同路径的对象分布 print(select_container(/mnt/coss/user/data/part_0001)) # bucket_0045 print(select_container(/mnt/coss/user/data/part_0002)) # bucket_0208这段代码的逻辑是对对象路径做 MD5取哈希值的整数再对容器数取模。MD5 的分布特性保证对象在目录间大致均匀比按文件名首字母分桶的方式稳定得多。参数上 container_count 的选择要按对象总量来估算——每个目录承载 50 万到 100 万对象是合理区间目录太小碎片多目录太大哈希冲突和后台扫描成本都会涨。假设预期 1 亿对象256 个目录平均每个约 39 万对象处于健康范围。相比维护一棵深层目录树扁平化组织的索引层简洁得多。文件访问协议更高效元数据性能瓶颈也随之缓解。哈希取模的另一个隐含优势单目录的对象列表保存在 Redis 的 Set 结构里目录遍历操作只需要一次内存查询。3.3 本地优先数据布局少占网络 I/O 的双刃剑COSS 采用本地优先的数据布局策略计算节点创建对象时优先选择本地存储相比数据任意分布的布局策略访问效率更高。分布化的本地存储架构实现了作业间的数据隔离有效减少访问冲突规避 I/O 竞争问题高并发时比集中式共享存储架构的 I/O 效率高。但这里有个必须说清楚的代价。本地优先意味着客户端数少的时候系统只利用到部分 I/O 节点的本地存储和 I/O 能力其他节点的资源闲着。论文实验里的对比曲线很诚实当客户端较少时COSS 聚合带宽低于 Lustre因为 Lustre 的数据分布不受节点位置限制能充分利用全部 8 个 OSS。直到客户端数继续扩大Lustre 的 I/O 竞争加剧、聚合带宽饱和甚至下降COSS 的优势才体现出来。这个特性决定了 COSS 的适用场景计算节点多、单节点数据访问量大的超算作业。如果集群规模小本地优先反而成了性能短板。做架构选型时不能被峰值数据迷惑要看自己的规模落在曲线哪一段。与 Ceph 的 CRUSH 算法对比Ceph 强调全局均匀分布每个 PG 按权重散到所有 OSDCOSS 的本地优先选择把数据留在本地本质上是缩短 I/O 路径。两者都解决数据分布问题但出发点不同——一个是分布式系统通用设计一个是贴着超算作业模式做的优化。对超算场景作业的计算和存储放在同一批节点上是最自然的搭配。4. 基于 Redis 的元数据管理文件创建性能提升 5 倍的核心机制4.1 从 inode 到 Key/Value目录遍历为什么快了这么多传统文件系统的元数据以 inode 为核心组织文件属性、数据块指针、权限信息都挂在 inode 上。Lustre 的元数据服务部署在 MDS 节点文件创建要分配 inode、写入目录项、记录时间戳每一步都是磁盘操作目录遍历时ls 操作要和 MDS 交互获取文件列表还要多次和 OST 交互获取文件大小等信息。文件数到百万量级这种多次往返的开销就非常可观。COSS 把元数据整体搬进 Redis用 Key/Value 结构存储。对象元数据既包含传统 POSIX 类型的元数据如文件所有者、大小、最近访问时间又支持用户自定义元数据比如对象所在的 I/O 节点、计算阶段号。这个自定义元数据的设计被很多人忽略但对 HPC 场景非常关键——作业调度器可以根据阶段号直接定位数据位置不用扫描数据面。目录遍历从磁盘交互变成内存查询性能量级完全不同。内存访问延迟是微秒级磁盘即使是 SSD 也要几十微秒到毫秒级加上网络往返差距是两个数量级以上。这就是为什么论文测试中文件总数从 16 万增加到 128 万Lustre 的 ls 耗时明显上升COSS 几乎是一条平线。4.2 Redis 元数据的 Key 设计与操作流程COSS 对元数据的操作包括创建、查询、更新、删除。实际落地时 Key 怎么设计直接影响查询效率和扩展性。常见做法是用 Hash 存对象属性用 Set 维护目录索引# 1. 对象元数据写入Hash 结构一次命令写入全部属性 HMSET obj:user/data/part_0001 size 4194304 owner uid_1024 \ node io_node_07 ctime 1700000000 stage 3 # 2. 目录索引维护Set 结构记录一级目录下所有对象 SADD dir:user/data obj:user/data/part_0001 # 3. 临时对象生命周期管理到期自动清理 EXPIRE obj:user/data/part_0001 604800三条命令分别对应元数据写入、目录索引维护、生命周期管理。键名规则上obj: 前缀加完整路径作为主键dir: 前缀加一级目录名作为目录索引键。参数说明size 单位是字节node 字段记录对象所在 I/O 节点——这是自定义元数据的典型用法数据管理工具查询时直接拿到数据位置省去数据面扫描。stage 字段记录计算阶段号配合作业调度。EXPIRE 的秒数按作业周期设置临时文件到期自动回收避免元数据无限增长。写入顺序上有个细节值得注意先写对象元数据再写目录索引还是反过来实践里优先保证数据落盘成功后再写元数据因为元数据指向一个不存在的对象比数据存在但没有索引要好恢复得多。数据结构键名示例存储内容Hashobj:user/data/part_0001POSIX 属性、自定义元数据Setdir:user/data该目录下所有对象键Stringpath:user/data/part_0001对象路径到 I/O 节点的映射Redis 选型而不是 MySQL 或者 LevelDB核心原因是读写吞吐。Redis 单实例 QPS 可以达到十万级纯内存访问对元数据这种高频率小请求的模式最合适。LevelDB 虽然是嵌入式 KV但读写路径上还有磁盘操作和压缩开销。论文里 Redis 用的是集中存储模式配合超算作业的负载特征单节点足够。4.3 拟线性扩展的真相瓶颈在数据面不在元数据面论文里有一个容易被忽略的细节文件创建、删除性能随 I/O 节点数量增加呈现近线性提升。这说明元数据面没有成为瓶颈——如果元数据节点拖后腿I/O 节点加再多创建性能也上不去。元数据操作全部在内存完成路径解析简单不涉及磁盘 I/O所以 8 个 I/O 节点时创建性能可以达到 Lustre 的 2.15 倍删除性能到 5.13 倍。删除性能差距比创建更大原因在于 Lustre 删除文件要清理 inode、更新目录项、释放数据块映射多个步骤都要落盘COSS 删除对象只需要从 Redis 删掉 Hash 键和 Set 成员再标记数据面存储空间可回收路径短很多。这套设计的边界也很清楚Redis 单节点有内存上限和吞吐上限。论文实验是 1 个元数据节点扛住 8 个 I/O 节点和 32 个计算节点如果集群规模再扩大一个数量级元数据节点就要考虑集群化。后续演进方向是 Redis Cluster 分片按对象路径哈希分到多个元数据节点这也是论文结尾提到下一步要研究高可用和容错机制的原因。5. 避坑复现 COSS 这类对象存储最常见的五个翻车点5.1 小规模客户端下性能反而不如 Lustre本地优先策略的双刃剑现象4 到 8 个客户端做带宽测试COSS 的聚合带宽明显低于同等规模的 Lustre有人看到这个结果直接判定系统不行。原因本地优先的数据布局导致只有部分 I/O 节点存储被使用其他节点资源闲置Lustre 的全局分布能打满全部 OSS。解决评估这类系统不能只看单点数据要从小规模到大规模跑完整曲线。论文里的对比数据讲得很清楚客户端超过 16 个之后 COSS 优势才起来到 32 客户端时读/写聚合带宽分别提升 22.5% 和 50.4%。5.2 哈希取模在对象量少时目录分布不均现象创建几千个测试文件一半 bucket 是空的某个 bucket 里堆了 30% 的对象导致单目录操作变慢。原因哈希取模在样本量小时自然存在倾斜这是统计特性不是实现 bug。解决对象量小的时候不要急着扩目录数量可以先集中在少量 bucket等对象规模上来再分裂。或者引入虚拟桶先哈希到虚拟桶再映射到实际目录分布更平滑。5.3 Redis 不配持久化重启元数据全丢现象IO 节点没动元数据节点重启客户端和 I/O 节点全部失联ls 不到任何对象——广播式的翻车。原因Redis 默认配置只吃内存不落盘元数据节点重启后内存清空全系统元数据消失。解决部署时强制开启 AOF 和 RDB。AOF 保证崩溃最多丢几秒数据RDB 做定期快照加速恢复。配置上 appendonly yes 打开appendfsync everysec 在安全和性能之间取平衡。5.4 数据管理工具查到的对象读不出来元数据与数据不一致现象数据管理工具显示文件存在位置在 io_node_07但客户端对这个路径发起 read 请求返回错误。原因数据访问和数据管理分离后两个平面的信息可能脱节——元数据写成功了数据实际没落盘或者数据挪过元数据里的 I/O 节点信息没更新。解决对象创建时坚持先数据后元数据的顺序数据确认落盘再写元数据启动时做一次元数据和数据面的对账列出孤儿对象和缺失对象。策略迁移操作要设计成事务性的或者带状态标记避免迁移中途崩溃导致两边都不认。5.5 用户态 I/O 栈没有内核缓存兜底性能全靠自己调现象小文件随机读写性能波动大同一个测试反复跑结果能差两倍。原因用户态实现绕开了内核 page cache没有现成的缓存和预取机制文件系统常见的局部性优化全部失效。解决客户端加一层读缓存按访问模式做预取数据块大小做成可配置参数小文件场景用小块减少内部碎片大文件顺序读场景用大块摊薄寻址开销。性能调优从调内核参数变成调自己代码工作量前置但调好了上限更高。6. 性能验证方法用 MPI-tiotest 和 Mdtest 复现论文的对比实验6.1 实验环境怎么搭论文实验在 51 台服务器上进行角色划分有参考价值10 台构建 COSS 存储环境9 台构建 Lustre 存储环境32 台作计算节点所有数据存储用 SSD节点间走 InfiniBand。如果本地复现最少 6 台就能跑通流程——1 台元数据、1 台服务、2 台 I/O、2 台计算节点。角色机器数配置要求COSS 元数据节点1大内存Redis 常驻COSS 服务节点1数据管理工具部署COSS I/O 节点8SSD 本地存储Lustre MDS1与 COSS 元数据节点同规格Lustre OSS8与 COSS I/O 节点同规格计算节点32跑客户端压测程序6.2 聚合带宽测试MPI-tiotest 怎么跑论文用基于 Tiobench 修改的 MPI-tiotest 测试聚合带宽关键参数是块大小和并发进程数# 32 个计算进程块大小 4MB读写各 5 轮 mpirun -np 32 --hostfile compute_hosts \ /opt/mpi-tiotest/mpi-tiotest -d /mnt/coss -b 4M -t 8 -w 5 -r 5-b 4M 指定数据块大小大块顺序读写接近 HPC 真实负载-t 8 是每节点线程数32 节点乘以 8 线程总共 256 并发 I/O 流-w 5 -r 5 表示写 5 轮读 5 轮取平均。注意先在两个规模下各跑一遍——8 进程和 32 进程——验证扩展性曲线的形状只看一组数据容易漏掉 5.1 里提到的本地优先边界问题。6.3 元数据性能测试Mdtest 的文件创建与删除# 每进程创建 10 万个文件跑 3 轮 mpirun -np 32 --hostfile compute_hosts \ /opt/mdtest/mdtest -d /mnt/coss -n 100000 -i 3 -F-n 100000 是每进程文件数32 进程乘以 10 万等于单轮 320 万次创建操作这是百万级元数据压力的标准测试量-i 3 表示跑 3 轮取稳定值-F 表示只创建文件不创建目录。跑完之后对比同一测试在 Lustre 上的结果看创建和删除速率差距以及客户端数从 1 翻到 32 时性能是否线性增长。6.4 判断拟线性扩展看什么指标扩展性判断的要点是效率值饱和带宽除以 I/O 节点数如果节点数翻倍效率基本不变就是线性扩展。论文里的扩展性曲线清晰展示了这一点。另外要看整条曲线在饱和点之后的形态——Lustre 到达饱和后下降COSS 保持平台期这个平台才是系统设计水平的分水岭。从那以后我每次评估一套新存储都强制跑一遍从 2 个客户端到满规模的两段式压测一段看小规模行为一段看大规模趋势先看曲线形状再看峰值数字。峰值高但曲线掉得快的系统和峰值平但曲线稳定的系统我选后者。这套方法论也推荐给你希望帮到你。本文还有配套的精品资源点击获取