簡體   English   中英

Node.js內存不足以進行pdfMake

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM