Node.js出现EMFILE错误:加载超22万行SQLite3数据时文件打开过多
解决Node.js处理大量文件时的EMFILE错误
你遇到的这个问题很典型——虽然fs.readFile确实会自动关闭文件描述符,但你的代码一次性发起了22万多个异步读取请求,这远远超过了Mac OS X默认的文件描述符限制(默认一般是1024或更高,但肯定到不了22万)。forEach是同步遍历的,它会在瞬间把所有文件的读取请求都发出去,每个未完成的请求都会占用一个文件描述符,很快就把系统的配额耗尽了,所以才会抛出EMFILE: too many open files错误。
下面给你几个可行的解决方案,按推荐程度排序:
1. 控制异步操作的并发数(最推荐)
不要一次性发起所有请求,而是限制同时进行的文件读取/插入操作数量,比如一次只处理50个或者100个,这样就能把文件描述符的使用量控制在系统允许的范围内。
方案A:使用async库的eachLimit
先安装async库:
npm install async
然后修改代码:
const async = require('async'); const fs = require('fs'); const path = require('path'); const dir = '/your/target/directory'; const CONCURRENCY_LIMIT = 50; // 可根据机器性能调整 fs.readdir(dir, {}, (err, files) => { if (err) throw err; async.eachLimit(files, CONCURRENCY_LIMIT, (file, callback) => { fs.readFile(path.join(dir, file), 'utf8', (err, data) => { if (err) { console.error(`处理文件${file}出错:`, err); return callback(err); } // 处理文件并插入数据库的逻辑 // ... callback(); // 告诉async这个文件处理完成了,可以处理下一个 }); }, (err) => { if (err) { console.error('批量处理出错:', err); } else { console.log('所有文件处理完成!'); } }); });
方案B:用Promise+async/await实现限流(无需额外库)
如果不想引入第三方库,可以用原生的Promise来实现分批处理:
const fs = require('fs').promises; const path = require('path'); const dir = '/your/target/directory'; const BATCH_SIZE = 50; async function processFiles() { const files = await fs.readdir(dir); // 分批处理文件 for (let i = 0; i < files.length; i += BATCH_SIZE) { const batch = files.slice(i, i + BATCH_SIZE); // 等待当前批次所有文件处理完成 await Promise.all(batch.map(async (file) => { const data = await fs.readFile(path.join(dir, file), 'utf8'); // 处理文件并插入数据库的逻辑 // ... })); console.log(`已完成${Math.min(i + BATCH_SIZE, files.length)}/${files.length}个文件`); } console.log('所有文件处理完成!'); } processFiles().catch(err => console.error('处理出错:', err));
2. 临时提高系统的文件描述符限制
Mac OS X默认的文件描述符上限比较低,你可以临时调高它,不过这只是治标不治本的方法(重启后会恢复默认):
在终端执行:
ulimit -n 65536
然后再运行你的Node.js程序。如果需要永久修改,需要修改系统配置文件,不过这个操作比较复杂,而且不推荐——因为即使调高了限制,一次性发起22万请求也会给系统带来很大压力,还是控制并发更合理。
3. 改用同步读取(不推荐)
虽然fs.readFileSync不会出现EMFILE问题(因为它读完一个文件才会处理下一个),但同步读取会阻塞Node.js的事件循环,处理22万文件会非常慢,而且期间程序无法响应其他请求,所以只适合小批量文件,不推荐用于你的场景。
内容的提问来源于stack exchange,提问作者punkish
相关产品推荐
相关产品推荐

