Faas
最后更新于
这有帮助吗?
最后更新于
这有帮助吗?
购买虚拟机服务,初始化虚拟机运行环境,安装应用运行环境,尽量和本地开发环境保持一致;
为了让用户能够访问应用,需要购买域名,用虚拟机 IP 注册域名;
配置 Nginx,启动 Nginx;
上传应用代码,启动应用。
Serverless 是对服务端运维体系的极端抽象,也就是说用户访问应用的模式没有变化,只是服务端无运维,只要关注业务逻辑。与传统部署的对比如下:
负载均衡和反向代理,FaaS 应用将这一步抽象为 HTTP 函数触发器;
在服务端构建代码的运行环境,FaaS 应用将这一步抽象为函数服务;
上传代码和启动应用,FaaS 应用将这一步抽象为函数代码。
当用户第一次访问 HTTP 函数触发器时,函数触发器就会 Hold 住用户的 HTTP 请求,并产生一个 HTTP Request 事件通知函数服务。
紧接着函数服务就会检查有没有闲置的函数实例;如果没有函数实例,就去函数代码仓库中拉取代码;初始化并启动一个函数实例,执行这个函数,传入这个 HTTP Request 对象作为函数的参数,执行函数。
再进一步,函数执行的结果 HTTP Response 返回函数触发器,函数触发器再将结果返回给等待的用户客户端。
通常纯 FaaS 的应用默认创建 3 个服务:
"XXXFunctionhttpTrigger"函数触发器,函数触发器是所有请求的统一入口,当请求发生时,它会触发事件通知函数服务,并且等待函数服务执行返回后,将结果返回给等待的请求。
"XXXService"函数服务,当函数触发器通知的“事件”到来,它会查看当前有没有闲置的函数实例,如果有则调用函数实例处理;如果没有,则会创建函数实例,等实例创建完毕后,再调用函数实例处理事件。
"XXXServiceXXXFunction"函数代码,“函数服务”在第一次实例化函数时,就会从这个代码仓库中拉取代码,并构建函数实例。
FaaS 与应用托管 PaaS 平台对比,最大的区别在于资源利用率,这也是 FaaS 最大的创新点。FaaS 的应用实例可以缩容到 0,而应用托管 PaaS 平台则至少要维持 1 台服务器或容器。也就是说没有用户请求时,函数服务没有任何的函数实例,也就不占用任何的服务器资源。而应用托管 PaaS 平台,创建应用实例的过程通常需要几十秒,为了保证服务可用性,必须一直维持着至少一台服务器运行应用实例。
FaaS 优势背后的关键点是可以极速启动。要理解极速启动背后的逻辑,要引入冷启动的概念。
FaaS 中的冷启动是指从调用函数开始到函数实例准备完成的整个过程。冷启动关注的是启动时间,启动时间越短,对资源的利用率就越高。
现在的云服务商,基于不同的语言特性,冷启动平均耗时基本在 100~700 毫秒之间。得益于 Google 的 JavaScript 引擎 Just In Time 特性,Node.js 在冷启动方面速度是最快的。
蓝色部分是Serverless平台(云服务商)负责,
红色部分由业务开发者负责,
函数代码初始化,一人一半。
也就是说蓝色部分在冷启动时候的耗时你不用关心,而红色部分就是你的函数耗时。
注意,FaaS 服务从 0 开始,启动并执行完一个函数,只需要毫秒级。这也是为什么 FaaS 敢缩容到 0 的主要原因。通常打开一个网页有个关键指标,响应时间在 1 秒以内,都算优秀。这么一对比,100 毫秒的启动时间,对于网页的秒开率影响真的极小。
Serverless平台(云服务商)还会不停地优化自己负责的部分,毕竟启动速度越快对资源的利用率就越高,例如冷启动过程中耗时比较长的是下载函数代码。所以一旦更新代码,Serverless平台(云服务商)就会开始调度资源,下载代码构建函数实例的镜像。请求第一次访问时,Serverless就可以利用构建好的缓存镜像,直接跳过冷启动的下载函数代码步骤,从镜像启动容器,这个也叫预热冷启动。所以如果有些业务场景对响应时间比较敏感,可以通过预热冷启动或预留实例策略,加速或绕过冷启动时间。
FaaS 在设计时已经考虑到冷启动导致异常的问题,所以在 FaaS 的“函数配置”里,都会提供一项“函数初始化入口”的选项。同时需要配置初始化时间,最少 1 秒。当配置了“函数初始化入口”,每次启动 FaaS 实例时,系统都会先等待初始化函数执行,而且在函数初始化时间结束后,才会继续执行函数。而从目前平台的配置来看,初始化时至少也需要 1 秒的时间。
对很多事件触发的应用场景,FaaS 增加几秒的初始化时间,影响并不大。但对很多后端应用则不然,尤其是 Java 等语言,如果软件包比较大,启动和初始化的时间会更长。
要缩短或绕过初始化的时间,要尽可能地利用 Runtime(运行我们代码所需要的函数库和二进制包) 里面提供的内置能力,例如 BaaS 化一直提倡使用服务编排和 HTTP 接口,就是因为云服务商 SDK 和 HTTP 协议,所有语言的 Runtime 里都内置了。
FaaS 中的 Runtime 是Serverless平台(云服务商)预先设计好的,会放一些常用的函数库和二进制包进去,相当于是平台的能力。
当遇到 FaaS 无法解决的场景,就考虑下降一层,使用 FaaS 的底层支撑技术 Docker 容器了。
FaaS 设计之初就牺牲了用户的可控性和应用场景,来简化代码模型,并且通过分层结构进一步提升资源的利用率。隐藏在 FaaS 冷启动中最重要的革新技术:分层结构。
以一个JavaScript写的HelloWorld程序为例:
从上图可以看出,FaaS实例执行时至少分为3层:
容器:理解为操作系统,代码要运行,总需要和硬件打交道,容器就是模拟出内核和硬件信息,让代码和 Runtime 可以在里面运行。目前的 FaaS 实现方案中,容器方案可能是 Docker 容器、VM 虚拟机,甚至 Sandbox 沙盒环境。容器的信息包括:
内存大小、
OS 版本、
CPU 信息、
环境变量等。
运行时:就是函数执行时的上下文 context。Runtime 的信息包括:
代码运行的语言和版本(例如 Node.js v10,Python3.6);
可调用对象(例如 aliyun SDK);
系统信息(例如环境变量)等。
具体函数代码
分层的好处:
容器层适用性更广,Serverless平台(云服务商)可以预热大量的容器实例,将物理服务器的计算资源碎片化。
Runtime 的实例适用性较低,可以少量预热;
容器和 Runtime 固定后,下载代码就可以执行了。
通过分层,可以做到资源统筹优化,这样就能让代码快速低成本地被执行。
Serverless平台(云服务商)负责的就是容器和 Runtime 的准备,开发者负责的是函数执行阶段。
一旦容器和 Runtime 启动后,就会维持一段时间,这段时间内的这个函数实例就可以直接处理用户数据请求。当一段时间内没有用户请求事件发生(各个云服务商维持实例的时间和策略不同),则会销毁这个函数实例。
用完即毁型:函数实例准备好后,执行完函数就直接结束。这是 FaaS 最纯正的用法。(用完即毁型对传统 MVC 改造的成本太大。)
常驻进程型:函数实例准备好后,执行完函数不结束,而是返回继续等待下一次函数被调用。这里需要注意,即使 FaaS 是常驻进程型,如果一段时间没有事件触发,函数实例还是会被Serverless平台(云服务商)销毁。(常驻进程型就是为了传统 MVC 架构部署上 FaaS 专门设计的。)
触发器就是一个常驻进程型模型一直在等待,只不过这个触发器是由Serverless平台(云服务商)处理的,这两个模型对应两种不同的应用场景。
FaaS 只是做了极端抽象,Serverless平台(云服务商)通过技术手段帮助开发者屏蔽了细节,让他们尽量只关注代码本身。所以,在用完即毁型中,只要将 MVC 的 Control 层部署到函数执行就可以了。这也意味着要将 MVC 架构的 Control 函数一个个拆解出来部署,一个 HTTP 请求对应一个 Control 函数;Control 函数实例启动时连接 MongoDB,一个请求处理完后直接结束。如果要提升 Control 函数的冷启动时间,Model 层同样要考虑 BaaS 化改造。
通常 FaaS 的收费标准,主要有两个维度:调用函数次数和函数耗时。
调用函数次数:函数每次被事件触发,计数器加一。这种模式因为不占资源,所以资源利用率高、收费低。
函数耗时:函数执行的运行时长,计算单位是 CU-S,也就是 CPU 运行了多少秒。
从MVC模式发展为MVVM模式,前端 View 层逐渐前置,发展成 SPA 单页应用;后端 Control 和 Model 层逐渐下沉,发展成面向服务编程的后端应用。
Node.js 的异步非阻塞和 JavaScript 天然亲近前端工程师的特性,自然地接过数据网关层。因此也诞生了 Node.js 的 BFF 层 (Backend For Frontend),将后端数据和后端接口编排,适配成前端需要的数据结构,提供给前端使用。
BFF 层充当了中间胶水层的角色,粘合前后端。未经加工的数据,称为元数据 Raw Data,对于普通用户来说元数据几乎不可读。所以需要将有用的数据组合起来,并且加工数据,让数据具备价值。对于数据的组合和加工,称之为数据编排。
BFF 层通常是由善于处理高网络 I/O 的 Node.js 应用负责。因为 BFF 层只是做无状态的数据编排,所以完全可以用 FaaS 用完即毁型模型替换掉 BFF 层的 Node.js 应用,也就是 SFF(Serverless For Frontend)。
理解了 BFF 到 SFF 的演进过程,再看一下新的请求链路逻辑。
前端的一个数据请求过来,函数触发器触发函数服务;
函数启动后,调用后端提供的元数据接口,并将返回的元数据加工成前端需要的数据格式;
FaaS 函数完全就可以休息了。
除了自己的后端应用数据接口,互联网上还有大量的数据可使用。
编排后端接口,编排互联网上的数据,是比较常见的场景。编排云服务商的各种服务才是Serverless平台起飞的地方。
服务编排和数据编排很像,主要区别是对云服务商提供的各种服务进行组合和加工。
在 FaaS 出现之前,就有服务编排的概念,但服务编排受限于服务支持的 SDK 语言版本,常见的情况是用 yaml 文件或命令行来编排服务。要使用这些服务或 API,都要通过自己熟悉的编程语言去找对应的 SDK,在自己的代码中加载 SDK,使用秘钥调用 SDK 方法进行编排。就和数据编排一样,服务端运维部署成本非常高,而且如果没有 SDK,则需要自己根据平台提供的接口或协议实现 SDK。
现在有了 FaaS 拓展了可以使用 SDK 的边界,比如 Web 服务需要发送验证码邮件:
用一个用完即毁型 FaaS 函数,调用云服务商的 SDK 发送邮件;
再用一个常驻进程型 FaaS 函数生成随机字符串验证码,生成后记录这个验证码,并且调用发送邮件的 FaaS 将验证码发给用户邮箱;
用户验证时,再调用常驻进程型 FaaS 的方法校验验证码是否正确。
这也是FaaS的一个亮点:语言无关性。意味着不再局限于单一的开发语言了,可以利用 Java、PHP、Python、Node.js 各自的语言优势,混合开发出复杂的应用。
FaaS 服务编排被云服务商特别关注正是因为它具备的这种开放性。使用 FaaS 可以创造出各种各样复杂的服务编排场景,而且还与语言无关,这大大增加了云服务商各种服务的使用场景。当然,这对开发者也提出了要求,它要求开发者去更多地了解云服务商提供的各种服务。
用完即毁型(FaaS最纯正的用法)和常驻进程型,最大的区别就是在函数执行阶段,函数执行完之后函数实例是否直接结束。
FaaS 中的扩缩容,可以 FaaS 函数配置中的“单实例并发度”。也就是单个函数实例可以承载多少的并发量,如果事件触发并发量超出了这个并发度,则会自动扩容。或者利用实时监控(单个容器核心指标,包括 CPU 利用率、内存使用率、硬盘使用率、网络连接数等),去控制扩缩容,例如当单个函数实例承受不了时,内存使用率会越来越高,它的执行时间也会越来越长。
纵向扩缩容:增加单机配置,随着性能提升成本曲线会陡
横向扩缩容:增加服务器数量,成本更加可控,也是最常用的默认扩缩容方式
Stateful 就是有状态的节点,用来保存状态,也就是存储数据,因此需要额外关注,需要保障稳定性,不能轻易改动。如果面对流量峰值/峰谷的差比较大时,要按峰值去设计 Stateful 节点来抗住高流量,没有流量时也要维持开销。
Stateless 就是无状态的节点,不存储任何状态,或者只能短暂存储不可靠的部分数据。因此在并发量高的时候,可以对 Stateless 节点横向扩容,而没有流量时可以缩容到 0。
常见stateful节点:
数据库,它要持久化保存用户的数据。
负载均衡,它要保存客户端的链接,才能将 Web 应用的处理结果返回给客户端。
因此,用完即毁型是天然的 Stateless,因为它执行完就销毁,无法单纯用它持久化存储任何值(内存中的数据是无法共享的);常驻进程型则是天然的 Stateful,因为它的主进程不退出,主进程可以存储部分值。
通常,stateless节点与stateful节点(如数据库)连接时,会增加冷启动时间,如果向数据库发送指令,变成 HTTP 访问数据接口 POST、DELETE、PUT、GET,那就可以利用数据编排和服务编排,这就是BaaS化。
数据接口的 POST、DELETE、PUT、GET 就是语义化的 RESTful API 的 HTTP 方法。用 MySQL 举例:
POST 对应 CREATE 指令,
DELETE 对应 DELETE 指令,
PUT 对应 UPDATE 指令,
GET 对应 SELECT 指令,
语义上是一一对应的,因此可以天然地将 MySQL 的操作转为 RESTful API 操作。
传统数据库方式,因为 TCP 链路复用和通信字段冗余低,同样的操作会比 HTTP 快。FaaS 可以直连数据库,但传统数据通过 IP 形式连接往往很难生效,因为云上环境都是用 VPC 切割的。所以 FaaS 直连数据库,我们通常要采用云服务商提供的数据库 BaaS 服务,但目前很多 BaaS 服务还不成熟。
既然 FaaS 不适合用作 Stateful 的节点,那我们是不是可以将 Stateful 的操作全部变成数据接口,外移?这样我们的 FaaS 就可以用数据编排,自由扩缩容了。
BaaS化核心思想就是将后端应用转换成 NoOps 的数据接口,这样 FaaS 在 SFF 层就可以放开手脚,而不用再考虑冷启动时间了,BaaS 化最重要的部分是后端数据接口应用的开发人员也可以不再关心服务端运维的事情。