You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js临界区问题:多用户写入JSON文件需同步请求吗?

多用户并发写文件的同步问题及Node.js实现方案

必须做同步处理,你的代码目前存在两个严重问题:

  1. 竞态条件:多个请求同时读取users.json,拿到相同的旧数据,各自修改后写入,后写入的操作会覆盖前一个的修改,直接导致用户数据丢失。
  2. 响应时机错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 18:06:27