• 踩坑笔记 NFS坑-2个pod读写文件延迟问题


    现象

    有2个服务通过NFS去共享文件,一个服务创建,一个服务读取。

    • A服务在podA上创建文件
    • B服务在podB上查找A创建的文件

    问题: 偶先的B服务查找不到A创建的文件

    排查

    排查过程如下

    1.查看A和B服务POD的磁盘挂载和使用情况

    #查看磁盘挂载和使用情况
    df -h
    
    • 1
    • 2

    发现使用了nfs挂载以及挂载的目录

    2.复现文件,去日照查找指定文件

    我会在B服务查找文件的代码,前后打印当前文件夹下的文件

    listfiles("list - pre") // 增加日志
    xxxfile.exits()
    listfiles("list - post")// 增加日志
    
    • 1
    • 2
    • 3
    #查找日志
    grep 'list - ' *.log
    07:28:40,382 ...
    07:28:45,390 ...
    07:28:50,400 ...
    07:28:55,411 ...
    07:29:00,414 ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    发现在当时的场景中文件夹中的确没有要查询的文件。

    3.查找指定问题文件

    #查找文件
    find /nfs/pic -name abc.jpg
    find . -name abc.jpg
    #查找目录
    find . -type d -name abc
    
    • 1
    • 2
    • 3
    • 4
    • 5

    发现文件时存在的

    4.查找文件详细信息创建时间

    ll --full-time
    nobody nobody 2023-10-11 07:08:40.144 abc.jpg
    
    • 1
    • 2

    发现创建时间是在程序读取后的时间。

    就怀疑是NFScache问题,查找相关资料总结如下

    原因

    1.在传统磁盘中,所有的数据都会被缓存在PageCache中,修改过的Page会被异步刷回服务端,所以磁盘的延时都比较低。

    2.但在NFS文件系统中,NFS不会将新创建的文件或者新写入的内容缓存在PageCache中,而是尽快刷回NAS服务端。

    因此,当多个服务器共享一个NFS协议文件系统时,NAS所有的操作都会比磁盘多一次网络调用开销,这个开销一般在100us ~ 1ms之间。其中,尽快刷回NAS服务端,则涉及NAS所提供的如下多节点一致性模型:

    • 模型1 基于超时的最终一致性

    NFS会缓存目录或者文件的属性(FileAttr),操作系统会根据FileAttr是否发生变化,来判断这个目录或者文件是否在其他ECS被修改过。同时,加载FileAttr后,操作系统在T时间内,会认为Cache(例如,文件的内容、目录下的文件列表)有效;超过T时间后,会去服务端重新获取一次FileAttr,如果FileAttr未发生变化,操作系统会认为与此文件相关的Cache全部还是有效的。

    说明

    • T为自适应的值。默认值:1s~60s。
    • 文件内容Cache:指缓存了这个文件的内容。
    • 目录子项Cache:缓存了这个目录下存在哪些文件、不存在哪些文件。

    文件内容Cache示例

    1. ECS-1读取了文件X的0~4K:第一次读,cache不命中,访问服务器,读取数据,并缓存在本地。
    2. ECS-2更新了文件X的0~4K:数据写到服务端,更新FileAttr中的mtime。
    3. ECS-1再次读文件X的04K:第二次读,如果时间距离第一次小于T,由于FileAttr还没有过期,因此,直接使用缓存中的04K。
    4. ECS-1再次读文件X的0~4K:第三次读,如果时间距离第一次大于T,则去访问服务端,获取新的FileAttr,然后发现mtime变了,因此丢弃cache中的数据,去服务端读。

    我们遇到的问题就是这个原因,第一次查询文件时,发现文件不存在就缓存了一个不存在的cache,导致下次直接查缓存,还是不存在。

    目录子项Cache示例:

    1. ECS-1尝试查找/a:第一次查找,发现a不存在,于是在目录/下缓存一个a不存在的信息。
    2. ECS-2创建文件/a。
    3. ECS-1再次查找/a:第二次查找,由于时间小于T,直接使用cache,然后向用户报错文件不存在。
    4. ECS-1 再次访问/a:第三次查找,由于时间大于T,访问服务端,获取目录/最新的FileAttr,发现mtime有变化,则丢弃cache中的数据,去服务端查询/a。

    更多有关NFS超时的最终一致性模型内容,请参见NFS

    • 模型二 基于文件close/open的CTO一致性

    由于超时的最终一致性无法保证ECS-2可以立刻读ECS-1写入的数据。因此,为了提升一致性,NFS还提供了基于文件的CTO(close-to-open)一致性保证,即当两个及以上计算节点同时读写相同的文件时,ECS-1的修改在ECS-2不一定能立即看到。但是,一旦ECS-1打开一个文件,写入若干数据,然后执行了文件关闭操作,之后在任何一个计算节点重新打开该文件都可以保证能访问到新写入的数据。

    例如,生产者ECS生产了文件X,生产完毕后执行了close。
    然后给消息队列发一条消息说,文件X生产完毕。
    消费者ECS订阅消息队列,读到消息X(文件X生产完毕),
    此时,消费者ECS再去open这个文件,通过open返回的fd去读取这个文件,则一定能够读到文件X的所有内容。如果消费者ECS在生产者ECS生产完毕之前,就open了文件X,并且持有了fd,当收到消息后,直接用这个fd去读,是不保证能够读取到最新数据的。


    问题简化为:ECS-1创建了文件abc,但是ECS-2需要过一段时间才能看到ECS-1创建的文件abc,有时会延迟1s,有时甚至会到1分钟,这是为什么?

    综合一下上面的一坨:这是Lookup Cache导致的,符合预期T时间。例如,ECS-2在ECS-1创建文件abc前进行了访问,导致ECS-2发生文件不存在,于是缓存了一条文件abc不存在的记录。在T时间内,由于FileAttr还没有过期,ECS-2再次访问时,仍会访问第一次缓存到文件abc不存在的记录。


    解决方案

    方案一

    在程序中 循环检测(比如检测10次),注意间隔的时长 不要太长1-2s,我们这边设置5秒时 有问题。

    方案二

    关闭ECS-2的Nagtive Lookup Cache,不缓存不存在的文件。该方案开销最小。

    挂载时,添加lookupcache=positive(默认值lookupcache=all)字段,挂载命令如下所示:

    sudo mount -t nfs -o vers=3,nolock,proto=tcp,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport,lookupcache=positive file-system-id.region.nas.aliyuncs.com:/ /mnt
    
    • 1

    方案三

    关闭ECS-2的所有缓存。该方案会导致性能非常差,请根据业务实际情况选择合适的方案。

    挂载时,添加actimeo=0字段,挂载命令如下所示:

    sudo mount -t nfs -o vers=3,nolock,proto=tcp,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport,actimeo=0 file-system-id.region.nas.aliyuncs.com:/ /mnt
    
    • 1

    总结

    尽管上述解决方案可以在一定程度上减轻NFS文件同步延迟问题,但它们仍然需要处理许多复杂的技术细节。然而,有一种更简单和可行的方法,可以彻底摆脱NFS的问题,那就是使用云对象存储服务(如OSS,对象存储服务)。

    参考

    • https://www.alibabacloud.com/help/zh/nas/user-guide/faq-about-mounting#section-al2-qzt-pd3
    • https://linux.die.net/man/5/nfs
  • 相关阅读:
    GO安装以及配置(1)
    使用 Python 给 PDF 添加目录书签
    芯片设计“花招”已耍完?无指令集架构颠覆旧套路
    flask SQLAlchemy
    [HTML5]移动端web页面开发常用的头部标签设置
    NAFNet(ECCV 2022)-图像修复论文解读
    下载测试视频
    Python 多线程之threading介绍
    sizeof类大小 + 程序内存空间理解 + const变量的生命周期实现
    291_C++_发送json数据给对于的URL【JSON数据交互】
  • 原文地址:https://blog.csdn.net/abu935009066/article/details/133806511