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

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

  1. 增加Node.js内存限制:如果确实需要处理大量数据,可以启动时加参数:node --max-old-space-size=4096 your-script.js(分配4GB堆内存),但这是临时方案,优先从代码层面优化。
  2. 日志记录:可以把失败的文件记录到日志里,方便后续排查。
  3. 跳过重复文件:如果有重复的文件名或文档ID,可以加个去重逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:01:32