### Serverless
無服務器計算是一種云計算執行模型,其中云提供商按需分配機器資源,代表客戶處理服務器。當應用程序未使用時,不會為應用程序分配計算資源。定價基于應用程序消耗的實際資源量(\[來源\](https://en.wikipedia.org/wiki/Serverless\_computing))。
使用\*\*無服務器架構\*\*,您可以完全專注于應用程序代碼中的各個功能。 AWS Lambda、Google Cloud Functions 和 Microsoft Azure Functions 等服務負責所有物理硬件、虛擬機操作系統和 Web 服務器軟件管理。
> \*\*提示\*\*本章不涵蓋無服務器功能的優缺點,也不深入探討任何云提供商的細節。
#### 冷啟動
冷啟動是您的代碼在一段時間內第一次執行。根據您使用的云提供商,它可能跨越幾個不同的操作,從下載代碼和引導運行時到最終運行您的代碼。此過程會增加\*\*顯著的延遲\*\*,具體取決于多種因素、語言、應用程序所需的包數量等。
冷啟動很重要,盡管有些事情超出了我們的控制范圍,但我們仍然可以做很多事情來使其盡可能短。
雖然您可以將 Nest 視為一個成熟的框架,旨在用于復雜的企業應用程序,但它也**適用于許多“更簡單”的應用程序**(或腳本)。例如,通過使用\[獨立應用程序\](https://docs.nestjs.com/standalone-applications) 功能,您可以在簡單的工作程序、CRON 作業、CLI 或無服務器功能中利用 Nest 的 DI 系統。
#### 基準
為了更好地了解在無服務器函數的上下文中使用 Nest 或其他知名庫(如`express`)的成本是多少,讓我們比較一下 Node 運行時需要多少時間來運行以下腳本:
~~~typescript
// #1 Express
import * as express from 'express';
async function bootstrap() {
const app = express();
app.get('/', (req, res) => res.send('Hello world!'));
await new Promise<void>((resolve) => app.listen(3000, resolve));
}
bootstrap();
// #2 Nest (with @nestjs/platform-express)
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { logger: ['error'] });
await app.listen(3000);
}
bootstrap();
// #3 Nest as a Standalone application (no HTTP server)
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AppService } from './app.service';
async function bootstrap() {
const app = await NestFactory.createApplicationContext(AppModule, {
logger: ['error'],
});
console.log(app.get(AppService).getHello());
}
bootstrap();
// #4 Raw Node.js script
async function bootstrap() {
console.log('Hello world!');
}
bootstrap();
~~~
對于所有這些腳本,我們使用了`tsc`(TypeScript)編譯器,因此代碼保持未捆綁(不使用`webpack`)。
| | |
| ------------------------------------ | ----------------- |
| Express | 0.0079s (7.9ms) |
| Nest with `@nestjs/platform-express` | 0.1974s (197.4ms) |
| Nest (standalone application) | 0.1117s (111.7ms) |
| Raw Node.js script | 0.0071s (7.1ms) |
> **注意** :機器:MacBook Pro Mid 2014,2.5 GHz 四核 Intel Core i7,16 GB 1600 MHz DDR3,SSD
現在,讓我們重復所有的基準測試,但這次,使用`webpack`(如果你安裝了\[Nest CLI\](https://docs.nestjs.com/cli/overview),你可以運行`nest build --webpack`)將我們的應用程序捆綁到一個可執行的 JavaScript 文件中。但是,我們將確保將所有依賴項(`node\_modules`)捆綁在一起,而不是使用 Nest CLI 附帶的默認`webpack` 配置,如下所示:
~~~javascript
module.exports = (options, webpack) => {
const lazyImports = [
'@nestjs/microservices/microservices-module',
'@nestjs/websockets/socket-module',
];
return {
...options,
externals: [],
plugins: [
...options.plugins,
new webpack.IgnorePlugin({
checkResource(resource) {
if (lazyImports.includes(resource)) {
try {
require.resolve(resource);
} catch (err) {
return true;
}
}
return false;
},
}),
],
};
};
~~~
> **提示**要指示 Nest CLI 使用此配置,請在項目的根目錄中創建一個新的`webpack.config.js` 文件。
使用此配置,我們收到以下結果:
| | |
| ------------------------------------ | ---------------- |
| Express | 0.0068s (6.8ms) |
| Nest with `@nestjs/platform-express` | 0.0815s (81.5ms) |
| Nest (standalone application) | 0.0319s (31.9ms) |
| Raw Node.js script | 0.0066s (6.6ms) |
> > **注意** :機器:MacBook Pro Mid 2014,2.5 GHz 四核 Intel Core i7,16 GB 1600 MHz DDR3,SSD
> **提示**您可以通過應用額外的代碼壓縮和優化技術(使用`webpack`插件等)進一步優化它。
如您所見,編譯方式(以及是否捆綁代碼)至關重要,并且對整體啟動時間有重大影響。使用`webpack`,您可以將獨立的 Nest 應用程序(具有一個模塊、控制器和服務的啟動項目)的引導時間平均降低到 ~32 毫秒,對于常規的基于 express 的 HTTP 的 NestJS 的啟動時間降低到 ~81.5 毫秒應用程序。
對于更復雜的 Nest 應用,比如有 10 個資源(通過`$nest g resource`schematic = 10 個模塊、10 個控制器、10 個服務、20 個 DTO 類、50 個 HTTP 端點 +`AppModule` 生成),整體在 MacBook 上啟動Pro Mid 2014,2.5 GHz 四核 Intel Core i7,16 GB 1600 MHz DDR3,SSD 大約 0.1298s (129.8ms)。將單體應用程序作為無服務器功能運行通常沒有太大意義,因此請將此基準測試更多地視為引導時間如何隨著應用程序增長而增加的示例。
#### 運行時優化
到目前為止,我們介紹了編譯時優化。這些與您在應用程序中定義提供程序和加載 Nest 模塊的方式無關,并且隨著您的應用程序變得越來越大,這將發揮重要作用。
例如,假設將數據庫連接定義為\[異步提供程序\](https://docs.nestjs.com/fundamentals/async-providers)。異步提供程序旨在延遲應用程序啟動,直到完成一個或多個異步任務。這意味著,如果您的無服務器函數平均需要 2 秒來連接到數據庫(在引導程序上),那么您的端點將需要至少兩秒的額外時間(因為它必須等到連接建立)才能發送回響應(當它是冷啟動并且您的應用程序尚未運行)。
如您所見,在\*\*無服務器環境\*\*中,您構建提供程序的方式有些不同,其中引導時間很重要。另一個很好的例子是,如果您使用 Redis 進行緩存,但僅限于某些場景。也許,在這種情況下,您不應該將 Redis 連接定義為異步提供程序,因為它會減慢引導時間,即使此特定函數調用不需要它。
此外,有時您可以使用“LazyModuleLoader”類延遲加載整個模塊,如\[本章\](https://docs.nestjs.com/fundamentals/lazy-loading-modules) 中所述。緩存也是一個很好的例子。想象一下,您的應用程序有,比如說,`CacheModule`,它在內部連接到 Redis,并且還導出了`CacheService` 以與 Redis 存儲進行交互。如果你不需要它來進行所有潛在的函數調用,你可以按需加載它,懶惰地。這樣,對于所有不需要緩存的調用,您將獲得更快的啟動時間(發生冷啟動時)。
~~~typescript
if (request.method === RequestMethod[RequestMethod.GET]) {
const { CacheModule } = await import('./cache.module');
const moduleRef = await this.lazyModuleLoader.load(() => CacheModule);
const { CacheService } = await import('./cache.service');
const cacheService = moduleRef.get(CacheService);
return cacheService.get(ENDPOINT_KEY);
}
~~~
另一個很好的例子是 webhook 或 worker,根據某些特定條件(例如,輸入參數),它可能執行不同的操作。在這種情況下,您可以在路由處理程序中指定一個條件,該條件為特定函數調用延遲加載適當的模塊,然后延遲加載每個其他模塊。
~~~typescript
if (workerType === WorkerType.A) {
const { WorkerAModule } = await import('./worker-a.module');
const moduleRef = await this.lazyModuleLoader.load(() => WorkerAModule);
// ...
} else if (workerType === WorkerType.B) {
const { WorkerBModule } = await import('./worker-b.module');
const moduleRef = await this.lazyModuleLoader.load(() => WorkerBModule);
// ...
}
~~~
#### 示例集成
您的應用程序的入口文件(通常是`main.ts`文件)應該看起來像\*\*取決于幾個因素\*\*因此\*\*沒有一個模板\*\*適用于每種情況。例如,啟動無服務器功能所需的初始化文件因云提供商(AWS、Azure、GCP 等)而異。此外,根據您是要運行具有多個路由/端點的典型 HTTP 應用程序還是只提供單個路由(或執行代碼的特定部分),您的應用程序的代碼看起來會有所不同(例如,對于每函數方法你可以使用`NestFactory.createApplicationContext`代替啟動HTTP服務器,設置中間件等)。
僅出于說明目的,我們將集成 Nest(使用`@nestjs/platform-express` 并啟動整個功能齊全的 HTTP 路由器)與 \[Serverless\](https://www.serverless.com/) 框架(在這種情況下,針對 AWS Lambda)。正如我們之前提到的,您的代碼將根據您選擇的云提供商以及許多其他因素而有所不同。
首先,讓我們安裝所需的軟件包:
~~~bash
$ npm i @vendia/serverless-express aws-lambda
$ npm i -D @types/aws-lambda serverless-offline
~~~
> **提示**為了加快開發周期,我們安裝了模擬 AWS λ 和 API Gateway 的`serverless-offline`插件。
安裝過程完成后,讓我們創建`serverless.yml`文件來配置無服務器框架:
~~~yaml
service: serverless-example
plugins:
- serverless-offline
provider:
name: aws
runtime: nodejs14.x
functions:
main:
handler: dist/main.handler
events:
- http:
method: ANY
path: /
- http:
method: ANY
path: '{proxy+}'
~~~
> **提示**要了解有關無服務器框架的更多信息,請訪問\[官方文檔\](https://www.serverless.com/framework/docs/)。
有了這些,我們現在可以導航到 main.ts 文件并使用所需的樣板更新我們的引導代碼:
~~~typescript
import { NestFactory } from '@nestjs/core';
import serverlessExpress from '@vendia/serverless-express';
import { Callback, Context, Handler } from 'aws-lambda';
import { AppModule } from './app.module';
let server: Handler;
async function bootstrap(): Promise<Handler> {
const app = await NestFactory.create(AppModule);
await app.init();
const expressApp = app.getHttpAdapter().getInstance();
return serverlessExpress({ app: expressApp });
}
export const handler: Handler = async (
event: any,
context: Context,
callback: Callback,
) => {
server = server ?? (await bootstrap());
return server(event, context, callback);
};
~~~
> **提示**對于創建多個 serverless 函數并在它們之間共享公共模塊,我們建議使用\[CLI Monorepo 模式\](https://docs.nestjs.com/cli/monorepo#monorepo-mode)。
> **警告**如果您使用`@nestjs/swagger` 包,則需要一些額外的步驟才能使其在無服務器功能的上下文中正常工作。查看此\[文章\](https://javascript.plainenglish.io/serverless-nestjs-document-your-api-with-swagger-and-aws-api-gateway-64a53962e8a2)了解更多信息。
接下來,打開`tsconfig.json`文件并確保啟用`esModuleInterop`選項以使`@vendia/serverless-express`包正確加載。
~~~json
{
"compilerOptions": {
...
"esModuleInterop": true
}
}
~~~
現在我們可以構建我們的應用程序(使用`nest build`或`tsc`)并使用`serverless`CLI在本地啟動我們的lambda函數:
~~~bash
$ npm run build
$ npx serverless offline
~~~
應用程序運行后,打開瀏覽器并導航到`http://localhost:3000/dev/\[ANY\_ROUTE\]`(其中`\[ANY\_ROUTE\]`是在您的應用程序中注冊的任何端點)。
在上面的部分中,我們已經展示了使用 webpack 和打包你的應用程序會對整個引導時間產生重大影響。但是,要使其與我們的示例一起使用,您必須在 webpack.config.js 文件中添加一些額外的配置。通常,為了確保我們的`handler`函數被調用,我們必須將`output.libraryTarget`屬性更改為`commonjs2`。
~~~javascript
return {
...options,
externals: [],
output: {
...options.output,
libraryTarget: 'commonjs2',
},
// ... the rest of the configuration
};
~~~
有了這個,你現在可以使用`$ nest build --webpack`來編譯你的函數代碼(然后`$ npx serverless offline`來測試它)。
還建議(但\*\*不是必需的\*\*因為它會減慢您的構建過程)安裝`terser-webpack-plugin`包并覆蓋其配置以在縮小生產構建時保持類名不變。在您的應用程序中使用`class-validator`時,不這樣做可能會導致不正確的行為。
~~~javascript
const TerserPlugin = require('terser-webpack-plugin');
return {
...options,
externals: [],
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
keep_classnames: true,
},
}),
],
},
output: {
...options.output,
libraryTarget: 'commonjs2',
},
// ... the rest of the configuration
};
~~~
#### 使用獨立應用程序功能
或者,如果你想保持你的函數非常輕量并且不需要任何與 HTTP 相關的特性(路由,還有守衛、攔截器、管道等),你可以只使用`NestFactory.createApplicationContext`(如前所述) 而不是運行整個 HTTP 服務器(以及底層的`express`),如下所示:
>main.ts
~~~typescript
import { HttpStatus } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { Callback, Context, Handler } from 'aws-lambda';
import { AppModule } from './app.module';
import { AppService } from './app.service';
export const handler: Handler = async (
event: any,
context: Context,
callback: Callback,
) => {
const appContext = await NestFactory.createApplicationContext(AppModule);
const appService = appContext.get(AppService);
return {
body: appService.getHello(),
statusCode: HttpStatus.OK,
};
};
~~~
> \*\*提示\*\*注意`NestFactory.createApplicationContext`不會用增強器(守衛、攔截器等)包裝控制器方法。為此,您必須使用`NestFactory.create` 方法。
您還可以將`event`對象傳遞給可以處理它并返回相應值(取決于輸入值和您的業務邏輯)的`EventsService`提供程序。
~~~typescript
export const handler: Handler = async (
event: any,
context: Context,
callback: Callback,
) => {
const appContext = await NestFactory.createApplicationContext(AppModule);
const eventsService = appContext.get(EventsService);
return eventsService.process(event);
};
~~~
- 介紹
- 概述
- 第一步
- 控制器
- 提供者
- 模塊
- 中間件
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 自定義裝飾器
- 基礎知識
- 自定義提供者
- 異步提供者
- 動態模塊
- 注入作用域
- 循環依賴
- 模塊參考
- 懶加載模塊
- 應用上下文
- 生命周期事件
- 跨平臺
- 測試
- 技術
- 數據庫
- Mongo
- 配置
- 驗證
- 緩存
- 序列化
- 版本控制
- 定時任務
- 隊列
- 日志
- Cookies
- 事件
- 壓縮
- 文件上傳
- 流式處理文件
- HTTP模塊
- Session(會話)
- MVC
- 性能(Fastify)
- 服務器端事件發送
- 安全
- 認證(Authentication)
- 授權(Authorization)
- 加密和散列
- Helmet
- CORS(跨域請求)
- CSRF保護
- 限速
- GraphQL
- 快速開始
- 解析器(resolvers)
- 變更(Mutations)
- 訂閱(Subscriptions)
- 標量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 聯合類型
- 枚舉(Enums)
- 字段中間件
- 映射類型
- 插件
- 復雜性
- 擴展
- CLI插件
- 生成SDL
- 其他功能
- 聯合服務
- 遷移指南
- Websocket
- 網關
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 適配器
- 微服務
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定義傳輸器
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 獨立應用
- Cli
- 概述
- 工作空間
- 庫
- 用法
- 腳本
- Openapi
- 介紹
- 類型和參數
- 操作
- 安全
- 映射類型
- 裝飾器
- CLI插件
- 其他特性
- 遷移指南
- 秘籍
- CRUD 生成器
- 熱重載
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模塊
- Swagger
- 健康檢查
- CQRS
- 文檔
- Prisma
- 靜態服務
- Nest Commander
- 問答
- Serverless
- HTTP 適配器
- 全局路由前綴
- 混合應用
- HTTPS 和多服務器
- 請求生命周期
- 常見錯誤
- 實例
- 遷移指南
- 發現
- 誰在使用Nest?