避免挂载失败写入本地目录:给 Linux 挂载点加不可变保护

给程序配置一个目录来保存上传文件、备份或日志,是很常见的做法。目录可能位于外置硬盘、NFS 或其他网络存储上,例如: /srv/nas 但“目录存在”不代表“存储已经挂载”。如果网络中断、NFS 服务没有启动,或者开机时挂载单元失败,/srv/nas 仍然只是系统盘上的普通目录。程序并不知道这一点,照常读写后会发现读取到的状态与挂载的存储不同步。 这个问题的关键不是“如何让挂载更可靠”,而是要让挂载失败时的写入尽早失败。 挂载点为什么会接收写入 挂载点只是一个目录。挂载成功前,路径解析到的是父文件系统;挂载成功后,Linux VFS 才把这个目录覆盖成另一个文件系统的入口: 挂载前:/srv/nas -> 根文件系统上的目录 挂载后:/srv/nas -> 外置磁盘或 NFS 文件系统 因此,挂载失败不会让路径自动消失,应用仍然可以对它执行 open、mkdir 和 rename。只要应用有权限,数据就会落到根文件系统。 用不可变属性让错误暴露出来 Linux 的 chattr 可以修改文件系统属性。对目录设置 i(immutable,不可变)属性后,目录内容不能被创建、删除、重命名或修改,即使调用者是 root 也不能直接绕过: sudo chattr +i /srv/nas 这正好符合挂载点的保护需求: 挂载还没有发生时,写入会收到 Operation not permitted,而不是悄悄占用系统盘; 挂载成功后,路径进入外部文件系统,外部文件系统自己的权限和属性生效; 外部存储掉线后,应用得到 I/O 错误,问题可以被监控发现。 i 属性作用在挂载点下面那个“被覆盖的目录”上。挂载成功后,这个目录暂时不可见,所以不会把它的权限带到新挂载的文件系统中。 一次完整的验证 下面以 /srv/nas 和 NFS 为例。生产环境请替换为自己的地址,并先在测试机验证。 1. 创建一个空挂载点 sudo install -d -o app -g app -m 0755 /srv/nas 挂载点最好保持为空。否则外部文件系统挂载后,目录中的原有文件会被隐藏;卸载后它们又会出现,容易造成“文件凭空消失”的误判。 2. 确认当前没有挂载 findmnt -T /srv/nas mountpoint -q /srv/nas && echo '已经挂载,请先确认' || echo '当前未挂载' 如果 findmnt 显示的来源仍是根文件系统,说明此时可以继续设置属性。不要在没有确认的情况下对一个已挂载的目录运行 chattr,否则可能修改到外部文件系统根目录的属性。 ...

August 4, 2026

多进程配置文件读写的演化历程

第一阶段:共享一套读写逻辑,谁都可以写 一开始,多个进程共用一套读写逻辑: if (!Exists(configPath)) { WriteDefaultConfig(configPath); } var config = ReadConfig(configPath); 问题有两个: 多个进程可能同时发现“文件不存在”,然后一起创建 一个进程正在写,另一个进程正好在读 如果是直接覆盖写,读取方可能看到: 文件刚被创建,内容还是空的 文件已经存在,但只写了一半 配置结构还没写完整 目标文件在替换过程中短暂不可用 对应的现象就是: 偶发读取失败 配置解析异常 文件内容丢失 某些进程启动时报错 根因很简单:读者看到了写入中的中间状态。 第二阶段:加锁、重试、缓存,先把系统稳住 先说 FileShare。 它想解决的其实是两个很具体的问题: 读取正好撞上写入,结果读到一半内容 两次写入互相重叠,结果把文件写坏 最直接的做法,就是用共享模式限制文件如何被同时打开。 它的目标不是提供原子替换,而是尽量减少下面这些情况: 读取撞上写入中的半成品 两次写入同时落到同一个目标文件 文件直接被另一进程占用导致操作失败 FileShare 带来的直接结果是:冲突不会消失,只会变成可以观察、可以重试的失败。 所以后面又加了重试。 这能减少两类问题: 少报“文件被占用” 让偶发冲突尽量靠重试跨过去 再往后,问题变成了独占时间。 配置读取很频繁,导致写入一直在等待读取释放文件锁。 所以开始引入缓存: var config = ReadConfig(configPath); var lastWriteTime = GetLastWriteTime(configPath); cache = (config, lastWriteTime); 下次访问时先比较时间戳: var currentWriteTime = GetLastWriteTime(configPath); if (currentWriteTime == cache.LastWriteTime) { return cache.Config; } cache = ReadConfig(configPath); return cache.Config; 没变,缓存有效;变了,缓存失效,重新加载。 ...

July 17, 2026