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.33.1及以上,并使用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目录。
操作前,请先获取测试使用的MNIST手写图像训练集,并将其上传到您的OSS Bucket的/tf-train/train/data目录中,供应用读取。
- MNIST手写图像训练集 
- OSS Bucket存储文件示例  
通过ossfs实现读写
由于写checkpoint是顺序追加写行为,因此可以选择ossfs 1.0或ossfs 2.0实现。
- 参考以下模板部署手写图像识别训练应用。 - 该应用使用简单的Python编写,并挂载使用OSS静态存储卷。关于OSS存储卷配置,请参见使用ossfs 1.0静态存储卷或使用ossfs 2.0存储卷。 - 以下示例中,应用将OSS Bucket的子路径 - /tf-train挂载至Pod的- /mnt目录。- 参考以下内容,创建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: "<your-bucket-name>" url: "oss-<region>.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
- 参考以下内容,创建训练容器。 - 在训练过程中,中间文件将写入Pod的 - /mnt/training_logs目录中,由ossfs上传至OSS Bucket的- /tf-train/training_logs目录中。- 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
 
- 验证数据正常读写。 - 查看Pod的状态。 - kubectl get pod tf-mnist- 等待Pod状态从Running转换至Completed,约需要数分钟,预期输出为: - NAME READY STATUS RESTARTS AGE tf-mnist 0/1 Completed 0 2m12s
- 查看Pod运行日志。 - 通过Pod运行日志查询数据加载所需的时间,该时间包含从OSS下载文件及TensorFlow加载的时间。 - kubectl logs tf-mnist | grep dataload- 预期输出如下,实际查询的时间与实例的性能和网络状态相关。 - dataload cost time: 1.54191803932
- 登录OSS管理控制台。查看OSS Bucket的 - /tf-train/training_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 kernel_cache -o max_stat_cache_size=10000 -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: "<your-bucket-name>" url: "oss-<region>.aliyuncs.com" otherOpts: "-o kernel_cache -o max_stat_cache_size=10000 -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: "<your-bucket-name>" url: "oss-<region>.aliyuncs.com" otherOpts: "-o max_stat_cache_size=0 -o allow_other" path: "/tf-train/training_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/training_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, bucketname) ... 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 -o umask=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 0/1 Completed 0 2m25s
- 查看Pod运行日志。 - 通过Pod运行日志查询数据加载所需的时间,该时间包含从OSS下载文件及TensorFlow加载的时间。 - kubectl logs tf-mnist | grep dataload- 预期输出如下,实际查询的时间与实例的性能和网络状态相关。预期输出表明,在只读模式中合理利用缓存,可提升数据读取的速度。在大规模训练或其他持续加载数据的场景中,优化效果更加明显。 - dataload cost time: 0.843528985977
- 登录OSS管理控制台。查看OSS Bucket的 - /tf-train/training_logs目录中已出现相关文件,表明数据可以正常从OSS中读写。 
 
相关参考
OSS SDK参考
阿里云官方OSS SDK部分参考代码如下:
更多支持语言PHP、Node.js、Browser.js、.NET、Android、iOS、Ruby,请参见SDK参考。
实现OSS读写分离的其他工具
| 工具 | 相关文档 | 
| OpenAPI | |
| ossutil命令行工具 | |
| ossbrowser图形化管理工具 |