Python 脚本写配置被中断就损坏:一次原子写入的排查与改造

举报
yd_235270612 发表于 2026/10/28 09:27:29 2026/10/28
【摘要】 用同目录临时文件+fsync+原子替换,避免脚本中断写坏配置

Python 脚本写配置被中断就损坏:一次原子写入的排查与改造


一、问题


一套定时脚本负责把采集结果写进一个 JSON 状态文件,另一条定时任务在几分钟后读取它做汇总。过去一个月里出现过三次"配置读不出来":文件大小正常,内容却只有半截,json.load 直接抛 JSONDecodeError。最典型的一次发生在机器内存紧张的时候,进程被 OOM killer 干掉,恰好卡在写文件那一刻,文件就停在了不完整的状态。


二、原因


最常见的写法是 open(path, 'w') 然后 json.dump。问题在于 'w' 模式一打开就把文件截断成 0 字节,而写入是分多次系统调用完成的:先写一部分,再写剩下的。中间任何一步中断——进程被杀、磁盘写满、机器掉电、异常栈一路退出去没走到正常的 close——文件都会停在"写了一半"。此时读端拿到的是坏数据,而原来的内容已经被截断,没有任何回滚的余地。


还有第二个隐患是并发:两个进程同时写同一个路径,后写的可能覆盖前者,也可能和前者交叉写入,得到一个两边都不是的混合体。


三、做法


思路一句话:永远不要就地改写正式文件,而是在同目录写一个临时文件,写完落盘后再 rename 覆盖。POSIX 的 rename 在同一文件系统内是原子操作,读端要么看到完整的旧文件,要么看到完整的新文件,不存在中间态。


def save_json_atomic(path, data):

    d = os.path.dirname(os.path.abspath(path))

    fd, tmp = tempfile.mkstemp(dir=d, prefix=".tmp-", suffix=".json")

    try:

        with os.fdopen(fd, "w", encoding="utf-8") as f:

            json.dump(data, f, ensure_ascii=False)

            f.flush()

            os.fsync(f.fileno()) # 让数据真正落盘,而不是停在页缓存

        os.replace(tmp, path) # 原子替换

    except BaseException:

        os.unlink(tmp) # 失败不留垃圾文件

        raise


几个容易漏的细节:


1. 临时文件必须和目标文件同目录。跨文件系统的 rename 会退化成"复制+删除",就不再是原子操作。


2. fsync 不能省。只 flush 只是把 Python 的缓冲区刷进内核页缓存,进程崩溃没问题,整机掉电仍然可能丢数据。


3. 权限要单独处理。mkstemp 建出来的文件默认是 0600,如果原文件是 0644,替换之后权限就悄悄变了,可能让另一个以不同用户运行的读端读不到。稳妥做法是替换前把临时文件 chmod 成原文件的权限。


4. 读端也要配合。读的时候用 try/except 兜住 JSONDecodeError,但不要一解析失败就写回一份空的默认值——那会把仅有的一份好数据也清掉。正确做法是报错退出,保留现场。


5. 并发场景再加一把文件锁(fcntl.flock 或一个 lock 文件),保证同一时刻只有一个进程在"读-改-写"。


改完之后还补了一条校验:替换之前先试着把临时文件解析一遍,解析不过就不替换,直接告警。这条在磁盘写满的场景里真的救过一次——那次 fsync 报 ENOSPC,旧文件原样保留。


四、小结


- "写文件"在工程上不是一个动作,而是一串动作,每一步都可能失败,所以需要给它一个明确的成功点。

- 原子写入的通用套路很短:同目录临时文件 → 写完 fsync → os.replace,失败就删临时文件。

- 判据也很简单:如果这个文件在读端看来坏了会造成故障,就值得用这套写法,代价不过十几行代码。

- 配套的写后校验和读端容错,比追求"永远不出错"要实际得多。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。