对象存储 OSS 凭借其支持海量非结构化数据存储、按需付费的弹性成本以及基于标准 HTTP 协议的跨平台便捷访问特性,成为大数据场景的重要存储方案。在 Kubernetes(K8s)容器化业务中,提升 OSS 数据的访问性能能有效节省计算资源,提高业务效率。如何提升海量文件的数据读取速率,对于 AI 训练集管理、量化回测、时序日志分析等场景尤为重要。
阿里云容器服务(ACK) 支持 StrmVol 类型存储卷,基于底层虚拟块设备及内核态文件系统,如 ext4、EROFS(Extended Read-Only File System,扩展只读文件系统),显著降低海量小文件访问延迟。本文将简单介绍 StrmVol 存储方案的实现原理及适用场景,并以图片训练集读取场景为例,展示 StrmVol 存储卷的使用方式。
海量小文件数据的访问性能瓶颈
在 K8s 环境中,业务容器访问 OSS 的标准方式是通过存储卷机制。这一过程依赖于容器存储接口(CSI)驱动,目前主流的实现方案是通过 FUSE(Filesystem in Userspace)客户端挂载 OSS 数据。
FUSE 允许用户在用户态空间实现文件系统逻辑,通过内核与用户态的交互将对象存储的元数据和数据映射为本地文件系统接口(POSIX)。CSI 驱动通过 FUSE 客户端将 OSS 路径挂载为容器内的本地目录,业务应用可直接通过标准文件操作(如 open()
、read()
)访问数据。
针对顺序读写大文件场景,FUSE 客户端可通过预读、缓存等技术显著提升性能(例如视频流媒体、大数据文件处理)。但在小文件场景依然存在性能瓶颈,这是因为:
- 频繁的内核态切换:每次文件操作(如
open()
、close()
)需在用户态 FUSE 进程与内核态之间多次上下文切换,导致额外开销。 - 元数据管理压力:对象存储的元数据(如文件列表、属性)需通过 HTTP/REST API 查询,海量小文件的元数据请求会显著增加网络延迟和带宽占用。
StrmVol存储卷:虚拟块设备方案
为解决 FUSE 在海量小文件场景中的性能瓶颈,StrmVol 存储卷提出了一种基于虚拟块设备(Virtual Block Device)加内核态文件系统(如 EROFS)的方案,通过消除 FUSE 中间层的性能损耗,使数据访问路径直接下沉至存储驱动层,提升数据的访问速度,特别适用于 AI 训练集加载、时序日志分析等需要快速遍历百万级小文件的业务场景。
核心机制与优化细节
- 快速索引构建:仅元信息同步,加速初始化流程
初始化阶段仅拉取 OSS Bucket 挂载点下文件元信息(如文件名、路径、大小)并构建索引,不包含对象扩展信息(如自定义元数据、标签),显著缩短索引构建时间,提升部署效率。
- 内存预取优化:并发读取提升数据访问效率
虚拟块设备通过预设内存空间作为临时存储介质,根据已构建的索引信息提前并发预取后续可能访问的数据块。这一机制减少 I/O 等待时间,尤其在海量小文件场景中可降低读取延迟。
- 内核态文件系统加速:避免用户态切换,提升容器化业务读取性能
容器化业务通过内核态文件系统 直接从内存中读取数据,避免用户态与内核态的频繁切换带来的上下文开销。其中,默认使用的 EROFS 文件系统通过压缩与高效访问机制,进一步提升存储空间利用率和数据读取性能。
适用场景
- 数据已存储在 OSS Bucket 中,且在业务运行期间数据无更新需求。
- 业务对文件系统的扩展信息不敏感。
- 只读场景,尤其是海量小文件或随机读场景。
图像数据集加载性能测试
本文示例通过 Argo Workflow 模拟分布式图像数据集加载场景。
数据集基于 ImageNet 部分数据,假设存储于 oss://imagenet/data/ 路径下,包含 4 个子目录(如 oss://imagenet/data/n01491361/),每个目录存放 10480 张图像,约 1.2G 大小。
安装strmvol-csi-driver
使用 strmvol 存储卷需要部署单独的 CSI 驱动(strmvol-csi-driver 组件),可直接在 ACK 应用市场中部署。部署后,该 CSI 驱动与 ACK 组件管理中维护的 csi-provisioner 与 csi-plugin 组件相互独立,不会产生冲突。
创建StrmVol存储卷
StrmVol 存储卷的 PVC 与 PV 定义与目前 ACK OSS 存储卷配置类似,本次测试中使用的存储卷 YAML 如下。
apiVersion: v1 kind: PersistentVolume metadata: name: pv-strmvol spec: capacity: # 挂载的OSS挂载点下最高可存储16 TiB数据。 storage: 20Gi # 仅支持ReadOnlyMany访问模式。 accessModes: - ReadOnlyMany persistentVolumeReclaimPolicy: Retain csi: driver: strmvol.csi.alibabacloud.com volumeHandle: pv-strmvol nodeStageSecretRef: name: strmvol-secret namespace: default volumeAttributes: bucket: imagenet path: /data url: oss-cn-hangzhou-internal.aliyuncs.com directMode: "false" resourceLimit: "4c8g" --- kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-strmvol namespace: default spec: accessModes: - ReadOnlyMany resources: requests: storage: 20Gi volumeName: pv-strmvol
其中,比较重要的 PV 配置参数为 directMode 与 resourceLimit。
- directMode: 是否开启 direct 模式。开启时,关闭预取与数据本地缓存,适合小文件随机读场景,如训练集的随机批量读取。本次示例使用简单的顺序读取,因此选择关闭 direct 模式。
- resourceLimit: 挂载虚拟块设备后能使用节点的最大资源限制。"4c8g"表示该虚拟块设备最多能占用节点的 4 vCPU、8GiB 内存资源。
基于Argo Workflows创建数据集处理工作流任务
工作流分为三阶段:
1)list-shards 阶段:通过 Python 脚本列出挂载路径下的 4 个目录生成路径集合;
2)parallel-processing 阶段:并发启动 4 个子任务,每个任务通过 parallel -j4
指令对对应目录下的所有图像执行4线程并行读取操作(模拟加载);
3)calculate-averages 阶段:汇总各任务耗时,计算并输出平均处理时间。
Argo Workflows 组件可在 ACK 集群组件管理中快速安装。安装后,可使用 Argo CLI 或 kubectl 快速提交工作流【1】。
apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: distributed-imagenet-training- spec: entrypoint: main affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "node-type" operator: In values: - "argo" volumes: - name: pvc-volume persistentVolumeClaim: claimName: pvc-strmvol templates: - name: main steps: - - name: list-shards template: list-imagenet-shards - - name: parallel-processing template: process-shard arguments: parameters: - name: paths value: "{{item}}" withParam: "{{steps.list-shards.outputs.result}}" - - name: calculate-statistics template: calculate-averages - name: list-imagenet-shards script: image: mirrors-ssl.aliyuncs.com/python:latest command: [python] source: | import subprocess import json output = subprocess.check_output("ls /mnt/data", shell=True, text=True) files = [f for f in output.split('\n') if f] print(json.dumps(files, indent=2)) volumeMounts: - name: pvc-volume mountPath: /mnt/data - name: process-shard inputs: parameters: - name: paths container: image: alibaba-cloud-linux-3-registry.cn-hangzhou.cr.aliyuncs.com/alinux3/alinux3:latest command: [/bin/bash, -c] args: - | yum install -y parallel SHARD_JSON="/mnt/data/{{inputs.parameters.paths}}" SHARD_NUM="{{inputs.parameters.paths}}" START_TIME=$(date +%s) echo "Processing shard $SHARD_JSON" find "$SHARD_JSON" -maxdepth 1 -name "*.JPEG" -print0 | parallel -0 -j4 'cp {} /dev/null' END_TIME=$(date +%s) ELAPSED=$((END_TIME - START_TIME)) mkdir -p /tmp/output echo $ELAPSED > /tmp/output/time_shard_${SHARD_NUM}.txt resources: requests: memory: "4Gi" cpu: "1000m" limits: memory: "4Gi" cpu: "2000m" volumeMounts: - name: pvc-volume mountPath: /mnt/data outputs: artifacts: - name: time_shard path: /tmp/output/time_shard_{{inputs.parameters.paths}}.txt oss: key: results/results-{{workflow.creationTimestamp}}/time_shard_{{inputs.parameters.paths}}.txt archive: none: {} - name: calculate-averages inputs: artifacts: - name: results path: /tmp/output oss: key: "results/results-{{workflow.creationTimestamp}}" container: image: registry-vpc.cn-beijing.aliyuncs.com/acs/busybox:1.33.1 command: [sh, -c] args: - | echo "开始合并结果..." TOTAL_TIME=0 SHARD_COUNT=0 echo "各分片处理时间统计:" for time_file in /tmp/output/time_shard_*.txt; do TIME=$(cat $time_file) SHARD_ID=${time_file##*_} SHARD_ID=${SHARD_ID%.txt} echo "分片 ${SHARD_ID}: ${TIME} 秒" TOTAL_TIME=$((TOTAL_TIME + TIME)) SHARD_COUNT=$((SHARD_COUNT + 1)) done if [ $SHARD_COUNT -gt 0 ]; then AVERAGE=$((TOTAL_TIME / SHARD_COUNT)) echo "--------------------------------" echo "总分片数量: $SHARD_COUNT" echo "总处理时间: $TOTAL_TIME 秒" echo "平均处理时间: $AVERAGE 秒/分片" echo "Average: $AVERAGE seconds" > /tmp/output/time_stats.txt else echo "错误:未找到分片时间数据" exit 1 fi outputs: artifacts: - name: test-file path: /tmp/output/time_stats.txt oss: key: results/results-{{workflow.creationTimestamp}}/time_stats.txt archive: none: {}
工作流的运行结果如图所示,列举所有图像并读取其数据的平均耗时为 21 秒。
开始合并结果... 各分片处理时间统计: 分片 1: 21 秒 分片 2: 21 秒 分片 3: 22 秒 分片 4: 21 秒 -------------------------------- 总分片数量: 4 总处理时间: 85 秒 平均处理时间: 21 秒/分片
开源方案
阿里云也将基于
containerd/overlaybd[https://github.com/containerd/overlaybd]项目提供了该技术的开源实现,结合 OCI image volume 可以为容器挂载一个只读数据卷。详细介绍可参考 https://www.youtube.com/watch?v=XqL5lh32lr8&t=962s
StrmVol存储卷总结
核心优化:
- 内存预取加速数据访问,减少 I/O 等待;
- 内核态直接读取避免用户态切换开销;
- 轻量索引快速初始化,仅同步必要元数据。
适用场景:
- 海量小文件只读场景;
- OSS 端数据存储无需频繁更新。
StrmVol 的详细使用方式与更多压测数据,可阅读官方文档使用 strmvol 存储卷优化 OSS 小文件读取性能【2】。
【1】快速提交工作流https://help.aliyun.com/zh/ack/distributed-cloud-container-platform-for-kubernetes/user-guide/create-a-workflow#section-bz1-rq8-7rz【2】使用strmvol存储卷优化OSS小文件读取性能https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/use-strmvol-storage-volumes