Alibaba Cloud Linux 3系统的NFS文件系统读取文件性能不符合预期,如何处理?

在NFS(Network File System)文件系统中通过read、copy_file_range等系统调用读取文件时,与同场景下的Alibaba Cloud Linux 2相比,可能会存在明显的性能退化情况。本文介绍在Alibaba Cloud Linux 3系统的ECS实例中,在NFS文件系统下读取文件时性能不符合预期的问题原因及解决方案。

问题描述

问题表现

在Alibaba Cloud Linux 3系统的NFS文件系统中读取文件时性能不符合预期,典型表现如下:

  • 使用read、copy_file_range等系统调用从NFS文件系统中读取大文件时,耗时较久。

  • 使用dd命令从NFS挂载点的文件中读取数据时,Alibaba Cloud Linux 3比Alibaba Cloud Linux 2用时多。例如:

    dd if=<nfs_mntpoint>/<testfile> of=/dev/null bs=1M
    说明

    该示例命令表示从NFS挂载点的testfile文件中读取数据,并将其发送到/dev/null设备中。运行完毕后,dd 会输出一些信息,包括读取的总字节数以及操作耗费的时间,用来计算读取数据的速率,可以评估NFS文件系统的性能表现。

影响范围

该问题主要在以下ECS实例中存在:

  • 镜像:aliyun_3_x64_20G_alibase_20210415.vhd及之后的所有镜像版本。

  • 内核:5.10.23-4.al8.x86_64及之后的所有内核版本。

  • 文件系统:挂载NFS文件系统,读取位于挂载点目录下的文件。

问题原因

在上游Linux内核中,read_ahead_kb表示块设备的预读窗口大小的参数。预读(read-ahead)是一个性能优化技术,它允许系统预测接下来可能被读取的数据并提前加载到内存中,这样当这部分数据被请求时,就可以直接从内存中读取,而不必等待磁盘I/O操作,从而降低延迟并提高数据读取效率。

  • 在Linux内核5.4版本之前,NFS文件系统的预读量通常会基于挂载时设置的rsize 参数(即每次NFS读请求的大小)。默认情况下,NFS的预读窗口read_ahead_kb大小被设置为rsize 参数的15倍。Alibaba Cloud Linux 2的内核版本是4.19,默认的rsize参数大小为1,024 KB,即read_ahead_kb大小为15,360 KB。

  • 然而,在Linux内核5.4版本中引入了一个提交(index : kernel/git/torvalds/linux.git),随后read_ahead_kb的值不再基于rsize参数,而是与VM_READAHEAD_PAGES参数相关。Alibaba Cloud Linux 3的内核版本是5.10,默认的read_ahead_kb大小为128 KB。

所以,Alibaba Cloud Linux 3相比于Alibaba Cloud Linux 2的读取文件性能有所下降。因此Alibaba Cloud Linux 3需要重新评估和调整预读窗口大小,以优化文件的读取效率。

重要

在NFS文件系统中,较大的预读窗口可能会提高大型文件连续读取的性能,但如果窗口太大,也可能导致不必要的数据被加载到内存中,尤其是在随机读取的场景中。因此建议您根据实际的业务场景,仔细评估实际的工作负载,调整read_ahead_kb参数值以确保找到最佳的预读窗口大小。

解决方案

您可以选择以下任意一种方式修改read_ahead_kb参数来设置预读值。

通过echo命令修改(单个文件系统修改)

  1. 查看NFS系统目标设备当前的预读参数。

    cat /sys/class/bdi/$(mountpoint -d <nfs_mountpoint>)/read_ahead_kb

    其中<nfs_mountpoint>需替换为实际的NFS挂载点路径,可以通过cat /proc/self/mountinfo命令获取。

  2. 适当调大NFS文件系统所对应设备的预读参数。

    sudo sh -c 'echo <num> > /sys/class/bdi/<major>:<minor>/read_ahead_kb'

    您需要根据实际环境替换以下参数:

    • <num>:需要设置的预读窗口大小(单位KB)。

    • <major>:<minor>:NFS文件系统的主要和次要设备号,可以通过sudo mountpoint -d <nfs_mountpoint>命令获取。

    例如:

    sudo sh -c 'echo 15360 > /sys/class/bdi/0:422/read_ahead_kb'
    说明

    如果您挂载了多个NFS文件系统,需要重复执行命令修改每一个设备的预读参数。

通过udev机制自动修改(多个文件系统修改)

您也可以利用udev机制,添加udev规则,为所有已挂载的设备手动触发一次udev事件,使其触发udev规则检查并自动修改预读参数,也可以使后续新挂载的文件卷自动修改其预读参数。操作步骤如下:

说明

udev(用户空间设备管理器)是Linux内核的一个子系统,负责设备节点的管理和自动化。udev机制的核心组件是udev守护进程,它运行于用户空间,并与内核通过uevent机制进行通信。

  1. 打开并编辑NFS的udev rules配置文件(位于/etc/udev/rules.d/目录)。如果不存在udev rules配置文件,请先自行创建。例如:

    sudo vim /etc/udev/rules.d/99-nfs.rules
  2. 在打开的文件中,添加udev规则,以自动修改预读参数。

    该示例表示将read_ahead_kb的值设置为15,360 KB,您可以根据需要修改预读参数。

    SUBSYSTEM=="bdi", ACTION=="add", PROGRAM="/bin/awk -v bdi=$kernel 'BEGIN{ret=1} {if ($4 == bdi) {ret=0}} END{exit ret}' /proc/fs/nfsfs/volumes", ATTR{read_ahead_kb}="15360"
  3. 保存并关闭文件。

  4. 重新加载udev规则,使新增规则生效。

    sudo udevadm control --reload
  5. 为所有已经挂载的设备手动触发一次udev事件,将已挂载设备的read_ahead_kb值一并修改。

    sudo udevadm trigger -c add -s bdi

通过修改NFS的配置文件(2.3.3-57.0.1.al8.1及之后版本的多个文件系统修改)

如果您的Alibaba Cloud Linux 3的NFS文件系统版本是nfs-utils-2.3.3-57.0.1.al8.1及之后的版本(可以通过rpm -qa | grep nfs-utils命令查询 ),可以通过修改NFS的配置文件来修改read_ahead_kb参数(位于 /etc/nfs.conf目录 )。

  1. 打开并编辑NFS的配置文件。

    sudo vim /etc/nfs.conf 
  2. 修改默认预读值,然后保存并关闭文件。

    [nfsrahead]
    nfs=15000
    nfs4=16000

    请根据实际挂载的NFS文件系统版本(nfs/nfsv4)修改对应参数值。nfs表示NFS文件系统协议版本是3;nfs4表示NFS文件系统协议版本是4,您可以通过mount -v | grep nfs命令查询。

  3. 对于已挂载的NFS文件系统,需要手动卸载后重新挂载使配置生效。

    sudo umount <nfs_mountpoint>
    sudo mount -t nfs -o vers=<NFS协议版本> <NFS服务器地址> <nfs_mountpoint>