[英]Nodejs out of memory on pipe for pdfMake
我有一個Nodejs服務器,用於創建大約1200個pdf表單,以后可以由客戶端下載。 它們是使用pdfmake創建的,然后輸出到服務器文件夾。 當我執行大約350個文檔中編寫的代碼時,Nodejs的內存不足。 我知道一定有更好的保存方法,但是我似乎無法弄清楚。
貓鼬查詢中的數據數組映射將調用以下方法。 創建和保存表單的相關代碼如下:
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();
};
有沒有其他方法可以保存文件,而不強迫它們進入流並且不會保留在內存中?
在得出答案之前,我將根據問題中的信息做出一個巨大的假設。 問題狀態create about 1200 pdf forms
。 這意味着我在函數whichForm
假設參數certList
是1200個項目的數組。 或者我應該說1200個將調用createOAReport
方法的項目。 你明白了。 我假設問題是我們在該Array.map
方法中調用該方法來創建PDF 1200次。 考慮到代碼的問題和上下文,我認為這很有意義。
繼續回答。 主要問題是您不只是嘗試創建1200個pdf。 您正在嘗試異步創建1200 pdf,這當然會給試圖一次完成所有工作的系統帶來壓力。 在像Node.js這樣的單線程系統上可能甚至更多。
一種簡單易用的解決方案是僅增加Node.js的內存。 通過使用--max-old-space-size
標志並在運行節點命令時以MB為單位設置內存大小。 您可以在本教程中找到有關此內容的更多信息。 但是簡短的版本是類似node --max-old-space-size=8192 main.js
。 這會將Node.js的內存大小增加到8192 MB或8 GB。
這種方法很少有問題。 主要是它不是超級可擴展的。 如果某天您要創建5000個pdf,該怎么辦? 您必須再次增加該內存大小。 也許會增加正在運行的機器的規格。
第二種解決方案(實際上可以與第一種解決方案一起使用)是使該過程不是異步的。 根據許多因素以及當前系統的優化程度,很可能會增加創建所有這些PDF所需的時間。
這個過程有點createOAReport
兩個步驟來進行編碼。首先是設置您的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數量會更容易。
在所有這些過程中添加一些錯誤處理也是一個好主意。
注意我實際上根本沒有測試過此代碼,因此可能存在一些錯誤。 但是總體思想和原則仍然適用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.