• 如何使用 Lambda 导出 EXCEL,并且实现本地调试


    因为Nodejs在使用第三方Typeorm库过程中,当要查询很多表,一次查询大量数据是时候会占用高额的内存。当项目已经运行上线一两年,避免大动干戈,一直在想有没有其他办法把消耗资源太大的代码提取出来,就像是给电脑之前的风冷换成高级水冷。

    解决问题的过程

    问题: AWS EC2 经常卡死,可知的事情为内存过高,cpu突然飙升,事情经常发生在客户在导出excel时有发生。

    分析问题: 导出和查询展示数据使用了同一个api和查询方式,是否为exceljs这个导出插件消耗了过多资源呢。

    为了方便监听内存变化,本地并没有使用pm2第三方工具,写了一段简单的代码。

    setInterval(()=>{
       const used = process.memoryUsage().heapUsed / 1024 / 1024;
       console.log(`The memory used: ${Math.round(used * 100) / 100} MB`);
    }, 200)
    
    • 1
    • 2
    • 3
    • 4

    第一次运行,当导出时,内存已经达到2G。

    一开始盲目认为是excel导出导致,跟exceljs这个第三方库相关。
    所以换了stream导出方式,参考了很多github的提问回答和stackoverflow一些大神的分享方式。

    核心是将
    const workbook = new ExcelJS.Workbook(); 
    改为了
    const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({
            stream: response, // nodejs的res
            useSharedStrings: false,
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    第二次运行,内存依然没有变化,白白花费了很久的时间,好在对exceljs又十分的深入了一遍。

    屏蔽掉所有的代码,只留了typeorm查询的部分,打印sql,发现内存占用很大。既然orm比较复杂查询涉及了十几张表。要是单独抽离一个项目关联关系、entity、DTO、token验证、AUTH管道、加密等等,还有很多业务逻辑,非常复杂,短时间也不现实。

    所以想到一个折中方案,只导出sql,然后放到Lambda服务上去做数据处理导出。

    本地调试Lambda

    一般我们创建AWS Lambda,需要登录aws的线上环境。之前要创建一个Lambda非常复杂,本地创建一个Lambda文件夹,书写完代码,无法本地测试,需要上传zip,然后在Lambda当前函数页面选择解压,测试再运行测试。一个简单的输出“Hello word”,可能需要好几分钟才能实现。再加上国内访问aws的网络,一天下来有效工作很低。

    使用VScode安装 [AWS Toolkit for Visual Studio Code] 实现本地调试。

    插件实际相当于实现了AWS Command Line Interface一些基本的配置集成到了VScode。使用AWS cli 相当于将所有的aws控制台操作都变成了命令行的方式,我们完全可以自己编写一套shell脚本实现lambda 的创建等操作。

    aws lambda create-function --function-name my-function \
    --zip-file fileb://function.zip --handler index.handler --runtime nodejs12.x \
    --role arn:aws:iam::123456789012:role/lambda-ex
    
    aws lambda invoke --function-name my-function out --log-type Tail
    
    • 1
    • 2
    • 3
    • 4
    • 5

    言归正传下面讲一讲AWS Toolkit。

    1. 首先创建IAM用户,Create New Access Key以创建一对Access Key ID 及Secret Access Key

    2. AWS SAM CLI Install for MAC OS

    3. Install docker

    4. 在vscode 应用里找到AWS Toolkit安装。安装成功之后:

      • Command+P 找到 => AWS: Create Credentials Profile
        根据提示输入 AWS Access Key 和 Secret Access Key
      • Command+P 找到 => AWS: Connect to AWS 然后会得到如下图:
    5. 打开Lambda列表右键下载到本地目录,到此我们已经成功了一大半。

    6. 假如你下载了SAM CLI, 那么选择本地下载后的Lambda函数,然后点击左侧debug,选择debug。

    sam-cli的使用

    这里你也可以使用Sam cli 终端命令自己测试一个lambda函数,也非常的cool。

    1. sam init
      现在,您将拥有如下文件结构:

    2. sam-app/template.yaml 您可以在这里配置API,例如HTTP方法、URL、要运行的代码的位置等。

      HelloWorldFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
          CodeUri: hello_world/
          Handler: app.lambda_handler
          Events:
            HelloWorld:
              Properties:
                Path: /hello
                Method: get
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 构建应用程序,然后您就可以运行本地API了
    $ cd sam-app/
    $ sam build
    $ sam local start-api
    
    • 1
    • 2
    • 3

    访问您的本地API

    请注意,第一次运行此操作可能需要一些时间,因为这是在设置API环境时进行的,例如创建映像和运行容器。任何后续呼叫都应该已经很快了。
    $ curl http://127.0.0.1:3000/hello
    {“message”: “hello world”}

    Lambda配置typescript
    1. 删除文件夹默认的test 和 app.js
    2. 安装
    npm install --save-dev typescript @types/aws-lambda @types/node
    
    • 1
    1. 编写package.json
    "scripts": { "tsc-init": "tsc --init", "compile": "tsc" }
    
    • 1
    1. npm run tsc-init
    2. 添加app.ts
    import { 
      APIGatewayProxyEvent, 
      APIGatewayProxyResult } 
    from "aws-lambda/trigger/api-gateway-proxy";
    
    export const lambdaHandler = async (
       event: APIGatewayProxyEvent
    ): Promise => {
      return {
        statusCode: 200,
        body: JSON.stringify({'test' : 123})
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. npm run compile
    2. sam build
    3. 编辑event.json,模拟先是的请求request
    sam local invoke -e events/event.json
    
    • 1
    导出excel 并上传到s3
    await uploadToS3({
          Bucket: process.env.AWS_BUCKET_NAME,
          Key: `${objectKey}.xlsx`,
          ContentType:
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
          Body: await excelSheet.workbook.xlsx.writeBuffer()
        });
    
    //Get signed url with an expiry date
    let downloadURL = await getS3SignedUrl({
          Bucket: process.env.AWS_BUCKET_NAME,
          Key: `${objectKey}.xlsx`,
          Expires: 3600 //this is 60 minutes, change as per your requirements
        });
    
    export async function uploadToS3(s3Data: S3.PutObjectRequest) { 
        try {
          return await s3.upload(s3Data).promise();
        } catch (error) {
          console.log(error);
          return error;
        }
    }
    
    export async function getS3SignedUrl(params: any): Promise {
        try {
          return s3.getSignedUrl("getObject", {
            Bucket: params.Bucket,
            Key: params.Key,
            Expires: params.Expires,
          });
        } catch (error) {
          console.log(error);
          throw error;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
  • 相关阅读:
    图像分割 - Canny边缘检测
    win10安装sql Server2016与ssms可视化
    flutter 递归
    [Model.py 02]Modification for creating terrain matrix2.
    FPGA的基础结构
    js实现pdf打印的功能
    模拟实现应用层协议
    Linux必备技能:掌握的必会命令
    掌握这些技巧,轻松批量压缩视频大小
    LeetCode 567. 字符串的排列
  • 原文地址:https://blog.csdn.net/xmqywx/article/details/126350222