这是第 76 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注咱们吧~ 本文首发于政采云前端博客: 结合阿里云 FC 谈谈我对 FaaS 的理解
进入主题以前,先从背景简述下最近前端界的热点词汇 Serverless,其实,Serverless 这个概念在其余领域已经提出来好久了。javascript
Serverless 直译为无服务器,表明一种无服务器架构(也被称为“无服务器计算”),并不表示不须要物理服务器,而是指不须要关注和管理服务器,直接使用服务便可,其实就是一种服务外包或者说服务托管,这些服务由第三方供应商提供。css
具体来讲,Serverless 就是指服务端逻辑由开发者实现,运行在无状态的计算容器中,由事件驱动,彻底被第三方管理,而业务层面的状态则记录在数据库或存储资源中。html
目前国内一些大型公司如阿里、腾讯、滴滴都已经将 Serverless 逐步在业务中落地(案例分享:2020.06.19 ServerlessDays · China Online)。前端
Serverless = FaaS+BaaS ,是目前界内比较公认的定义:vue
FaaS(Function as a Service):函数即服务java
BaaS(Backend as Service):后端即服务node
笔者认为,单看 FaaS 或者 BaaS,都是一种 Serverless ,只是通常咱们完整的应用须要二者结合才能实现。nginx
好,下面正式介绍 FaaS。git
做为一个前端,咱们平日里很难接触到服务器、运维方面的操做。假设如今给你一个任务,让你本身开发一个有先后端交互的应用,并从 0 到 1 进行部署,是否是以为光靠本身根本搞不定,这个任务有点难。github
传统应用的部署,咱们须要作不少工做:准备服务器、配置环境、购买域名、配置 nginx、……。应用发布以后,咱们还要考虑运维的问题,线上监控,扩缩容,容灾等等等等。
如今,咱们运用 FaaS 去开发部署的话,以上都不用考虑,只须要专一于业务逻辑开发便可,由于其它一切都托管给 FaaS 平台帮咱们处理了。
FaaS 是无服务器计算的一种形式。经过 FaaS,能够快速构建任何类型的应用和服务,它具备开发敏捷特性、自动弹性伸缩能力、免运维和完善的监控设施。所以:
所以,相比传统开发模式,FaaS 大大提升了开发和交付效率,是将来云服务发展的大趋势。自 2014 年始,在 AWS Lambda 以后,Google、IBM、Microsoft、阿里、腾讯、华为等国内外云厂商相继推出云函数计算平台 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 平台会为咱们管理。这个文件,有两个重要的部分:
// http trigger entry module.exports.handler = function(req, res, context) { server.httpProxy(req, res, context); };
前面说过,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 trigger entry module.exports.handler = function(req, res, context) { server.httpProxy(req, res, context); };
配置 HTTP 触发器的函数能够经过 HTTP 请求被触发执行。此时函数能够看作一个 Web Server,对 HTTP 请求进行处理,并将处理结果返回给调用端。
访问 html 页面、请求静态资源文件,以及请求接口,都是经过 http 请求去触发相应函数的执行。
能够在这里进行调试:
目前市面上已经有一些较为成熟的开源 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 函数若在一段时间内没有被事件触发运行,云服务商就会回收运行函数的容器资源,销毁函数实例,因此,在未被回收的这段时间内再次调用函数就是热启动;销毁后,从新建立就是冷启动。
冷启动具体作了哪些操做呢?以阿里云为例,大体包括了调度实例、下载解压代码、启动容器、启动运行时,这一过程结束后,函数才开始执行。因此冷启动的启动消耗时间受到不少因素的影响:
有专门研究对比,不一样语言的冷启动时间不一样
这个过程在冷启动过程当中相对比较耗时,可能几十毫秒,也可能几秒,看代码体积大小
这个过程的耗时取决于云服务商
各大云厂商都已经有了一些优化方案的最佳实践,须要开发者和云厂商共同努力:
减小代码体积:
下降冷启动频率
FaaS 还有一个局限性,就是平台会限制函数的执行时间,超出时间后执行代码的进程会被强行销毁,因此 FaaS 不适合长时间运行的应用。例如 AWS Lambda 函数不容许运行超过 15 分钟(之前只有 5 分钟),若是超过就会中断。使用时,应该根据本身的预期执行时间来设置超时值,防止函数的运行时间超出预期,而且建议调用函数的 client 端的 timeout 要稍稍大于函数设置的 timeout,这样才能防止执行环境不会意外退出。
相信你们读到这里,应该差很少能够明白 FaaS 的工做流程了,咱们总结一下:
Serverless 如今这么热,它对前端到底有什么影响呢?
整个实践下来发现,FaaS 帮咱们前端扩展了能力边界,做为前端,咱们本身一我的也能快速完成先后端开发以及部署工做,彻底不用关心服务器以及运维方面咱们不擅长的问题。前端也有机会参与服务端业务逻辑开发,更深刻业务,创造更大的价值。
本文结合阿里云 FC、Midway FaaS 框架快速建立 FaaS 应用的实践,向你们展现了什么是 FaaS,FaaS 的工做流程,优缺点,展示了 FaaS 颠覆传统开发模式的魅力。如今 FaaS 的应用场景很是普遍,Web 应用及小程序等移动应用、AI 及机器学习、物联网、实时数据处理等等。Serverless 时代,生产效率大大提升,每一个人都有更多机会创造无限可能,让咱们一块儿为将来加油!
Serverless Handbook——无服务架构实践手册
Serverless Architectures(译文)—(Martin Fowler)
政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com