我有一个Nodejs服务器,用于创建大约1200个pdf表单,以后可以由客户端下载。它们是使用pdfmake创建的,然后输出到服务器文件夹。当我执行大约350个文档中编写的代码时,Nodejs的内存不足。我知道必须有一种更好的保存方法,但是我似乎无法弄清楚。
以下方法被Mongoose查询中的一组数据映射所调用。创建和保存表单的相关代码如下:
const whichForm = certList => {
certList.map(cert => {
if (cert.Cert_Details !== null) {
switch (cert.GWMA) {
case 'OA':
case 'PC':
// Don't provide reports for Feedlots
if (cert.Cert_Details.cert_type !== null) {
if (cert.Cert_Details.cert_type === 'Irrigation') {
createOAReport(cert);
}
}
break;
case 'FA':
// Don't provide reports for Feedlots
if (cert.Cert_Details.cert_type === 'Irrigation') {
createFAReport(cert);
}
break;
}
}
}
}
不同文件:
const PdfPrinter = require('pdfmake/src/printer');
const fs = require('fs');
const createOAReport = data => {
console.log('PC or OA Cert ', data.Cert_ID);
// console.log(data);
let all_meters_maint = [];
data.Flowmeters.map(flowmeter => {
// Each Flow meter
// console.log(`Inside Flowmeter ${flowmeter}`);
if (flowmeter.Active === true) {
let fm_maint = [];
fm_maint.push({
text: `Meter Serial Number: ${flowmeter.Meter_Details.Serial_num}`
});
fm_maint.push({
text: `Type of Meter: ${flowmeter.Meter_Details.Manufacturer}`
});
fm_maint.push({ text: `Units: ${flowmeter.Meter_Details.units}`});
fm_maint.push({ text: `Factor: ${flowmeter.Meter_Details.factor}`});
all_meters_maint.push(fm_maint);
}
docDefinition.content.push({
style: 'tableExample',
table: {
widths: [200, 200, '*', '*'],
body: all_meters_maint
},
layout: 'noBorders'
});
const fonts = {
Roboto: {
normal: path.join(__dirname, '../', '/fonts/Roboto-
Regular.ttf'),
bold: path.join(__dirname, '../', '/fonts/Roboto-Medium.ttf'),
italics: path.join(__dirname, '../', '/fonts/Roboto-Italic.ttf'),
bolditalics: path.join(__dirname, '../', '/fonts/Roboto-
MediumItalic.ttf')
}
};
const printer = new PdfPrinter(fonts);
const pdfDoc = printer.createPdfKitDocument(docDefinition);
// Build file path
const fullfilePath = path.join(
__dirname,
'../',
'/public/pdffiles/',
`${data.Cert_ID}.pdf`
);
pdfDoc.pipe(fs.createWriteStream(fullfilePath));
pdfDoc.end();
};
是否存在另一种方法来保存文件,这些文件不会强迫它们进入流并且不会保留在内存中?
答案 0 :(得分:0)
在得出答案之前,我根据问题中的信息做出一个巨大的假设。问题指出create about 1200 pdf forms
。这意味着我假设函数whichForm
中的参数certList
是1200个项目的数组。或者我应该说1200个将调用createOAReport
方法的项目。你明白了。我假设问题是我们正在调用该方法在该Array.map
方法中创建1200次PDF。考虑到代码的问题和上下文,我认为这很有意义。
继续回答。主要问题是您不只是尝试创建1200个pdf。您正在尝试异步创建1200 pdf,这当然会给试图一次完成所有工作的系统带来压力。在像Node.js这样的单线程系统上,甚至更是如此。
简单易用的解决方案是仅增加Node.js的内存。通过使用--max-old-space-size
标志并在运行节点命令时以MB为单位设置内存大小。您可以在this tutorial上找到有关此信息的更多信息。但是简短的版本是像node --max-old-space-size=8192 main.js
这样的命令。这样会将Node.js的内存大小增加到8192 MB或8 GB。
该方法的几个问题。主要是它不是超级可扩展的。如果某天您要创建5000个pdf,该怎么办?您必须再次增加该内存大小。也许会增加正在运行的机器的规格。
第二种解决方案(实际上可以与第一种解决方案一起使用)是使该过程不异步。根据许多因素以及当前系统的优化程度,很可能会增加创建所有这些PDF所需的时间。
此过程有点象需要两个步骤才能对其进行编码。首先是设置您的createOAReport
函数以返回一个承诺以指示完成时间。第二步是更改whichForm
函数,以限制在任何单个时间点可以异步运行多少个项目。
您当然必须在系统上进行操作,以确定一次要运行多少个项目而不会使系统过载。微调这个数字并不是我关注的重点,当然您也可以通过增加为Node.js提供的内存来增加该数字。
当然,有很多方法可以做到这一点。我有一些方法的想法,这些想法比我将在此处展示的方法更好,但要复杂得多。限制一次运行多少个项目的基本思想仍然是相同的。您可以对其进行优化以满足您的需求。
我以前已经开发过像这样的系统,但是我不认为我做这件事的方法是最好或最干净的方法。但是在这个问题的结尾,我为您的示例附加了一些示例代码,试图说明我的观点。
const _ = require('lodash');
const MAX_RUNNING_PROMISES = 10; // You will have to play with this number to get it right for your needs
const whichForm = async certList => {
// If certList is ["a", "b", "c", "d"]
// And we run the following function with MAX_RUNNING_PROMISES = 2
// array would equal [["a", "b"], ["c", "d"]]
certList = _.chunk(certList, MAX_RUNNING_PROMISES);
// Of course you can use something other than Lodash here, but I chose it because it's the first thing that came to mind
for (let i = 0; i < certList.length; i++) {
const certArray = certList[i];
// The following line will wait until all the promises have been resolved or completed before moving on
await Promise.all(certArray.map(cert => {
if (cert.Cert_Details !== null) {
switch (cert.GWMA) {
case 'OA':
case 'PC':
// Don't provide reports for Feedlots
if (cert.Cert_Details.cert_type !== null) {
if (cert.Cert_Details.cert_type === 'Irrigation') {
return createOAReport(cert);
}
}
break;
case 'FA':
// Don't provide reports for Feedlots
if (cert.Cert_Details.cert_type === 'Irrigation') {
return createFAReport(cert);
}
break;
}
}
}));
}
}
然后输入其他文件。我们只需要转换它即可返回承诺。
const PdfPrinter = require('pdfmake/src/printer');
const fs = require('fs');
const createOAReport = data => {
return new Promise((resolve, reject) => {
console.log('PC or OA Cert ', data.Cert_ID);
// console.log(data);
let all_meters_maint = [];
const flowmeter = data.Flowmeters[0];
if (flowmeter.Active === true) {
let fm_maint = [];
fm_maint.push({
text: `Meter Serial Number: ${flowmeter.Meter_Details.Serial_num}`
});
fm_maint.push({
text: `Type of Meter: ${flowmeter.Meter_Details.Manufacturer}`
});
fm_maint.push({
text: `Units: ${flowmeter.Meter_Details.units}`
});
fm_maint.push({
text: `Factor: ${flowmeter.Meter_Details.factor}`
});
all_meters_maint.push(fm_maint);
}
docDefinition.content.push({
style: 'tableExample',
table: {
widths: [200, 200, '*', '*'],
body: all_meters_maint
},
layout: 'noBorders'
});
const fonts = {
Roboto: {
normal: path.join(__dirname, '../', '/fonts/Roboto-Regular.ttf'),
bold: path.join(__dirname, '../', '/fonts/Roboto-Medium.ttf'),
italics: path.join(__dirname, '../', '/fonts/Roboto-Italic.ttf'),
bolditalics: path.join(__dirname, '../', '/fonts/Roboto-MediumItalic.ttf')
}
};
const printer = new PdfPrinter(fonts);
const pdfDoc = printer.createPdfKitDocument(docDefinition);
// Build file path
const fullfilePath = path.join(
__dirname,
'../',
'/public/pdffiles/',
`${data.Cert_ID}.pdf`
);
pdfDoc.pipe(fs.createWriteStream(fullfilePath));
pdfDoc.on('finish', resolve); // This is where we tell it to resolve the promise when it's finished
pdfDoc.end();
});
};
在真正回答这个问题之后,我才意识到我最初的假设是错误的。由于其中一些pdf可能是在第二个功能和data.Flowmeters.map
系统中创建的。因此,尽管我不打算演示它,但是您也必须将我在整个答案中给出的相同想法应用于该系统。现在,我已经删除了该部分,而只是使用该数组中的第一项,因为这只是一个示例。
一旦您对此有所了解,并且只拥有一个处理创建PDF的功能,而在整个地方都没有那么多的.map
方法调用,您可能希望重新构建代码。提取.map
方法,并将其与PDF创建过程分开。这样,限制一次创建的PDF数量会更容易。
在所有这些过程中添加一些错误处理也是一个好主意。
注意我实际上根本没有测试过此代码,因此可能存在一些错误。但是总体思想和原则仍然适用。