NodeJS循环读取文件执行Couchbase CRUD遇问题及内存溢出解决
老哥,我来帮你搞定这个问题!你遇到的两个核心问题——fs.readFile不执行、内存溢出,其实都是异步流程管理和资源加载方式的坑,咱们一步步拆解解决:
先搞懂为什么最初fs.readFile没执行
大概率是你用了同步循环(比如普通for/forEach)配合异步的fs.readFile,循环会直接跑完,但readFile的回调/Promise还没触发,导致看起来像是没执行。或者你没正确等待Promise完成就继续后续逻辑,异步流程完全乱了。
内存溢出的根源
你改用Promise后,可能是一次性把所有JSON文件都读到内存里解析,如果文件数量多、单文件体积大,V8引擎的堆内存直接被占满,就会抛出FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed这个错误。
正确的解决方案:异步迭代+分批/串行处理
核心思路是不要一次性加载所有文件到内存,而是逐个/分批处理,同时确保异步流程被正确等待。下面给你具体的可运行代码:
第一步:封装异步工具函数&CRUD操作
先把fs的方法换成Promise版本(Node.js 10+自带fs.promises),同时确保Couchbase的CRUD操作是异步的,并且复用连接(别每次都创建/关闭cluster,太耗资源):
const fs = require('fs').promises; const path = require('path'); const couchbase = require('couchbase'); // 全局复用Couchbase连接(启动时创建一次即可) let cluster; async function initCouchbase() { if (!cluster) { cluster = await couchbase.connect('couchbase://localhost', { username: '你的用户名', password: '你的密码' }); } return cluster.bucket('你的bucket名').defaultCollection(); } // 封装CRUD操作(示例用upsert,你可以改成自己需要的逻辑) async function crudOps(doc) { const collection = await initCouchbase(); try { await collection.upsert(doc.id, doc); // 替换成你的CRUD逻辑 console.log(`✅ 处理完成:${doc.id}`); } catch (err) { console.error(`❌ 处理失败 ${doc.id}:`, err.message); } }
第二步:串行逐个处理文件(最稳,内存占用最低)
用for...of配合await,确保处理完一个文件再加载下一个,完全避免内存暴涨:
async function processFilesSerial(dirPath) { try { // 读取目录下所有文件,过滤出JSON const files = await fs.readdir(dirPath); const jsonFiles = files.filter(file => path.extname(file) === '.json'); console.log(`找到 ${jsonFiles.length} 个JSON文件,开始串行处理...`); // 逐个处理,await确保异步流程顺序 for (const file of jsonFiles) { const filePath = path.join(dirPath, file); // 读取文件内容(Promise版本,自动等待) const content = await fs.readFile(filePath, 'utf8'); // 解析JSON,加错误处理避免单个文件崩掉整个流程 let doc; try { doc = JSON.parse(content); } catch (parseErr) { console.error(`⚠️ 解析失败 ${file}:`, parseErr.message); continue; // 跳过坏文件,继续处理下一个 } // 执行CRUD,等待完成再走下一个 await crudOps(doc); } console.log('🎉 所有文件处理完成!'); } catch (err) { console.error('💥 流程出错:', err.message); } finally { // 程序结束时关闭Couchbase连接 if (cluster) await cluster.close(); } } // 调用函数,替换成你的JSON目录路径 processFilesSerial('./your-json-files-dir');
第三步:分批并行处理(兼顾速度和内存)
如果串行处理太慢,可以改成分批并行,比如每次同时处理5个文件,既提升速度,又不会一次性加载太多文件到内存:
async function processFilesInBatches(dirPath, batchSize = 5) { try { const files = await fs.readdir(dirPath); const jsonFiles = files.filter(file => path.extname(file) === '.json'); const totalBatches = Math.ceil(jsonFiles.length / batchSize); console.log(`找到 ${jsonFiles.length} 个JSON文件,分成 ${totalBatches} 批处理...`); for (let i = 0; i < jsonFiles.length; i += batchSize) { // 切分出当前批次的文件 const batch = jsonFiles.slice(i, i + batchSize); // 并行处理当前批次的所有文件,Promise.all等待所有完成 await Promise.all(batch.map(async (file) => { const filePath = path.join(dirPath, file); const content = await fs.readFile(filePath, 'utf8'); let doc; try { doc = JSON.parse(content); } catch (parseErr) { console.error(`⚠️ 解析失败 ${file}:`, parseErr.message); return; } await crudOps(doc); })); console.log(`✅ 完成第 ${Math.floor(i / batchSize) + 1}/${totalBatches} 批`); } console.log('🎉 所有批次处理完成!'); } catch (err) { console.error('💥 流程出错:', err.message); } finally { if (cluster) await cluster.close(); } } // 调用分批处理,批量大小设为5(可根据你的内存调整) processFilesInBatches('./your-json-files-dir', 5);
第四步:处理超大JSON文件(流式解析)
如果你的JSON文件特别大(比如几百MB甚至GB级),就算单个文件也会撑爆内存,这时候要用流式解析,比如stream-json库(先装:npm install stream-json):
const { parser } = require('stream-json'); const { streamArray } = require('stream-json/streamers/StreamArray'); const fs = require('fs'); const path = require('path'); async function processLargeJsonFile(filePath) { return new Promise((resolve, reject) => { console.log(`开始流式处理超大文件:${filePath}`); // 创建读取流,解析成JSON数组(如果是单个对象,用streamObject) const stream = fs.createReadStream(filePath) .pipe(parser()) .pipe(streamArray()); stream.on('data', async ({ value }) => { // 暂停流,避免数据堆积导致内存暴涨 stream.pause(); try { await crudOps(value); } catch (err) { console.error(`❌ 处理流式数据失败:`, err.message); } finally { // 处理完当前数据,恢复流 stream.resume(); } }); stream.on('end', () => { console.log(`🎉 超大文件处理完成:${filePath}`); resolve(); }); stream.on('error', (err) => { console.error(`💥 流式处理出错:`, err.message); reject(err); }); }); } // 调用处理超大文件 // processLargeJsonFile('./huge-data.json');
额外优化Tips
- 增加Node.js内存限制:如果确实需要处理大量数据,可以启动时加参数:
node --max-old-space-size=4096 your-script.js(分配4GB堆内存),但这是临时方案,优先从代码层面优化。 - 日志记录:可以把失败的文件记录到日志里,方便后续排查。
- 跳过重复文件:如果有重复的文件名或文档ID,可以加个去重逻辑。
内容的提问来源于stack exchange,提问作者user2405589

