Node.js临界区问题:多用户写入JSON文件需同步请求吗?
多用户并发写文件的同步问题及Node.js实现方案
必须做同步处理,你的代码目前存在两个严重问题:
- 竞态条件:多个请求同时读取
users.json,拿到相同的旧数据,各自修改后写入,后写入的操作会覆盖前一个的修改,直接导致用户数据丢失。 - 响应时机错误:
fs.writeFile是异步操作,你用check变量判断写操作结果的逻辑完全无效——写操作还没执行完,if(!check)就已经触发,会直接返回"Created",即使后续写操作失败也无法修正响应。
下面针对不同场景给出Node.js的实现方案:
方案1:单进程场景——内存锁/任务队列
Node.js是单线程事件循环,单进程下用内存中的锁或任务队列,就能保证同一时间只有一个请求在读写文件,避免竞态问题。
用async/await+简单锁改造代码
先实现一个基础的互斥锁:
let isFileLocked = false; // 等待锁释放 const acquireLock = () => { return new Promise(resolve => { const checkLock = () => { if (!isFileLocked) { isFileLocked = true; resolve(); } else { setTimeout(checkLock, 10); } }; checkLock(); }); }; // 释放锁 const releaseLock = () => { isFileLocked = false; };
然后改造路由,用async/await替代回调,确保操作顺序:
app.post("/register", async function(req, res) { try { // 先获取锁 await acquireLock(); // 读取文件(用Promise版API更易维护) const data = await fs.promises.readFile(__dirname + "/users.json", 'utf8'); const users = JSON.parse(data); // 添加新用户 users.users.push(req.body); // 写入文件 await fs.promises.writeFile(__dirname + "/users.json", JSON.stringify(users), 'utf8'); // 返回成功响应 res.send(JSON.stringify("Created")); } catch (err) { // 任何步骤出错都返回错误 res.send(JSON.stringify("error")); console.error(err); } finally { // 无论成功失败都释放锁 releaseLock(); } });
更优雅的任务队列方式
如果请求量较大,用队列排队处理更稳妥,避免轮询锁:
const taskQueue = []; let isProcessing = false; // 处理队列任务 const processQueue = async () => { if (isProcessing || taskQueue.length === 0) return; isProcessing = true; const { task, res } = taskQueue.shift(); try { await task(); } catch (err) { res.send(JSON.stringify("error")); console.error(err); } finally { isProcessing = false; processQueue(); // 处理下一个任务 } }; // 添加任务到队列 const addTask = (task, res) => { taskQueue.push({ task, res }); processQueue(); };
路由里使用队列:
app.post("/register", function(req, res) { addTask(async () => { const data = await fs.promises.readFile(__dirname + "/users.json", 'utf8'); const users = JSON.parse(data); users.users.push(req.body); await fs.promises.writeFile(__dirname + "/users.json", JSON.stringify(users), 'utf8'); res.send(JSON.stringify("Created")); }, res); });
方案2:多进程场景——文件系统级锁
如果你的Node.js应用用了cluster模块或者多进程部署,内存锁会失效(不同进程内存不共享),这时候需要用文件系统级别的排他锁。
Node.js v14.18.0+原生支持文件锁,示例代码:
app.post("/register", async function(req, res) { let fileHandle; try { // 以读写模式打开文件 fileHandle = await fs.promises.open(__dirname + "/users.json", 'r+'); // 添加排他锁(其他进程无法同时读写) await fileHandle.lock('ex'); // 读取文件内容 const data = await fileHandle.readFile('utf8'); const users = JSON.parse(data); // 添加新用户 users.users.push(req.body); // 先截断文件内容,再写入新数据 await fileHandle.truncate(0); await fileHandle.writeFile(JSON.stringify(users), 'utf8'); res.send(JSON.stringify("Created")); } catch (err) { res.send(JSON.stringify("error")); console.error(err); } finally { // 务必释放锁并关闭文件句柄 if (fileHandle) { await fileHandle.unlock().catch(() => {}); await fileHandle.close(); } } });
额外提醒
- 尽量避免用JSON文件做用户数据存储,并发场景下更推荐用数据库(比如MySQL、MongoDB),数据库原生支持事务和行级锁,比自己实现文件同步靠谱得多。
- 你的原代码里,
users["users"]可以简化成users.users,写法更简洁。
内容的提问来源于stack exchange,提问作者tekblade
相关产品推荐
相关产品推荐

