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
相关产品推荐
相关产品推荐

