结合阿里云 FC 谈谈我对 FaaS 的理解

avatar
@政采云有限公司@政采云技术

这是第 76 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注我们吧~ 本文首发于政采云前端博客:结合阿里云 FC 谈谈我对 FaaS 的理解

进入主题之前,先从背景简述下最近前端界的热点词汇 Serverless,其实,Serverless 这个概念在其他领域已经提出来很久了。

Serverless

概念

Serverless 直译为无服务器,代表一种无服务器架构(也被称为“无服务器计算”),并不表示不需要物理服务器,而是指不需要关注和管理服务器,直接使用服务即可,其实就是一种服务外包或者说服务托管,这些服务由第三方供应商提供。

具体来说,Serverless 就是指服务端逻辑由开发者实现,运行在无状态的计算容器中,由事件驱动,完全被第三方管理,而业务层面的状态则记录在数据库或存储资源中。

目前国内一些大型公司如阿里、腾讯、滴滴都已经将 Serverless 逐步在业务中落地(案例分享:2020.06.19 ServerlessDays · China Online)。

Serverless 与 FaaS 的联系

Serverless = FaaS+BaaS ,是目前界内比较公认的定义:

  • FaaS(Function as a Service):函数即服务

    • 负责服务端业务逻辑场景,需要开发者自己实现,服务的粒度比微服务更小,小到以函数为单位
    • 事件驱动的 Serverless 服务,毫秒级弹性伸缩
    • 无法进行内存或数据共享,是无状态的
    • 无需运维,部署、运维等都由云平台提供
  • BaaS(Backend as Service):后端即服务

    • 负责通用服务场景,不需要开发者自己开发,由云厂商提供,比如数据库、身份验证、消息队列、对象存储等服务

    • 有状态

笔者认为,单看 FaaS 或者 BaaS,都是一种 Serverless ,只是一般我们完整的应用需要两者结合才能实现。

好,下面正式介绍 FaaS。

FaaS

作为一个前端,我们平日里很难接触到服务器、运维方面的操作。假设现在给你一个任务,让你自己开发一个有前后端交互的应用,并从 0 到 1 进行部署,是不是觉得光靠自己根本搞不定,这个任务有点难。

传统应用的部署,我们需要做很多工作:准备服务器、配置环境、购买域名、配置 nginx、……。应用发布之后,我们还要考虑运维的问题,线上监控,扩缩容,容灾等等等等。

现在,我们运用 FaaS 去开发部署的话,以上都不用考虑,只需要专注于业务逻辑开发即可,因为其它一切都托管给 FaaS 平台帮我们处理了。

概念

FaaS 是无服务器计算的一种形式。通过 FaaS,可以快速构建任何类型的应用和服务,它具有开发敏捷特性、自动弹性伸缩能力、免运维和完善的监控设施。因此:

  • 开发者只需专注于业务逻辑开发,无需管理服务器、运行环境等 IT 基础设施

  • FaaS 平台会为我们准备好计算资源,以 弹性、可靠的方式运行我们的代码,实现毫秒级弹性伸缩,轻松应对峰值压力

  • 用户只需根据函数的实际执行时间按量付费

因此,相比传统开发模式,FaaS 大大提高了开发和交付效率,是未来云服务发展的大趋势。自 2014 年始,在 AWS Lambda 之后,Google、IBM、Microsoft、阿里、腾讯、华为等国内外云厂商相继推出云函数计算平台 FaaS。

快速创建 FaaS 应用

函数计算开发方式有很多种,比如:Fun 工具、函数计算 FC 平台、Serverless VScode、云开发平台。本文借助阿里云函数计算平台,通过其提供的模版快速创建、部署一个 Web 应用,向大家更清楚地展示 FaaS 是什么。

本文直接基于模版创建,用户也可以选择自己上传应用代码直接部署 Web 应用。

我们选择有前后端交互、数据增删改查等行为的 Todo List 应用,它是一个前后端一体化(前后端代码共属一个项目中开发、调试、部署,高效且节省资源) FaaS 应用。

服务/函数

服务

一个应用可以拆分为多个服务。从资源使用维度出发,一个服务可以由多个函数组成。先创建服务,再创建函数。

可以看到 TodoList 应用部署成功后创建好的服务,我们可以对该服务进行配置、删除、查看指标等操作,还可以对其下的函数进行配置、删除。

函数

函数是系统调度和运行的单位。函数必须从属于服务,同一个服务下可以有多个函数,同一个服务下的所有函数共享相同的设置,例如服务授权、日志配置,但彼此相互独立,互不影响。

函数代码

FaaS 对多种语言都有良好的支持性,比如阿里云支持 Node.js、Python、PHP、Java 等等,开发者可以使用自己熟悉的语言,根据平台提供的函数接口形式编写代码。这也意味着,团队协作时,大家可以利用多种语言混合开发复杂应用。

点击代码执行,可以看到这里有一个在线编辑器,里面就是模板生成的代码,可以在此处进行运行、调试。该应用前端页面用 React 实现,后端服务基于 Node 的 Express 框架。

函数入口

template.yml是我们的函数信息配置文件,告诉云厂商我们的代码入口函数、触发器类型等操作。

函数入口为src/index.handler ,即src/index.js服务端代码文件中的 handler 方法,该文件代码如下:

const { Server } = require('@webserverless/fc-express')
const express = require('express');
const fs = require('fs');
const path = require('path');
const bodyParser = require('body-parser');

// initial todo list
let todos = [
  {
    id: '123',
    text: 'Go shopping',
    isCompleted: false,
  },
  {
    id: '213',
    text: 'Clean room',
    isCompleted: true,
  },
];


const staticBasePath = path.join('public', 'build');

const app = express();

// index.html
app.all("/", (req, resp) => {
  resp.setHeader('Content-Type', 'text/html');
  resp.send(fs.readFileSync('./public/build/index.html', 'utf8'));
});

// 静态资源文件:js、css、图片
// static js resources  
app.all('/*.js', (req, resp) => {
  const filePath = path.join(staticBasePath, req.path);
  resp.setHeader('Content-Type', 'text/javascript');
  resp.send(fs.readFileSync(filePath, 'utf8'));
});

// static css resources
app.all('/*.css', (req, resp) => {
  const filePath = path.join(staticBasePath, req.path);
  resp.setHeader('Content-Type', 'text/css');
  resp.send(fs.readFileSync(filePath, 'utf8'));
});

// static svg resources
app.all('/*.svg', (req, resp) => {
  const filePath = path.join(staticBasePath, req.path);
  resp.setHeader('Content-Type', 'image/svg+xml');
  resp.send(fs.readFileSync(filePath, 'utf8'));
});

// static png resources
app.all('/*.png', (req, resp) => {
  const filePath = path.join(staticBasePath, req.path);
  resp.setHeader('Content-Type', 'image/png');
  resp.send(fs.readFileSync(filePath, 'utf8'));
});

app.all('/manifest.json', (req, resp) => {
  const filePath = path.join(staticBasePath, req.path);
  resp.setHeader('Content-Type', 'application/json');
  resp.send(fs.readFileSync(filePath, 'utf8'));
});

// 增删改查对应的api接口
// list api
app.get('/api/listTodos', (req, resp) => {
  resp.send(JSON.stringify(todos));
});

// create api
app.get('/api/createTodo', (req, resp) => {
  const { todo: todoStr } = req.query;
  const todo = JSON.parse(todoStr);
  todos.push({
    id: todo.id,
    text: todo.text,
    isCompleted: todo.isCompleted,
  });
  resp.send(JSON.stringify(todos));
});

// update api
app.get('/api/updateTodo', (req, resp) => {
  const { todo: todoStr } = req.query;
  const targetTodo = JSON.parse(todoStr);
  const todo = todos.find((todo) => todo.id === targetTodo.id);
  if (todo) {
    todo.isCompleted = targetTodo.isCompleted;
    todo.text = targetTodo.text;
  }
  resp.send(JSON.stringify(todos));
});

// remove api
app.get('/api/removeTodo', (req, resp) => {
  const { id } = req.query
  // TODO: Implement methods to filter todos, filtering out item with the same id
  // todos = todos.filter();
  const todosIndex = todos.findIndex((todo) => todo.id === id);
  if (todosIndex !== -1) {
    todos.splice(todosIndex, 1);
  } 
  resp.send(JSON.stringify(todos));
});

const server = new Server(app);

// 向外暴露了 http触发器入口
// http trigger entry
module.exports.handler = function(req, res, context) {
  server.httpProxy(req, res, context);
};

可以看到,我们不需要自己起服务,FaaS 平台会为我们管理。这个文件,有两个重要的部分:

  • 1.函数入口 handler,也是 HTTP 触发器入口,下文会详细介绍
// http trigger entry
module.exports.handler = function(req, res, context) {
  server.httpProxy(req, res, context);
};
  • 2.Web Service 和 api,通过路由调用相应的服务,比如请求路径为"/"时,会返回 Web 应用的 html 页面;请求"/api/*"时,调用接口返回数据

触发器

前面说过,FaaS 是一种事件驱动的计算模型,即函数的执行是由事件驱动的,没有事件触发,函数就不运行。与传统开发模式不同,函数不需要自己启动一个服务去监听数据,而是通过绑定一个(或者多个)触发器。

触发器就是触发函数执行的方式,我们需要为函数创建指定的触发器。

FaaS 应用的触发器有多种(不同云厂商的触发器会有所区别),但基本都支持 HTTP、对象存储、定时任务、消息队列等触发器,其中 HTTP 触发器是最常见的。

以阿里云函数计算为例,介绍几个代表类型:

触发器类型

名称描述
HTTP 触发器1.HTTP 触发器通过发送 HTTP 请求触发函数执行,主要适用于快速构建 Web 服务等场
2.HTTP 触发器支持 HEAD、POST、PUT、GET 和 DELETE 方式触发函数
3.可以通过绑定自定义域名为 HTTP 函数映射不同的 HTTP 访问路径
4.开发人员可以快速使用 HTTP 触发器搭建 Web service和 API
OSS 触发器(对象存储)1.OSS 事件能触发相关函数执行,实现对 OSS 中的数据进行自定义处理
日志服务触发器1.当日志服务定时获取更新的数据时,通过日志服务触发器,触发函数消费增量的日志数据,并完成对数据的自定义加工
定时触发器1.在指定的时间点自动触发函数执行
API 网关触发器1.API 网关作为事件源,当有请求到达后端服务设置为函数计算的 API 网关时,API 网关会触发函数的执行。函数计算会将执行结果返回给 API 网关
2.与 HTTP 触发器类似,可应用于搭建 Web 应用。相较于 HTTP 触发器,您可以使用 API 网关进行 IP 白名单或黑名单设置等高级操作

开发者在调试时,如果不配置触发器,也可以使用控制台、命令行工具 或者 SDK 等方式直接调用函数执行。

我们点开 TodoList 的触发器,可以看到创建的 HTTP 触发器,WEB 用户通过 HTTP 请求即可触发函数的执行。

注意这里的提示语:打开链接,会下载一个 html 附件,这时我们打开,因为找不到静态资源文件,应用不能正常运行。

可以点击自定义域名去用平台为我们创建的临时域名打开:

可以看到,页面和功能都正常了。我们查看、新增、更新、删除,在控制台里可以看到发起的请求和返回结果

HTTP 触发器

该应用 HTTP 触发器的入口函数形式如下:

// http trigger entry
module.exports.handler = function(req, res, context) {
  server.httpProxy(req, res, context);
};

配置 HTTP 触发器的函数可以通过 HTTP 请求被触发执行。此时函数可以看做一个 Web Server,对 HTTP 请求进行处理,并将处理结果返回给调用端

访问 html 页面、请求静态资源文件,以及请求接口,都是通过 http 请求去触发相应函数的执行。

可以在这里进行调试:

FaaS 框架

目前市面上已经有一些较为成熟的开源 FaaS 框架,比如 OpenFaaS、funktion、Kubeless、Fission等等,本文向大家介绍阿里云今年正式发布的Midway FaaS框架,它是用于构建 Node.js 云函数的 Serverless 框架,它提供了函数的本地开发、调用、测试整个链路。它可以开发新的 Serveless 应用,也提供方案将传统应用迁移至各云厂商的云函数。阿里内部已经使用一年多了。

我们可以运用脚手架@midwayjs/faas-cli提供的能力在本地快速创建、调试、mock、部署一个 FaaS 应用。Serverless 有一个很大的弊端,就是和云服务商平台强绑定,但是Midway Serverles 是防平台锁定的,它能一套代码能够运行在不同的平台和运行时之上,Midaway faas的部署可以跨云厂商,默认部署到阿里云FC,我们也可以修改部署到其它平台,如腾讯云SCF、AWS Lambda。

Midway FaaS 体系也与云工作台进行了结合,使用了和本地同样的能力,这里选择登录阿里云开发平台,用示例库模版再次创建一个 TodoList 应用进行演示,只不过这个是用 Midway FaaS 构建的。

代码目录结构可以简单抽取为:

|-- src
|		|-- apis //函数代码
|				|--	config //针对不同环境创建配置文件
|				|--	configuration.ts //扩展能力配置
|				|--	index.ts // 函数代码入口文件,里面包括多个函数
|		|-- components // 前端组件
|		|-- index.tsx // 前端页面入口文件(该应用前端基于React,若是Vue,则是App.vue)
|-- f.yml // 函数信息配置文件

f.yml配置文件

service: serverless-hello-world

// 服务提供商
provider:
  name: aliyun
  runtime: nodejs10 //运行时环境及版本信息

// 函数具体信息(包括函数入口、触发器等等)
functions:
  render:
    handler: render.handler 
    events:
      - apigw:
          path: /*
  list:
    handler: todo.list 
    events:
      - apigw:
          path: /api/list
  update:
    handler: todo.update
    events:
      - apigw:
          path: /api/update
  remove:
    handler: todo.remove
    events:
      - apigw:
          path: /api/remove
  add:
    handler: todo.add
    events:
      - apigw:
          path: /api/add
          
// 构建的配置信息
package:
  include:
    - build //打包目录
  artifact: code.zip //打包名称

函数代码中一个函数对应一个 api 接口:

安装依赖后,我们npm run dev,阿里云为我们创建了一个临时域名用于调试:

可以看到请求的这些资源和接口数据:

一键部署,非常方便。

收费标准

传统应用我们的服务是一直占用资源的,而 FaaS 在资源空闲时不收费,按需付费,可以大大节省开支。

收费标准:

  • 调用函数次数
  • 函数运行时间

因为每月都有免费额度,所以在个人日常使用时基本不需要付费。

(PS:但还是要特别注意,部署的应用不用时一定要及时下线,否则可能会收取费用,还有开通的服务、功能也一定要仔细留意一下收费标准,比如可能想解决冷启动的问题,会开通预留实例功能,如果我们不用的话,一定要注意手动释放,否则即使它没有执行任何请求,也会从启动开始不断收费,费用可不小)

冷启动

再说说 FaaS 目前备受关注的一个问题——冷启动

FaaS 中的函数首次调用更新函数或长时间未调用时重新调用函数时,平台会初始化一个函数实例,这个过程就是冷启动,平均耗时在几百毫秒。

延迟问题

FaaS 因为冷启动,不能立即调用函数,调用延迟会给应用性能带来影响,针对冷启动的延迟问题,各大云服务商非常关注,正在想办法不断优化。

与冷启动相呼应的是热启动,热启动指函数调用时不用重新创建新的函数实例,而是直接复用之前的函数实例。因为 FaaS 函数若在一段时间内没有被事件触发运行,云服务商就会回收运行函数的容器资源,销毁函数实例,所以,在未被回收的这段时间内再次调用函数就是热启动;销毁后,重新创建就是冷启动。

延迟原因

冷启动具体做了哪些操作呢?以阿里云为例,大致包括了调度实例、下载解压代码、启动容器、启动运行时,这一过程结束后,函数才开始执行。所以冷启动的启动消耗时间受到很多因素的影响:

  • 编程语言

    有专门研究对比,不同语言的冷启动时间不同

  • 代码大小

​ 这个过程在冷启动过程中相对比较耗时,可能几十毫秒,也可能几秒,看代码体积大小

  • 容器创建

    这个过程的耗时取决于云服务商

  • 配置等

如何优化

各大云厂商都已经有了一些优化方案的最佳实践,需要开发者和云厂商共同努力:

  • 减少代码体积:
    • 开发者可以通过精简代码,删除无用依赖,加速下载函数代码过程
    • 比如腾讯云对代码做了两级的缓存,可以有效降低下载代码时间
  • 资源复用,缩短函数执行时间
  • 选择冷启动时间较少的语言
  • 选择合适的内存:函数内存越大,冷启动表现越优
  • 避免不必要的配置
  • 降低冷启动频率
    • 使用定时触发器定时访问函数,这样可以防止函数实例一段时间没被使用被销毁
    • 使用 initializer 函数入口,函数计算会异步调用初始化接口,消除初始化用户代码的时间
  • 预留实例

执行时长

FaaS 还有一个局限性,就是平台会限制函数的执行时间,超出时间后执行代码的进程会被强行销毁,所以 FaaS 不适合长时间运行的应用。例如 AWS Lambda 函数不允许运行超过 15 分钟(以前只有 5 分钟),如果超过就会中断。使用时,应该根据自己的预期执行时间来设置超时值,防止函数的运行时间超出预期,并且建议调用函数的 client 端的 timeout 要稍稍大于函数设置的 timeout,这样才能防止执行环境不会意外退出。

FaaS 工作流程

相信大家读到这里,应该差不多可以明白 FaaS 的工作流程了,我们总结一下:

  • 开发者编写函数代码,可以在线编辑或者本地上传,完成后,FaaS 平台为我们部署应用,创建函数服务
  • 客户端通过设置的触发器,通知函数服务
  • 若存在函数实例,则直接在该执行环境中调用函数;没有,则先经过冷启动(调度实例、下载代码、启动实例、启动运行时),再执行函数
  • 函数根据用户请求量动态扩容响应请求,将内容返回给用户。函数执行完后,若一段时间内无事件触发,函数实例就会被销毁,FaaS 应用快速缩容到 0

对前端的影响

Serverless 现在这么热,它对前端到底有什么影响呢?

整个实践下来发现,FaaS 帮我们前端扩展了能力边界,作为前端,我们自己一个人也能快速完成前后端开发以及部署工作,完全不用关心服务器以及运维方面我们不擅长的问题。前端也有机会参与服务端业务逻辑开发,更深入业务,创造更大的价值。

结语

本文结合阿里云 FC、Midway FaaS 框架快速创建 FaaS 应用的实践,向大家展示了什么是 FaaS,FaaS 的工作流程,优缺点,展现了 FaaS 颠覆传统开发模式的魅力。现在 FaaS 的应用场景非常广泛,Web 应用及小程序等移动应用、AI 及机器学习、物联网、实时数据处理等等。Serverless 时代,生产效率大大提高,每个人都有更多机会创造无限可能,让我们一起为未来加油!

参考文章

Serverless Handbook——无服务架构实践手册

阿里云函数计算使用文档

Midway Serverless 使用文档

腾讯云函数计算冷启动优化实践

Serverless Architectures(译文)—(Martin Fowler)

推荐阅读

浅析 vue-router 源码和动态路由权限分配

编写高质量可维护的代码:一目了然的注释

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com