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

Node.js/SocketIO聊天程序读取JSON文件崩溃问题求助

问题根因
  • 读写冲突:当前使用异步fs.writeFile直接覆盖原文件,高并发消息场景下多次写请求互相干扰,会出现写一半进程中断、内容覆盖的问题,最终导致JSON文件为空或格式损坏,触发JSON.parse解析失败报错。
  • 内存溢出:所有历史消息全量存储在内存的this.messages数组中,消息量持续增长时内存占用会无限制升高,无论调大多少PM2内存上限最终都会被打满触发重启。
  • 无异常兜底:JSON解析逻辑没有捕获异常,文件损坏后直接抛出未捕获异常导致进程崩溃,PM2反复重启会重复触发解析错误,拉高CPU占用。
快速修复方案

1. 第一优先级:阻止进程崩溃

1.1 给JSON读取加异常兜底

解析失败时直接降级为空数组,避免进程直接退出:

// 读取逻辑修正后
if (fs.existsSync(dataPathFile)) {
  try {
    const content = fs.readFileSync(dataPathFile, "UTF-8").trim();
    this.messages = content ? JSON.parse(content) : [];
  } catch (err) {
    // 可在这里加文件损坏的日志记录
    this.messages = [];
  }
} else {
  this.messages = [];
}

1.2 替换为原子写逻辑,避免损坏存储文件

先写临时文件,写完后再原子替换原文件,同时加锁避免并发写冲突:

// 全局写锁标记,避免并发写
let isWriting = false;
let hasPendingWrite = false;

async function saveMessageFile() {
  if (isWriting) {
    hasPendingWrite = true;
    return;
  }
  isWriting = true;
  try {
    const tempFilePath = `${dataPathFile}.tmp`;
    // 先写入临时文件
    await fs.promises.writeFile(tempFilePath, JSON.stringify(this.messages), "UTF-8");
    // 原子替换原文件,不会出现半写情况
    await fs.promises.rename(tempFilePath, dataPathFile);
  } catch (err) {
    console.error('消息存储失败', err);
  } finally {
    isWriting = false;
    if (hasPendingWrite) {
      hasPendingWrite = false;
      saveMessageFile();
    }
  }
}

2. 第二优先级:缓解内存/CPU占用过高问题

2.1 限制内存存储的消息条数

只保留最近N条消息在内存,避免数组无限膨胀:

// 可根据业务需求调整保留条数
const MAX_MEMORY_MESSAGES = 2000;
// 新增消息后裁剪数组
this.messages.push(newMessage);
if (this.messages.length > MAX_MEMORY_MESSAGES) {
  this.messages = this.messages.slice(-MAX_MEMORY_MESSAGES);
}

如果需要保留全量历史,可以单独写归档逻辑,把旧消息追加到归档文件,不要全量存在内存中。

2.2 降低写操作频次

不要每次新增一条消息就触发写文件,改成防抖批量写:比如累计3条消息或者间隔2秒再执行一次写操作,大幅减少JSON序列化和IO操作的CPU消耗。

内容的提问来源于stack exchange,提问作者nvanhaute

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 09:45:01