在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命令修改(单个文件系统修改)
- 查看NFS系统目标设备当前的预读参数。 - cat /sys/class/bdi/$(mountpoint -d <nfs_mountpoint>)/read_ahead_kb- 其中 - <nfs_mountpoint>需替换为实际的NFS挂载点路径,可以通过- cat /proc/self/mountinfo命令获取。
- 适当调大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机制进行通信。
- 打开并编辑NFS的udev rules配置文件(位于 - /etc/udev/rules.d/目录)。如果不存在udev rules配置文件,请先自行创建。例如:- sudo vim /etc/udev/rules.d/99-nfs.rules
- 在打开的文件中,添加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"
- 保存并关闭文件。 
- 重新加载udev规则,使新增规则生效。 - sudo udevadm control --reload
- 为所有已经挂载的设备手动触发一次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目录 )。
- 打开并编辑NFS的配置文件。 - sudo vim /etc/nfs.conf
- 修改默认预读值,然后保存并关闭文件。 - [nfsrahead] nfs=15000 nfs4=16000- 请根据实际挂载的NFS文件系统版本(nfs/nfsv4)修改对应参数值。 - nfs表示NFS文件系统协议版本是3;- nfs4表示NFS文件系统协议版本是4,您可以通过- mount -v | grep nfs命令查询。
- 对于已挂载的NFS文件系统,需要手动卸载后重新挂载使配置生效。 - sudo umount <nfs_mountpoint> sudo mount -t nfs -o vers=<NFS协议版本> <NFS服务器地址> <nfs_mountpoint>