OSS存储卷支持多种客户端,不同客户端对写操作的支持程度不同。通常来说,完备的写操作支持会牺牲部分读性能。因此,数据的读写分离能最大程度避免写操作对读性能的影响,显著提升读多写少场景的数据访问性能。本文介绍在读多写少场景下如何通过不同的OSS存储卷客户端,或OSS SDK、ossutil工具等方式实现数据的读写分离。
前提条件
集群的存储组件为CSI。不同客户端要求的CSI组件版本不同,请及时升级组件版本。具体操作,请参见管理csi-plugin和csi-provisioner组件。
已创建OSS Bucket,且Bucket与集群属于同一阿里云账号。
重要不建议跨账号使用OSS。
使用场景
OSS存储常见的使用场景包含只读和读写。对于读多写少的场景,建议您将OSS数据的读写操作进行分离:
读:通过选择不同的OSS存储卷客户端,或修改配置参数,以优化数据读取速度。
写:通过ossfs 1.0客户端实现完整写能力,或通过OSS SDK等方式写入数据。
只读
在大数据业务的推断过程、数据分析、数据查询等场景中使用时,为避免数据被误删除和误修改,建议您将OSS存储卷的访问模式配置为ReadOnlyMany。
OSS存储卷当前支持ossfs 1.0, ossfs 2.0和strmvol三种类型的客户端,均支持只读操作。
建议您升级CSI组件版本至1.32.2及以上,并使用ossfs 2.0替代ossfs 1.0优化只读场景性能。关于ossfs 2.0存储卷的使用方式,请参见使用ossfs 2.0存储卷。
若您的业务为数据集读取、量化回测、时序日志分析等需要读取海量小文件数据的场景,可选择使用strmvol存储卷。关于strmvol存储卷的使用方式,请参见使用strmvol存储卷。
更多关于客户端适用场景和选型建议的信息,请参见客户端选型参考。
若您的业务需要在只读场景使用ossfs 1.0客户端,可参考以下参数配置提升数据读取性能。
参数
说明
kernel_cache
开启后,通过内核缓存优化读性能。适用于不需要实时访问最新内容的场景。
缓存命中时,ossfs重复读取文件时,将通过内核缓冲区高速缓存处理,仅使用未被其他进程使用的可用内存。
parallel_count
以分片模式上传或下载大文件时,分片的并发数,默认值为20。
max_multireq
列举文件时,访问文件元信息的最大并发数。此处需大于等于parallel_count的值,默认值为20。
max_stat_cache_size
用于指定文件元数据的缓存空间可缓存多少个文件的元数据。单位为个,默认值为1000。如需禁止使用元数据缓存,可设置为0。
在不需要实时访问最新内容的场景下,当目录下文件比较多时,可以根据实例规格增加支持的缓存个数,加快ls的速度。
direct_read
ossfs 1.91及以上版本针对只读场景新增直读模式。
直读模式的功能介绍与性能测试详情,请参见ossfs 1.0新版本功能介绍及性能压测。
直读模式调优的具体方式,请参考只读场景性能调优。
读写
在读写场景中,您需要将OSS存储卷的访问模式配置为ReadWriteMany。
目前ossfs 1.0支持完整的写操作,ossfs 2.0仅支持顺序追加写。通过ossfs进行写操作时,注意事项如下:
在并发写场景中,ossfs无法保证数据写入的一致性。
挂载状态下,登录应用Pod或宿主机,在挂载路径下删除或变更文件,都会直接删除或变更OSS Bucket中对应的源文件。您可以开启OSS Bucket的版本控制,避免误删除重要数据,请参见版本控制。
在读多写少、尤其是读写路径分离的场景中,例如,在大数据业务的训练过程中,建议您将OSS数据的读写操作进行分离,即将OSS存储卷的访问模式配置为
ReadOnlyMany
,然后通过配置缓存参数优化数据读取速度,并通过SDK等方式写入数据。具体操作,请参见使用示例。
使用示例
本文以手写图像识别训练应用为例,介绍如何实现OSS存储的读写分离。该示例为一个简单的深度学习模型训练,业务通过只读OSS存储卷从OSS Bucket的/data-dir
目录中读取训练集,并通过读写OSS存储卷或OSS SDK将checkpoint写入OSS Bucket的/log-dir
目录。
通过ossfs实现读写
由于写checkpoint是顺序追加写行为,因此可以选择ossfs 1.0或ossfs 2.0实现。
参考以下模板部署手写图像识别训练应用。
该应用使用简单的Python编写,并挂载使用OSS静态存储卷。关于OSS存储卷配置,请参见使用ossfs 1.0静态存储卷或使用ossfs 2.0存储卷。
以下示例中,应用将OSS Bucket的子路径
/tf-train
挂载至Pod的/mnt
目录。操作前,请先将MNIST手写图像训练集上传到/tf-train/train/data
目录中,供应用读取。参考以下内容,创建ossfs 1.0存储卷。
cat << EOF | kubectl apply -f - apiVersion: v1 kind: Secret metadata: name: oss-secret namespace: default stringData: akId: "<your-accesskey-id>" akSecret: "<your-accesskey-secret>" --- apiVersion: v1 kind: PersistentVolume metadata: name: tf-train-pv labels: alicloud-pvname: tf-train-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain csi: driver: ossplugin.csi.alibabacloud.com volumeHandle: tf-train-pv nodePublishSecretRef: name: oss-secret namespace: default volumeAttributes: bucket: "<a-bucket-name>" url: "oss-cn-beijing.aliyuncs.com" otherOpts: "-o max_stat_cache_size=0 -o allow_other" path: "/tf-train" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: tf-train-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi selector: matchLabels: alicloud-pvname: tf-train-pv EOF
参考以下内容,创建训练容器。
cat << EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: labels: app: tfjob name: tf-mnist namespace: default spec: containers: - command: - sh - -c - python /app/main.py env: - name: NVIDIA_VISIBLE_DEVICES value: void - name: gpus value: "0" - name: workers value: "1" - name: TEST_TMPDIR value: "/mnt" image: registry.cn-beijing.aliyuncs.com/tool-sys/tf-train-demo:rw imagePullPolicy: Always name: tensorflow ports: - containerPort: 20000 name: tfjob-port protocol: TCP volumeMounts: - name: train mountPath: "/mnt" workingDir: /root priority: 0 restartPolicy: Never securityContext: {} terminationGracePeriodSeconds: 30 volumes: - name: train persistentVolumeClaim: claimName: tf-train-pvc EOF
训练开始前,
trainning_logs
目录为空。在训练过程中,中间文件将写入Pod的/mnt/training_logs
目录中,由ossfs上传至OSS Bucket的/tf-train/trainning_logs
目录中。
验证数据正常读写。
查看Pod的状态。
kubectl get pod tf-mnist
等待Pod状态从Running转换至Completed,约需要数分钟,预期输出为:
NAME READY STATUS RESTARTS AGE tf-mnist 1/1 Completed 0 2m
查看Pod运行日志。
通过Pod运行日志查询数据加载所需的时间,该时间包含从OSS下载文件及TensorFlow加载的时间。
kubectl logs tf-mnist | grep dataload
预期输出如下,实际查询的时间与实例的性能和网络状态相关。
dataload cost time: 1.54191803932
登录OSS管理控制台。查看OSS Bucket的
/tf-train/trainning_logs
目录中已出现相关文件,表明数据可以正常从OSS中读写。
通过读写分离优化ossfs数据读取速度
改造应用实现读写分离。
读:参数配置优化后的ossfs 1.0只读存储卷实现读操作
写:分别以ossfs 1.0读写存储卷以及OSS SDK实现写操作
使用ossfs 1.0读写存储卷实现写操作
下文以手写图像识别训练应用和ossfs 1.0只读+读写卷为例,介绍如何改造应用实现读写分离。
参考以下内容,创建ossfs 1.0只读存储卷。
针对只读场景,对ossfs 1.0存储卷的配置参数进行优化。
将PV和PVC的
accessModes
均修改为ReadOnlyMany
,Bucket的挂载路径可缩小至/tf-train/train/data
。在
otherOpts
中增加-o max_stat_cache_size=10000 -o kernel_cache -o umask=022
选项,使ossfs在读取数据时能使用内存高速缓冲区加速处理,并增加元数据支持的缓存个数(10000个元数据缓存大约占40 M的内存,可根据实例规格及读取的数据量多少进行调整),以及通过umask使容器进程以非root用户运行时也有读权限。更多信息,请参见使用场景。
cat << EOF | kubectl apply -f - apiVersion: v1 kind: Secret metadata: name: oss-secret namespace: default stringData: akId: "<your-accesskey-id>" akSecret: "<your-accesskey-secret>" --- apiVersion: v1 kind: PersistentVolume metadata: name: tf-train-pv labels: alicloud-pvname: tf-train-pv spec: capacity: storage: 10Gi accessModes: - ReadOnlyMany persistentVolumeReclaimPolicy: Retain csi: driver: ossplugin.csi.alibabacloud.com volumeHandle: tf-train-pv nodePublishSecretRef: name: oss-secret namespace: default volumeAttributes: bucket: "<a-bucket-name>" url: "oss-cn-beijing.aliyuncs.com" otherOpts: "-o max_stat_cache_size=10000 -o kernel_cache -o umask=022 -o allow_other" path: "/tf-train/train/data" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: tf-train-pvc spec: accessModes: - ReadOnlyMany resources: requests: storage: 10Gi selector: matchLabels: alicloud-pvname: tf-train-pv EOF
参考以下内容,创建ossfs 1.0读写存储卷。
cat << EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolume metadata: name: tf-logging-pv labels: alicloud-pvname: tf-logging-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain csi: driver: ossplugin.csi.alibabacloud.com volumeHandle: tf-logging-pv nodePublishSecretRef: name: oss-secret namespace: default volumeAttributes: bucket: "<a-bucket-name>" url: "oss-cn-beijing.aliyuncs.com" otherOpts: "-o max_stat_cache_size=0 -o allow_other" path: "/tf-train/trainning_logs" --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: tf-logging-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi selector: matchLabels: alicloud-pvname: tf-logging-pv EOF
参考以下内容,创建训练容器。
说明训练业务的逻辑无需任何改造,只需要部署时同时挂载只读及读写存储卷。
cat << EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: labels: app: tfjob name: tf-mnist namespace: default spec: containers: - command: - sh - -c - python /app/main.py env: - name: NVIDIA_VISIBLE_DEVICES value: void - name: gpus value: "0" - name: workers value: "1" - name: TEST_TMPDIR value: "/mnt" image: registry.cn-beijing.aliyuncs.com/tool-sys/tf-train-demo:rw imagePullPolicy: Always name: tensorflow ports: - containerPort: 20000 name: tfjob-port protocol: TCP volumeMounts: - name: train mountPath: "/mnt/train/data" - name: logging mountPath: "/mnt/trainning_logs" workingDir: /root priority: 0 restartPolicy: Never securityContext: {} terminationGracePeriodSeconds: 30 volumes: - name: train persistentVolumeClaim: claimName: tf-train-pvc - name: logging persistentVolumeClaim: claimName: tf-logging-pvc EOF
使用OSS SDK实现写操作
下文以手写图像识别训练应用和OSS SDK为例,介绍如何改造应用实现读写分离。
在容器环境中安装SDK,可在构建镜像时,增加以下内容。具体操作,请参见安装。
RUN pip install oss2
参考OSS的官方文档Python SDK demo修改源代码。
以上述手写图像识别训练应用为例,源镜像的相关源代码如下。
def train(): ... saver = tf.train.Saver(max_to_keep=0) for i in range(FLAGS.max_steps): if i % 10 == 0: # Record summaries and test-set accuracy summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False)) print('Accuracy at step %s: %s' % (i, acc)) if i % 100 == 0: print('Save checkpoint at step %s: %s' % (i, acc)) saver.save(sess, FLAGS.log_dir + '/model.ckpt', global_step=i)
以上代码中,每进行100次迭代,会将中间文件(checkpoint)存入指定的log_dir目录,即Pod的
/mnt/training_logs
目录。由于Saver的max_to_keep
参数为0,将维护所有的中间文件。如果迭代1000次,则存放10组checkpoint文件在OSS端。通过修改代码,实现通过OSS SDK上传中间文件,修改要求如下:
配置访问凭证,从环境变量中读取AccessKey和Bucket信息。具体操作,请参见配置访问凭证。
为减少容器内存的使用,可将
max_to_keep
设置为1,即总是只保存最新一组训练中间文件。每次保存中间文件时,通过put_object_from_file
函数上传至对应Bucket目录。
说明在读写目录分离的场景中,使用SDK时,还可以通过异步读写进一步提升训练效率。
import oss2 from oss2.credentials import EnvironmentVariableCredentialsProvider auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider()) url = os.getenv('URL','<default-url>') bucketname = os.getenv('BUCKET','<default-bucket-name>') bucket = oss2.Bucket(auth, url, bucket) ... def train(): ... saver = tf.train.Saver(max_to_keep=1) for i in range(FLAGS.max_steps): if i % 10 == 0: # Record summaries and test-set accuracy summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False)) print('Accuracy at step %s: %s' % (i, acc)) if i % 100 == 0: print('Save checkpoint at step %s: %s' % (i, acc)) saver.save(sess, FLAGS.log_dir + '/model.ckpt', global_step=i) # FLAGS.log_dir = os.path.join(os.getenv('TEST_TMPDIR', '/mnt'),'training_logs') for path,_,file_list in os.walk(FLAGS.log_dir) : for file_name in file_list: bucket.put_object_from_file(os.path.join('tf-train/training_logs', file_name), os.path.join(path, file_name))
修改后的容器镜像为
registry.cn-beijing.aliyuncs.com/tool-sys/tf-train-demo:ro
。修改部分应用模板,使其通过只读方式访问OSS。
将PV和PVC的
accessModes
均修改为ReadOnlyMany
,Bucket的挂载路径可缩小至/tf-train/train/data
。在
otherOpts
中增加-o kernel_cache -o max_stat_cache_size=10000 -oumask=022
选项,使ossfs在读取数据时能使用内存高速缓冲区加速处理,并增加元数据支持的缓存个数(10000个元数据缓存大约占40M的内存,可根据实例规格及读取的数据量多少进行调整),以及通过umask使容器进程以非root用户运行时也有读权限。更多信息,请参见使用场景。在Pod模板中增加OSS_ACCESS_KEY_ID、OSS_ACCESS_KEY_SECRET环境变量,其值可从oss-secret中获取,与配置OSS存储卷中的信息保持一致。
验证数据正常读写。
查看Pod状态。
kubectl get pod tf-mnist
等待Pod状态从
Running
转换至Completed
,约需要数分钟,预期输出为:NAME READY STATUS RESTARTS AGE tf-mnist 1/1 Completed 0 2m
查看Pod运行日志。
通过Pod运行日志查询数据加载所需的时间,该时间包含从OSS下载文件及TensorFlow加载的时间。
kubectl logs tf-mnist | grep dataload
预期输出:
dataload cost time: 0.843528985977
预期输出表明,在只读模式中合理利用缓存,可提升数据读取的速度。在大规模训练或其他持续加载数据的场景中,优化效果更加明显。
登录OSS管理控制台。查看OSS Bucket的
/tf-train/trainning_logs
目录中已出现相关文件,表明数据可以正常从OSS中读写。
相关参考
OSS SDK参考
阿里云官方OSS SDK部分参考代码如下:
更多支持语言PHP、Node.js、Browser.js、.NET、Android、iOS、Ruby,请参见SDK参考。
实现OSS读写分离的其他工具
工具 | 相关文档 |
OpenAPI | |
ossutil命令行工具 | |
ossbrowser图形化管理工具 |