跳到主要内容
版本:2.0.0

聚合部署

Midway 针对 HTTP 场景,提供了一种聚合部署的方式,在开发时和传统 Web 应用类似,在部署时将多个路由部署在同一个函数容器中,可以节省冷启动时间,节省费用。

聚合部署模式特别适合用于传统中后台服务。

创建代码

创建聚合部署的代码示例。

$ npm -v

# 如果是 npm v6
$ npm init midway --type=faas-aggr my_midway_app

# 如果是 npm v7
$ npm init midway -- --type=faas-aggr my_midway_app

也可以执行 npm init midway ,选择 faas-aggr 脚手架。

信息

该脚手架针对 HTTP 的场景做了特殊处理,会将所有的 HTTP 接口部署为同一个函数(聚合部署)。

目录结构

以下就是一个函数的最精简的结构,核心会包括一个 f.yml 标准化函数文件,以及 TypeScript 的项目结构。

.
├── f.yml # 标准化 spec 文件
├── package.json # 项目依赖
├── src
│ └── index.ts # 函数入口
└── tsconfig.json

我们来简单了解一下文件内容。

  • f.yml   函数定义文件
  • tsconfig.json tsc 配置文件(没有 IDE 会报错)
  • src 函数源码目录
  • src/index.ts 示例函数文件

函数文件

我们首先来看看函数文件,传统的函数是一个 function ,为了更符合 midway 体系,以及使用我们的依赖注入,这里将它变成了 Class。

和传统应用相同,我们依旧使用 @Controller 装饰器来开发聚合的 HTTP 函数。

如下代码,我们暴露了三个路由,在聚合部署模式下,会只部署成一个 HTTP 函数:

import { Inject, Provide, Controller, Get, Post } from '@midwayjs/decorator';
import { Context } from '@midwayjs/faas';

@Provide()
@Controller('/')
export class APIService {
@Inject()
ctx: Context;

@Get('/')
async hello() {
return 'Hello Midwayjs';
}

@Get('/get')
async get() {
return this.ctx.query;
}

@Post('/post')
async post() {
return this.ctx.method;
}
}

函数定义文件

f.yml 是函数的定义文件,通过这个文件,在构建时生成不同平台所能认识的文件,示例中的文件内容如下。

service:
name: midway-faas-examples ## 函数组名,可以理解为应用名

provider:
name: aliyun ## 发布的平台,这里是阿里云

aggregation: ## 对 HTTP 函数使用聚合模式部署
all: ## 部署的函数名
functionsPattern: ## 匹配的函数规则
- '*'

本地开发

HTTP 函数本地开发和传统 Web 相同,输入以下命令。

$ npm run dev
$ open http://localhost:7001

Midway 会启动 HTTP 服务器,打开浏览器,访问 [http://127.0.0.1:7001](http://127.0.0.1:7001) ,浏览器会打印出 Hello midwayjs   的信息。

本地测试

使用和应用相同的测试方法来测试,针对 HTTP 函数,使用封装了 supertest 的 createHttpRequest 方法创建 HTTP 客户端。

唯一和应用不同的是,使用 createFunctionApp 方法创建函数应用(app)。

createFunctionApp 方法是 createApp 方法在函数场景下的定制(其中指定了函数的 @midwayjs/serverless-app 框架)。

信息

这里不直接使用 @midwayjs/faas 框架,而是使用 @midwayjs/serverless-app   框架,因为后者包含了网关模拟到函数调用的系列步骤。

HTTP 测试代码如下:

import { createFunctionApp, close, createHttpRequest } from '@midwayjs/mock';
import { Framework } from '@midwayjs/serverless-app';
import * as assert from 'assert';

describe('test/index.test.ts', () => {
it('should get /', async () => {
// create app
const app = await createFunctionApp<Framework>();

// make request
const result = await createHttpRequest(app).get('/');

// use expect by jest
expect(result.status).toBe(200);
expect(result.text).toBe('Hello Midwayjs');

const result2 = await createHttpRequest(app).get('/get').query({ name: 123 });
// or use assert
assert.deepStrictEqual(result2.status, 200);
assert.deepStrictEqual(result2.body.name, '123');

// close app
await close(app);
});
});

和纯函数的区别

普通的函数,会将单个的函数注册到特定的路由上。客户端请求的流量,会分别打到不同的函数实例上,这样的好处是每个接口对应的函数实例数量可能都是不同的,调用多的接口,实例就多,调用少的接口就会少。

坏处就是,如果调用量较少,函数的冷启动概率就大,调用的时间会明显变大,由于每个函数都会有开销,资源没有复用,最终的收费也会变多。

而聚合部署,会将所有的路由都注册到 /*  路由上,由框架内部的路由代码进行分发,所有的函数共享同一个容器,任意的请求都会让这个容器保活,使得冷启动的可能性大大减少。同时,由于代码是复用的,容器的复用率大大增加,比较适合于中后台这类请求均衡且接口的调用量相对均衡的场景。

函数名规则

使用聚合模式部署的函数,我们一般使用 @Controller  装饰器或者一体化方式进行开发,和传统 Web 开发、测试保持一致。

在构建时,我们会生成 f.yml  中的 functions  字段,一般情况下用户不需要关心函数名,接口等信息。

在使用 @Controller  装饰器的情况下,生成的函数名规则为 providerId_methodName ,即依赖注入的 key 和方法名的组合。

比如:

@Provide('userService') // <--- 取的是这个名字,如果不填,默认是类名的驼峰形式
@Controller('/api')
export class UserService {
@Get('/get/:id')
async getUser(@Query() name) {}

@Post('/create')
async createUser(@Query() name) {}
}

构建时会自动生成 userService_getUser  和 userService_createUser  两个函数并做内部路由处理。

下面是生成的 YAML 伪代码(实际由于是聚合部署,变为内部路由,并不会创建这段代码)。

functions:
userService_getUser:
events:
- http:
method: get
path: /get/[:id]
userService_createUser:
events:
- http:
method: post
path: /create