Node.js调用fs.writeFile写入JSON文件时格式随机损坏如何解决
问题诱因
- 核心原因是并发写入冲突:你当前使用的异步
readFile+writeFile逻辑是非原子操作,也没有加锁控制,当同一时间有多个请求/异步任务触发这段读写逻辑时,会出现多个任务先后读取到旧版文件内容、并行写入的情况,两次写入的内容部分覆盖/叠加,就会出现你看到的「完整JSON后拼接半截JSON」的损坏现象。 - 次要原因是非原子写入风险:直接用
writeFile覆盖原文件,写入过程中如果出现进程崩溃、系统断电、磁盘满等异常,会导致文件只写入一半内容直接损坏。 - 你当前的
writeFile回调为空,所有写入层面的错误都会被吞掉,无法及时感知异常。
修复方案
- 加读写锁控制:单进程场景下可以使用
proper-lockfile这类库,在读写database.json前先加锁,确保同一时间只有一个读写操作执行,从根源避免并发修改冲突。 - 改用原子写入逻辑:不要直接覆盖原文件,先将序列化后的JSON写入同目录下的临时文件(例如
database.json.tmp),确认临时文件写入成功后,再调用fs.rename原子替换原文件,即使写入临时文件过程中出现异常,原文件也不会被破坏。示例逻辑参考:
const fs = require('fs/promises') const path = require('path') const lockfile = require('proper-lockfile') async function updateUserCooldown(user) { const dbPath = path.resolve(__dirname, database_name) const tmpPath = `${dbPath}.tmp` let release try { // 加锁 release = await lockfile.lock(dbPath) // 读取文件 const data = await fs.readFile(dbPath, 'utf-8') const obj = JSON.parse(data) const userIdx = obj.users.findIndex(v => v.id === user) const cooldowns = obj.users[userIdx].cooldowns if (!cooldowns.some(item => item.QUIZ)) { cooldowns.push({ "QUIZ": Date.now() + 86400000 }) } const json = JSON.stringify(obj, null, 4) // 写入临时文件 await fs.writeFile(tmpPath, json, 'utf-8') // 原子替换原文件 await fs.rename(tmpPath, dbPath) } catch (err) { console.error('写入数据库失败', err) // 清理残留的临时文件 await fs.unlink(tmpPath).catch(() => {}) } finally { // 释放锁 if (release) await release() } }
- 补全所有异步操作的错误捕获逻辑,不要留空回调,避免异常被吞。
- 如果你的业务修改频率较高,建议直接改用SQLite这类轻量文件数据库,自带事务和并发控制能力,无需手动处理文件锁和原子写入的问题。
内容的提问来源于stack exchange,提问作者UnidentifiedX
相关产品推荐
相关产品推荐

