Skip to main content

聚合部署

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 脚手架。

info

该脚手架针对 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 框架)。

info

这里不直接使用 @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