lambda nodejs 函数降低冷启动时间的最佳实践

news2024/9/30 5:34:31

bg

lambda nodejs 函数降低冷启动时间的最佳实践

  • lambda nodejs 函数降低冷启动时间的最佳实践
    • 前言
    • 什么是冷启动时间
    • 打包服务端 js
    • 什么是 inline
    • 进一步封装的打包工具
    • 存在的弊端以及解决方案
    • Next Chapter
    • 完整示例及文章仓库地址

前言

本文章的思路,继承发展自这两篇文章:

  • serverless 降低冷启动时间的探索 - 服务端打包 node_modules
  • Nodejs云函数冷启动时间的优化

这里要感谢这2篇文章的作者:ice breaker2年前就提供了这么优秀的思路和解决方案了,真是忍不住给他点赞呀。

首先在看这篇文章之前,我先必须给你介绍一个概念,就是 冷启动时间

什么是冷启动时间

这个特性各个服务商的 serverless 云函数都存在,这个和 函数容器的生命周期 息息相关。

Lambda 为例,Lambda 生命周期可以分为三个阶段:

  • Init:在此阶段,Lambda 会尝试解冻之前的执行环境,若没有可解冻的环境,Lambda 会进行资源创建,下载函数代码,初始化扩展和 Runtime,然后开始运行初始化代码(主程序外的代码)。
  • Invoke:在此阶段,Lambda 接收事件后开始执行函数。函数运行到完成后,Lambda 会等待下个事件的调用。
  • Shutdown:如果 Lambda 函数在一段时间内没有接收任何调用,则会触发此阶段。在 Shutdown 阶段,Runtime 关闭,然后向每个扩展发送一个 Shutdown 事件,最后删除环境。

冷启动

当您在触发 Lambda 时,若当前没有处于激活阶段的 Lambda 可供调用,则 Lambda 会下载函数的代码并创建一个 Lambda 的执行环境。从事件触发到新的 Lambda 环境创建完成这个周期通常称为 “冷启动时间”。显然,这个时间肯定是越短越好的。

这里可以参考 AWS 这篇博客 以获取更多信息。

其中 AWS 提供的几种降低冷启动时间的方式有:

  • 选择合适的编程语言 (我们大部分情况无法更换)
  • 减小应用程序大小
  • 预热 (定时触发器防止回收和预置并发,保留实例)
  • JVM 分层编译(java特供)

其中可行性最高的方式,就是本篇文章要探讨的 减小应用程序大小

打包服务端 js

回到正题,为什么要去打包服务端 js 代码呢? 用 layer 的方式不是蛮好吗?

这里必须要知道的一点是,函数冷启动的时间,是和整体运行以及其依赖的代码包大小,是息息相关的。

比如上篇文章中的示例,我们把 uuid 这个依赖给做成 layer 上传了上去,但是你有没有想过,既然 uuid 的所有实现都是 js,为什么不把它整个源代码,打入我们的函数构建产物中呢?这样还省了依赖一个 layer 呢。

同样的道理,我们函数也可以把 express,lodash 等等依赖,全部打入我们的函数包里去,以减小整体代码包的体积。

这就像我们在写前端项目那样,本质上也会把所有运行时代码,全部给 inline 打成一个一个 chunk 到各个 js 里面去,毕竟浏览器可没有什么 node_modules 的加载机制。你写 vue 还是写 react 都是直接加载它们inline的整个代码的。

什么是 inline

这里给一个例子: 原先你的ts代码可能是这样写的:

import express from 'express'

然后经过 tsc,产物变成了这样:

// commonjs format
const express = require('express')

而假如走 inline 那产物中就不会出现 express,而是直接把 express 相关的代码全部给打了进来,效果类似于:

// 部分代码
var require_express = __commonJS({
  "../../node_modules/.pnpm/express@4.18.2/node_modules/express/lib/express.js"(exports, module2) {
    "use strict";
    var bodyParser2 = require_body_parser();
    var EventEmitter = require("events").EventEmitter;
    var mixin = require_merge_descriptors();
    var proto = require_application();
    var Route = require_route();
    var Router = require_router();
    var req = require_request2();
    var res = require_response2();
    exports = module2.exports = createApplication;
    function createApplication() {
      var app2 = function(req2, res2, next) {
        app2.handle(req2, res2, next);
      };
      mixin(app2, EventEmitter.prototype, false);
      mixin(app2, proto, false);
      app2.request = Object.create(req, {
        app: { configurable: true, enumerable: true, writable: true, value: app2 }
      });
      app2.response = Object.create(res, {
        app: { configurable: true, enumerable: true, writable: true, value: app2 }
      });
      app2.init();
      return app2;
    }
    exports.application = proto;
    exports.request = req;
    exports.response = res;
    exports.Route = Route;
    exports.Router = Router;
    exports.json = bodyParser2.json;
    exports.query = require_query();
    exports.raw = bodyParser2.raw;
    exports.static = require_serve_static();
    exports.text = bodyParser2.text;
    exports.urlencoded = bodyParser2.urlencoded;
    var removedMiddlewares = [
      "bodyParser",
      "compress",
      "cookieSession",
      "session",
      "logger",
      "cookieParser",
      "favicon",
      "responseTime",
      "errorHandler",
      "timeout",
      "methodOverride",
      "vhost",
      "csrf",
      "directory",
      "limit",
      "multipart",
      "staticCache"
    ];
    removedMiddlewares.forEach(function(name) {
      Object.defineProperty(exports, name, {
        get: function() {
          throw new Error("Most middleware (like " + name + ") is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.");
        },
        configurable: true
      });
    });
  }
});
// ......

显然这种方式下,可以打出更小更单一的包,因为所有的碎片化的 js 依赖,都被打成到了单文件里面去了,减少了 io 的次数,而且还能够一起制定策略,如 split chunk or compress

要实现这种效果,其实基本上所有流行的打包工具都内置了这个功能。

比如 webpack/esbuild,当然 rollup 也有对应的插件支持,@rollup/plugin-node-resolve 就是它的实现方式之一。

其中笔者文章开头提到的 2 篇文章里的实现方式,就是基于 rollup 这个工具去实现的,在此不再叙述。在 2 年后的今天,也很高兴看到了更多,基于它们的开箱即用的工具,使得我们不需要安装大量的插件或者编写复杂的打包配置,就能实现同样的效果,让我们一起来看看吧。

进一步封装的打包工具

在过去,比较构建库比较流行 rollup 或者 esbuild,不过现在有了基于它们更进一步的打包工具: unbuild / tsup

其中 unbuild 这个工具先跳过,我们会在 monorepo 章节中介绍它,这里我们主要来介绍 tsup 在函数打包中的用法。

tsupesbuild 进一步封装而来。它太开箱即用了,甚至可以 0 config。它本身的打包配置,主要是基于约定的:

比如它会默认去 inline 我们所有在运行时引用的,但是却是注册在 devDependencies 里的包

dependencies 里的包,则是被默认加入了 external 中,不进行 node resolve

这个约定实际上很简单却很实用,类似于这样 rollup 的配置:

// rollup.config.ts
import { readFileSync } from 'node:fs'
const pkg = JSON.parse(
  readFileSync('./package.json', {
    encoding: 'utf8'
  })
)
const dependencies = pkg.dependencies as Record<string, string> | undefined
const config: RollupOptions = {
  // ...
  plugins: [
    json(),
    nodeResolve(),
    commonjs(),
    typescript()
  ],
  external: [...(dependencies ? Object.keys(dependencies) : [])]
}

显然对比起来 tsup.config.ts 文件的配置就 简洁 多了,见下:

// tsup.config.ts
import { defineConfig } from 'tsup'
const isDev = process.env.NODE_ENV === 'development'
export default defineConfig({
  entry: ['src/index.ts'],
  splitting: false,
  sourcemap: isDev,
  clean: true,
  // external: []
})

接着注册指令即可 package.json 里的 npm scripts (这里我加了 cross-env 包和 NODE_ENV 环境变量是为了做更多,比如条件编译等等事情)

  "scripts": {
    "dev": "cross-env NODE_ENV=development tsup --watch",
    "build": "cross-env NODE_ENV=production tsup",
  },

这样执行这命令,你的函数就被打入 dist/inde.js 里了,赶紧去检查一下产物吧。

存在的弊端以及解决方案

上面说的这种打包方式其实存在一定的弊端:

  1. 首先,它改变了第三方依赖的目录结构
  2. 其次它只能处理一些 js 依赖,不使用特定的插件,常常会出现一些非 js 依赖的缺失

这个问题的严重性会导致一系列的问题,比如某些包源代码里面是依赖文件目录的:

// node_modules/some-lib/dist/index.js
const defaultDbFile = path.resolve(__dirname, '../data/ip2region.xdb')

那这行代码被打入我们函数包就会有问题,因为目录结构被破坏了,这会导致第三方包调用出错。

目录结构的变化如下:

(打包前)本地可以运行的目录结构

  • src
    • index.ts
  • node_modules
    • some-lib
      • dist
        • index.js
      • data
        • ip2region.xdb

(打包后)

  • dist
    • index.js
  • node_modules
    • some-lib
      • dist (用不到了)
        • index.js
      • data
        • ip2region.xdb

注意此时 node_modules/some-lib/dist/index.js 里的代码inline到了 dist/index.js 里去了。但是 defaultDbFile 的引用的路径却变化了,因为 __dirname 变化了,此时正确的路径实际上是 path.resolve(__dirname, '../node_modules/some-lib/data/ip2region.xdb')

那么如何解决呢?目前比较好的解决方案,是使用 external 的方式,不去主动 inline 那些可能会导致问题的包,并把那些包挑出来,做成 layer 再进行绑定。幸好这种包是小概率会遇到的,测试环境很容易发现问题。

Next Chapter

现在你已经学会了打包服务端代码的策略。

下一篇,《更灵活的 serverless framework 配置文件》中,将会详细介绍如何让你的部署配置文件变得灵活起来。

完整示例及文章仓库地址

https://github.com/sonofmagic/serverless-aws-cn-guide

如果你遇到什么问题,或者发现什么勘误,欢迎提 issue 给我

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/984162.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vite vue项目 运行时 \esbuild\esbuild.exe 缺失 错误码 errno: -4058, code: ‘ENOENT‘,

vite vue项目运行 npm run dev 报错某个模块启动文件丢失信息 D:\PengYe_code\2\vite-vue3-admin>npm run dev> vite-vue3-admin1.0.2 dev > vitenode:events:504throw er; // Unhandled error event^Error: spawn D:\PengYe_code\2\vite-vue3-admin\node_modules\vi…

jupyter 添加中文选项

文章目录 jupyter 添加中文选项1. 下载中文包2. 选择中文重新加载一下&#xff0c;页面就变成中文了 jupyter 添加中文选项 1. 下载中文包 pip install jupyterlab-language-pack-zh-CN2. 选择中文 重新加载一下&#xff0c;页面就变成中文了 这才是设置中文的正解&#xff…

出现Browse information of one xxxx解决方法

不良现象如下&#xff1a; Browse information of one or more files is not available: Doing a project rebuild might fix this. 解决的方法&#xff1a;将C文件里面的内容全部注释掉&#xff0c;再编译正常。 然后再将注释掉的代码打开&#xff0c;再次编译就正常了。

【笔试强训选择题】Day35.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01; 文章目录 前言 一、Da…

什么是互联网打工人都需要知道的API?

我们生活在一个科技主导的世界。在这里&#xff0c;数据无处不在。作为许多不同产品的用户&#xff0c;我们所追寻的不再是某一个能将工作完成的最佳产品&#xff0c;而是一个不仅能有效完成工作&#xff0c;同时也与我们所使用的其他工具完美兼容的产品。因此&#xff0c;了解…

08-JVM垃圾收集器详解

上一篇&#xff1a;07-垃圾收集算法详解 如果说收集算法是内存回收的方法论&#xff0c;那么垃圾收集器就是内存回收的具体实现。 虽然我们对各个收集器进行比较&#xff0c;但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现&#xff0c;更加没…

Web安全研究(四)

No Honor Among Thieves: A Large-Scale Analysis of Malicious Web Shells Stony Brook University Ruhr-University Bochum 数据集地址&#xff1a;https://github.com/HACHp1/CWSOGG_dataset Web shell作为恶意脚本&#xff0c;攻击者将其上传到被攻陷的Web服务器&#xff…

iTunes备份文件在哪?苹果手机怎么恢复iTunes备份?

iTunes是苹果手机的一个常见应用&#xff0c;很多小伙伴都使用它来备份手机上的重要数据。通过iTunes备份数据到电脑后还可以进行随时管理和查看。itunes备份文件在哪&#xff1f;手机数据丢失怎么恢复iTunes备份&#xff1f;接下来&#xff0c;本文将给大家介绍一下&#xff0…

javaweb03-js基础

文本中涉及的一些基础介绍&#xff0c;不是全的。只写一些最常见、最经常使用的&#xff0c;其他的想了解可以自行查找资料。 前言&#xff1a; script引入 内部引用 script 外部引用 script:src 一、js语法 1.编写语法 &#xff08;1&#xff09;区分大小写&#xff0c;建议…

达之云BI平台助力中国融通集团陕西军民服务社有限公司实现数字化运营

中国融通集团陕西军民服务社是一家大型综合类零售购物中心&#xff0c;公司目前管理系统运行了10年左右&#xff0c;面临系统新零售支持发展严重滞后&#xff0c;行业主流应用落地困难&#xff0c;如线上业务、到家业务、全渠道营销、电子发票、自助收银、扫码购、无感停车、未…

拦截器失效和工具类中静态变量注入失败的问题

拦截器失效和工具类中静态变量注入失败的问题 文章目录 拦截器失效和工具类中静态变量注入失败的问题1.拦截器配置冲突2.路径配置错误3.关于工具类中Maper注入失效的问题解决办法1&#xff1a;手动赋值给静态变量 问题描述&#xff1a;项目中需要设置多个拦截器拦截不同路径&am…

【Java实战项目】【超详细过程】—大饼的图片服务器3(ImageDao类详解)

ImageDao详解 一、向数据库中写入图片属性1.与数据库建立连接2.创建并拼接SQL语句3.执行SQL语句4.定义异常类JavaImageServerException5.关闭数据库连接6.写入图片的完整代码 二、查找数据库中所有图片属性1.与数据库建立连接2.创建并拼接SQL语句3.执行SQL语句4.处理结果集5.关…

python+django吉他乐谱推荐交流网站的实现vue

而吉他乐谱推荐交流网站能很好地解决这一问题&#xff0c;轻松应对乐谱推荐&#xff0c;既能提高用户对乐谱评论&#xff0c;又能加快乐谱推荐交流网站的效率&#xff0c;取代人工管理是必然趋势。 本吉他乐谱推荐交流网站以Django作为框架&#xff0c;B/S模式以及MySql作为后台…

Vue错误记录

文章目录 1. 项目build的时候报错Warning: Accessing non-existent property cat of module exports inside circular dependency2. WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not …

嵌入式linux(imx6ull)下RS485接口配置

接口原理图如下&#xff1a; 由原理图可知收发需要收UART_CTS引脚控制,高电平时接收&#xff0c;低电平时发送。通过查看Documentation/devicetree/bindings/serial/fsl-imx-uart.yaml和Documentation/devicetree/bindings/serial/rs485.yaml两个说明文档&#xff0c;修改设备树…

Visual Stadio使用技巧

C语言调试技巧 Debug 和 Release 的介绍 Debug&#xff1a;通常称为调试版本&#xff0c;它包含调试信息&#xff0c;并且不作任何优化&#xff0c;便于程序员调试&#xff08;可调试&#xff09;。 Release&#xff1a;通常称为发布版本&#xff0c;它往往时进行了各种优化&a…

照片能做真人三维建模?

易模App开启真人手办定制以来&#xff0c;许多用户朋友在积极尝试&#xff0c;更有用户反馈了一种可以使模型成果更精致的建模方式——螺旋连拍。 螺旋连拍使用易模App人像模式自定义方法&#xff0c;上传拍好的真人照片即可AI建模&#xff0c;操作方法简单。那么如何拍摄用于建…

9月5日上课内容 第一章 NoSQL之Redis配置与优化

本章结构 关系型数据库和非关系型数据库 概念介绍 ●关系型数据库&#xff1a; 关系型数据库是一个结构化的数据库&#xff0c;创建在关系模型&#xff08;二维表格模型&#xff09;基础上&#xff0c;一般面向于记录。 SQL 语句&#xff08;标准数据查询语言&#xff09;就是…

【TSN】(一)中英译文

【Two Stream Net】 一&#xff0c;双语翻译 文章目录 【Two Stream Net】Abstract1 Introduction1.1 Related work 2 Two-stream architecture for video recognition3 Optical flow ConvNets3.1 ConvNet input configurations3.2 Relation of the temporal ConvNet archite…

数据可视化、BI和数字孪生软件:用途和特点对比

在现代企业和科技领域&#xff0c;数据起着至关重要的作用。为了更好地管理和理解数据&#xff0c;不同类型的软件工具应运而生&#xff0c;其中包括数据可视化软件、BI&#xff08;Business Intelligence&#xff09;软件和数字孪生软件。虽然它们都涉及数据&#xff0c;但在功…