Node.js:写配置文件时先写临时文件再 rename

24 次浏览1 条回复

直接 writeFile('config.json', ...) 有个小风险:进程崩在写入中间时,文件可能只剩半截。Node 里也可以走临时文件 + rename 这条路,临时文件放同目录,最后替换会更稳一点。

环境:Node.js 20+,Linux/macOS/Windows,只用内置模块。保存为 atomic-json.mjs:

import { mkdtemp, writeFile, rename, rm, readFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { tmpdir } from 'node:os';

async function writeJsonAtomic(path, value) {
  const dir = dirname(path);
  const tempDir = await mkdtemp(join(dir, '.tmp-write-'));
  const tempFile = join(tempDir, 'data.json');

  try {
    await writeFile(tempFile, JSON.stringify(value, null, 2) + '\n', 'utf8');
    await rename(tempFile, path);
  } finally {
    await rm(tempDir, { recursive: true, force: true });
  }
}

const file = join(tmpdir(), 'node-atomic-json-demo.json');
await writeJsonAtomic(file, { version: 1, enabled: true });
console.log(await readFile(file, 'utf8'));

运行:

node atomic-json.mjs

如果目标目录可能不存在,先 mkdir(dir, { recursive: true })。另外跨磁盘移动就不是同一个原子替换语义了,所以临时文件最好一直放在目标文件同目录。

这个思路没问题,补一个小坑:如果要强调“落盘后更抗崩”,最好在 rename 前把临时文件 fsync 一下;Linux/macOS 上还可以在替换后 fsync 目标目录。

另外 Windows 上覆盖已有文件时行为更容易踩平台差异,实际项目里建议先在目标环境跑一下已有配置文件被替换的用例。临时文件放同目录这一点是对的,别放系统 tmp 再跨盘移动。