第1章介绍了 Serverless 的起源、定义及其与前端的关系,本章将介绍我们应该在什么情况下使用 Serverless。在讨论其适用的场景之前,我们需要先了解 Serverless 的优势和劣势,通过对其优劣势的了解,自然能够知道其适用范围。在本章的最后,我们将从前端研发人员的视角出发,介绍 Serverless 在前端中的典型应用场景。
我们在前面已经了解了 Serverless 的两个核心功能:FaaS 与 BaaS。
实际上,BaaS 是在当前云计算产品下的一层封装,底层基于 PaaS 构建。而 PaaS 则已是比较成熟的产品了。我们现在使用的各种云计算服务,如数据库服务、缓存服务、消息队列服务等,已经无须再自行开发、安装和配置,直接在云计算供应商中选择对应的产品即可。比如,AWS 及阿里云等云计算供应商都提供了数据库的云计算产品:RDS(Relational Database Service,关系型数据库服务);同样,对于缓存和消息队列,我们也无须自己安装 Redis 或者KafkaMQ 等开源产品,直接使用云计算产品即可。而 BaaS,则可以被看作 PaaS 的 Serverless化。虽然目前大部分云计算产品已经做到了 Serverless 化,开发者无须关心这些云计算产品背后的部署和运维情况,只需要直接在服务端代码中调用它们对应的 SDK(Software Development Kit)即可;但 BaaS 则更进一步,它免去了在服务端调用该服务,前端再调用服务端的烦琐流程;而是通过提供客户端 SDK 的方式,让研发人员能够直接在客户端中调用这些云计算产品。
除了能够在客户端中直接调用,同样我们也具有在 FaaS 服务的函数中调用这些 BaaS 的能力。使用 FaaS 构建应用,可以让开发者将业务逻辑以代码片段的方式实现;在实现过程中对于云计算服务的依赖,则可以通过 BaaS SDK调用对应的 BaaS 服务来获取。最后,整个应用由一系列代码片段组成。
上面这种通过函数片段来串联各种 BaaS 服务的场景,即典型的 FaaS 与 BaaS 的结合应用。BaaS 自身基于 PaaS;而 PaaS 已是比较成熟的云计算产品,其成熟的产品体系给快速产品迭代所带来的优势是不言而喻的。下面将主要讨论这种新型计算资源交付模式——FaaS的优缺点。
FaaS,代表的是运行函数片段的能力。与传统的虚拟化技术或容器化技术相比,它具备诸多优点,主要包括无须考虑操作系统和文件系统、无须管理系统上的依赖软件、能根据负载情况实现自动扩容/缩容,以及通过这种自动扩容/缩容的弹性计算实现按调用付费。
FaaS 相对来说还是新兴的事物,它的缺点从目前来看也是比较明显的,主要包括缺乏系统性的文档、示例、工具以及被认可的最佳实践。同时由于长期处于云计算供应商各自发展的无组织状态,导致其缺乏标准化实现以及成熟的开源生态。另外,其在函数的调试方面,与本地应用开发比起来也显得不太方便。
因此,对于 FaaS 的特性,我们可以总结如下:
◎ NoOps。我们基本无须关心与运维相关的工作。
◎ 函数是无状态的。因为调度系统会自动地选择扩容或缩容的时机,所以无法在函数中保存一个临时变量,并让这个变量能够在以后的请求中被访问。
◎ 按用量付费。通常以函数的执行时长作为计费依据。
基于这些特性,我们能推导出与传统的虚拟化技术相比,FaaS 具有以下优点(优势):
◎ 更高的研发效率。开发者无须关注函数的可用性问题,可以更聚焦于函数本身。
◎ 更低的部署成本。无须登录到服务器,通过控制台或命令行工具,即可完成服务的部署。
◎ 更低的运维成本。开发者无须担心容量问题,函数将根据负载情况来实现自动扩容/缩容。
◎ 更低的学习成本。操作系统、容器、运行环境等,对开发者都是不可见的。
◎ 更低的服务器费用。采用了按调用量付费的方式,可以大大缩减开发者的服务器成本。
◎ 更灵活的部署方案。每个函数的发布是根据版本进行的,可以更容易地实现灰度发布的能力。
◎ 更高的系统安全性。统一托管运行环境,开发者自身无须接触服务器信息。
同样,我们还应该清楚它目前的缺点(劣势):
◎ 平台学习成本高。由于其是一种全新的架构,因此缺乏文档、示例、工具以及最佳实践。
◎ 调试成本较高。由于运行环境(Runtime)由云计算供应商提供,因此本地调试和日志查询比较困难。
◎ 冷启动时性能可能下降。由于部分语言自身的特性和限制,冷启动时间会较长。
◎ 供应商锁定。缺乏标准化实现以及成熟的开源生态,不同云计算供应商的实现不一致,这导致其迁移困难。
在介绍了 FaaS 的优缺点后,接下来将介绍其典型应用场景。
通过上述优缺点的梳理,我想大家应该已经比较了解适合 Serverless 的场景了。这些场景中的业务,应该具备下面这些特征:
◎ 状态可以通过 BaaS 存储至数据库或其他持久化的服务中;因为函数自身是无状态的,所以我们无法直接在函数中保存这些数据。
◎ 每一个函数都是可以独立工作的,其相互间应该尽量没有依赖,或依赖较小。如果出现大量函数的联动调用,则由于计费系统是按单个函数进行统计的,因此它的计算资源费用就会上升。
◎ 函数长期处于无负载的状态,或负载波动不可预测并且十分剧烈。在长期无负载的状态下,可以自动缩容到零个实例,这可以大幅度地降低成本。同时,不可预测的剧烈波动,也可以使得自动扩容/缩容的能力得以最大化地发挥。
◎ 业务是基于事件驱动的,每一个函数能通过事件触发,这些事件包括 HTTP 请求、数据库修改、定时事件等。
◎ 对冷启动时间没有强烈的性能要求。当缩容到零个实例后,新的请求需要重新初始化容器,这可能导致毫秒至秒级的延迟。
同时,采用 Serverless 架构,对于团队或公司来说,如果其研发模式和理念符合以下特征,则可能更容易完成改造:
◎ 业务是快速变化的,因此需要研发人员提高应用程序的变更速度和频率。
◎ 希望能够降低服务器的运维成本,减少运维人员及相应的开支。
◎ 希望能够降低服务器的机器成本,减少服务器的数量及相应的开支。
◎ 希望提高生产力,并且能承受使用尚未成熟的技术所带来的风险。
如果实际项目越符合以上特征,则越适合采用 Serverless 架构进行研发;但这并不是绝对的。下面我们将基于这些特征,介绍 Serverless 的几个典型示例。
建立在基于视频和图片的用户分享应用,因为具有良好的用户体验,近年来广受欢迎。这些视频和图片涉及的领域已不仅仅是娱乐行业,其目前已经渗透到电商、餐饮等各行各业中。这些应用的核心服务,则是基于多媒体文件的处理技术的。和开发者自己搭建一套转码服务比起来,基于 Serverless 的多媒体文件处理,能够有效地提高文件处理的效率,降低转换所消耗的计算资源成本。
我们以视频为例,若自己搭建一套视频转码服务,则成本相当高昂。这主要是由于视频转码对计算资源的消耗造成的。当用户上传一个视频后,我们希望能够在较短的时间完成转码过程,生成最终文件,这样该视频才能够被其他用户及时地收看。并且,我们还需要考虑同时有多个用户在同一时间上传文件的情况。因此,为了得到较好的用户体验,我们需要保持较快的转换速度。往往需要部署大量的转码服务器才能够应对不确定的视频数量。
视频转码是由用户手动上传视频文件后触发的,因此其需求波动也将受到上传者的影响,比如,凌晨几乎不会有人上传文件。在这种场景下,它对计算资源的需求波动较大,采用Serverless 方案,将有利于提高效率、降低成本。多媒体处理是 Serverless 最早的业务场景应用之一。
图2-1展示了基于Serverless的视频转码服务架构。当用户上传一个视频后,它将被存储到文件存储服务中。然后会自动触发多个函数,其中包括三个用于转码的函数和一个用于封面图片提取的函数。这些函数会将视频处理成不同分辨率的,并且根据算法生成合适的视频封面。最后,再将得到的文件写入文件存储服务中,并同步到 CDN(Content Delivery Network,内容分发网络)。
图2-1 基于 Serverless 的视频转码服务架构
在对数据库进行新增、修改或删除时,如果我们希望能够执行一个方法来进行附加的处理,并且这个处理可能是一个异步操作,那么我们通常有两种实现方式。
第一种方式是将附加处理的代码,接着数据变更之后的代码实现,让它们在一个流程中完成。但这两个操作本身可能并没有相关性,如果只是简单地合并,那么可能会造成这两个功能的耦合,不利于后续扩展。稍好的第二种方式是,当数据库变更时,发送一条消息,我们再通过另一个应用订阅它。但如此一来,我们不得不为了部署这些附加处理的实现而单独部署和维护一个应用集群。
而 FaaS 则在基于消息的基础上免除了应用的维护成本。这种情况非常类似于关系型数据库的触发器功能,那么我们为什么不直接用触发器实现这个附加处理过程呢?实际上,通过FaaS,可以提供比触发器更强大和更灵活的能力。首先,它更加自由,因为无须受数据库触发器所支持的编程语言的约束,所以我们可以选择合适的语言进行编写。其次,测试更加简单,由于触发器对数据库及数据的高度依赖,因此其很难做到自动化测试;而函数则更容易实现高覆盖率的自动化测试。最后,其管理更加方便。无论是多版本的管理,还是灰度功能的控制,触发器都很难提供相应的能力支持,而 FaaS 则更加可控。
下面举一个比较常见的应用场景。假如我们上线了一款全球性的应用。这时候我们的运营人员需要发布一篇公告,那么它应该针对不同国家提供不同语言的版本。业务流程通常是这样的:运营人员会先基于某一语言编写一篇公告,再由翻译人员将其翻译成各国语言,最终发布。为了提高翻译的效率,我们应该给翻译人员提供由机器初步翻译的内容,然后让其进行校对。
基于 Serverless 的自动翻译架构如图2-2所示。当运营人员提交公告后,保存到数据库的数据将触发函数,函数将它们转换为不同语言的版本后进行保存。在保存后,又将触发通知函数,自动发送电子邮件通知翻译人员完成校对工作。
图2-2 基于 Serverless 的自动翻译架构
随着 5G 的普及,IoT (Internet of Things,物联网)必将呈现出爆发式的增长趋势。由于有海量的传感器,并且每个传感器都会向服务器上报数据;因此,如何处理如此大规模的突发流量是我们需要考虑的问题。
以往,我们需要提前评估这些 IoT 设备的最大流量,以确保自己能处理来自所有传感器的瞬间流量。由于不可能一直保持最高并发流量,因此在大多数时候,其计算资源存在被浪费的现象。而通过 FaaS 和它的弹性扩容/缩容能力,现在我们可以更加高效地处理突发流量,同时无须为没有使用的计算资源而买单。
在家庭中,为了实现智能体验,系统首先需要了解用户家庭当前的情况,然后在云端进行分析,最后决策系统将做出某些具体操作。比如当室内温度高于人体舒适温度,并持续一段时间后,系统应该自动关闭门窗,并打开空调,以便将室内温度调整到合适的范围。
要实现上述智能场景,系统必须能够感知环境信息。目前,环境信息通常是通过安装在家庭中的各种传感器(如温湿度传感器、声音传感器等)或智能设备(如智能水表、智能冰箱、智能门窗)上报而来的。图2-3展示了基于 Serverless 的 IoT 数据采集架构,这些上报数据通过 FaaS 进行处理后汇总到数据库,以便进行后续的智能决策。
图2-3 基于 Serverless 的 IoT 数据采集架构
随着手机智能助手以及智能音箱的普及,目前聊天机器人已开始被人们所接受和喜爱。一款智能音箱通常会包含很多功能,比如讲故事、放音乐、定闹钟、查快递、叫外卖等等,我们将这些功能统称为技能(Skill)。由于各个技能是相互独立的,因此它们在实现上通常也隶属于不同的应用。
一款智能音箱通常有成千上万个技能,如果每个技能都由单独的应用实现,则这必将带来大量的运维成本并产生高昂的服务器费用。实际上,在这些成千上万的技能中,长尾效应十分显著。因此有大量的技能是处于“冷冻”状态的,其访问量很低。这些技能所占用的服务器资源长期处于闲置状态。因此,通过 FaaS 替代传统的应用架构,将大幅节省这些不必要的服务器费用,同时还将降低研发人员的运维成本。
另外,在我们与聊天机器人对话的时候,它们无须从一开始就提供毫秒级的响应,具有一定延迟的回复反而可能会使对话感觉更加自然。因此,当基于 FaaS 实现这个能力时,之前的启动时间的短板对于这个场景来说反而是一个优点。
图 2-4 展示了基于 Serverless 的聊天机器人架构。所有的技能(Skill)通过函数来实现,如此一来这些技能可以相互独立地开发、测试和发布;但又不会因为过于分散,而占用大量的服务器资源。
图2-4 基于 Serverless 的聊天机器人架构
计划任务,即提前设置并定制执行的任务,这是很多业务场景中比较常见的一种需求。比如作为前端研发人员,我们需要了解 Web 应用中用户的使用情况,这时候需要用到页面打点和统计工具。这些工具每天都将生成一份报表;而数据则一般是通过在凌晨的固定时间计算前一天站点的页面访问情况,最终汇总后生成的。对于访问情况,我们更关注的是趋势,而不是实时情况。因此,这些数据的统计无须做到实时,每天计算一次即可。
如图 2-5 所示,这些每天只需要执行几分钟,并且需要消耗大量计算资源的任务,非常适合使用 FaaS 执行。基于弹性伸缩的能力,这些服务可以在较短的时间里有效地完成计算。而在一天的其他时间,开发者无须为闲置的服务资源付费。
图2-5 基于 Serverless 的计划任务
提供 Web API 的后端服务,是我们最常见的研发场景之一。对于前端研发人员来说,我们通常会使用 Node.js 构建后端服务。然而由于 Node.js 的特性和相关工具的不完善,针对内存泄漏的排查工作一直是一个老大难问题。这也是 Node.js 的服务稳定性很难得到保障的原因所在。
而使用 FaaS 进行部署后,由于其动态扩容/缩容的操作,使得某一容器并不会长期地存在,因此内存泄漏这一问题有所缓解。在传统的 Node.js 应用中,为了提高应用整体的稳定性,我们会使用 PM2(参见链接 1)一类的守护进程(daemon)来部署真正的工作进程(worker)。这样,即使出现内存泄漏导致工作进程退出,守护进程也会立即启动一个新的工作进程。因此,PM2 是从进程级别来确保服务可用性的;而与 PM2 不同,FaaS 则直接从容器层面来保障服务的可用性。对研发人员来说所有函数都是即用即走的,其不用知道容器是如何被启动、销毁的,也无须担心由于异常而导致应用程序退出。
因此,通用后端服务改由 FaaS 提供后,我们不必再担心一些异常情况的出现,也无须人工地设置守护进程。若真正地出现了进程的异常退出,那么该容器会被立即销毁,并重新启动一个新的容器。同时,系统会将该异常的详细情况以邮件的方式通知研发人员,让其能够立即知晓,并展开原因的排查工作。
图2-6展示了基于 Serverless 的后端服务架构。由于 FaaS 自身并不支持 HTTP 协议,因此还需要通过 API 网关(API Gateway)来对外提供 Web API。
图2-6 基于 Serverless 的后端服务架构
上面我们讨论了关于 FaaS 的服务端应用场景,那么站在前端研发人员的角度,在前端领域中 Serverless 可以应用在哪些场景呢?
FaaS 作为下一代云计算资源的交付模式,可以用来取代以往 Node.js 完成工作的方式。除了前面提到的通过 FaaS 提供 Web API 的通用后端应用,还有下面4个应用场景。
对于 Web 应用,前端研发人员一般会使用 Node.js 来实现服务端部分。我们以实现一个单页面应用(SPA,Single Page Application)的服务端为例,它通常有两部分功能:应用入口页面和 Web API。
针对应用入口页面,由于 SPA 应用的路由控制均在前端完成,因此页面请求总是被定向到一个固定的 HTML 文件,再通过 HTML 加载相应的 JavaScript Bundle 文件,在浏览器中完成路由。而 Web API 则是在框架中通过函数实现的,并且函数将绑定到一个对应的路由中。为了实现方便,我们会选用一个 Web 框架(如 Koa、Express 或 Egg)。完成开发后,我们会将这个包含一组 Web API 的 Node.js 应用部署到服务器上。
在大多数情况下,这些 Web API 的请求量并不是平均分配的。可能有时候查询请求较多(如商品浏览),但也有可能会有突增的写入请求(如“秒杀”活动)。并且,提供不同能力的服务也需要不同的计算资源。比如写入通常比查询更加复杂,所以会消耗更多的服务器资源。因此针对不同时期的流量进行预测,是一件较为困难的工作。若评估得太高,将导致计算资源的浪费;而评估得太低,则会造成用户请求时间的增长,甚至导致其请求超时。
基于弹性计算的 FaaS 架构可以更好地适应这一场景。入口页面直接通过网关返回,而Web API 则可以更灵活地实现。每一个 API 变得可以独立评估,从而更准确地预估服务器的负载情况。并且,这些服务可以根据自己的负载情况自动扩容/缩容从而独立计费,这使得对服务器资源的管理和利用更加精细化。
SSR(Server Side Rendering,服务端渲染)是最近前端的热门话题。在主流的前端方案中,我们通常使用 React、Vue、Angular 等前端框架来构建应用程序。这样,页面的渲染实际上是在客户端中完成的。也就是说,浏览器会首先加载框架页面和 JavaScript 代码,随后通过 Ajax请求数据,之后将请求的数据填充到模板中,得到完整的页面。而基于 SSR 的实现方式,则把上述页面的初始化过程放在了服务端来完成。服务端在内部完成数据接口的请求之后,会直接将数据填充到模板中,并将最终的页面文件返回给客户端。这样减少了前端 JavaScript 代码的加载过程,也无须单独发起 Ajax 请求来获取数据。这使得页面的初始化过程得到了大幅提速。除了能有效降低首次访问的页面渲染时间,SSR 技术对 SEO(Search Engine Optimization,搜索引擎优化)也将更加友好。这对于以信息展示为主的站点,较为重要。
然而,从本质上来说,SSR 并不是一种新技术。早在十几年前 ASP、JSP、PHP 之类的服务端动态脚本语言,以及后来 Node.js 下的 EJS、jade、nunjucks 等模板引擎渲染技术出现时,实际上页面就已经是由服务端输出的了。直到后来 SPA 的流行,页面的渲染才转移到客户端中进行。但 SSR 与前面这些技术不同的是,它让用户在首次访问时,由服务端渲染页面;而在后续的交互中,又通过 SPA 的方式完成渲染。可以认为,SSR 是在纯服务端渲染(如 ASP)与纯客户端(如 SPA)渲染之间的一种方案。它既通过客户端渲染,保障了良好的页面交互体验;又通过服务端渲染,让页面首次打开的速度得以提升。
而基于 FaaS 来提供 SSR 输出,让我们可以更方便地使用服务端计算资源。将每一个函数绑定到一个 URL,当用户请求这个 URL 时,该函数将返回一个对应的 SSR 页面。因此,我们只需要完成函数的编写并发布即可,剩下的运维工作都将由平台完成。
这种按函数来返回页面的方式,让我们能够灵活地实现组装。我们可以将页面切分为多个区域,而每一个区域由特定的函数生成。这样,当用户打开页面时,我们可以优先请求并加载某一区域的页面代码。同时,这些不同区域的模块可以十分灵活地复用到不同的页面中。并且,我们可以根据不同的更新频率来缓存这些区域,从而进一步提高服务的性能。其架构如图 2-7所示。
图2-7 基于 Serverless 的 SSR 架构
这种将页面划分为多个区域的方式,进一步封装之后,被称为组件即服务(Component as a Service)。这里的每个“组件”均通过 FaaS 的函数提供。
移动客户端应用的研发人员,通常会将精力聚焦于 App 的研发中。使用 Serverless 架构,我们可以通过 BaaS 更轻松地集成和使用对应的服务端能力。
另外,将原有服务端的业务逻辑通过 FaaS 实现,可以有效地提升服务端业务逻辑的开发效率。这使得研发人员无须在开发移动客户端应用的同时,关注服务端的可用性等问题。这可以让研发人员将精力更加聚焦在移动客户端应用自身的开发中,加快 App 的迭代速度。
该场景和上述移动客户端应用较为类似,通常一个小程序同样由一个对应的服务端来提供Web API。在传统架构下,研发人员将使用云计算产品来实现服务端功能。因此,小程序的研发工作,一般不是由前端工程师独立完成的,而需要涉及一个研发团队。
通过 Serverless 架构的实施,将改变这一现状。目前微信及支付宝等小程序平台均已提供了相应的 Serverless 云计算服务,包括 FaaS 及 BaaS 两大功能。研发人员通过 FaaS 编写Web API,并通过 BaaS 来使用必要的云计算服务(如数据库服务、文件存储服务等),以完成小程序的后端开发工作,这使得小程序研发人员的服务端研发成本大幅降低。Serverless 在小程序的落地将更有利于小程序的应用和普及。
对于在小程序场景中,基于云计算供应商提供的 Serverless 服务如何实施,我们将在本书的第3部分具体讲解。
在本章中,我们在一开始介绍了 Serverless 架构的优势与劣势,随后通过案例讲解了Serverless 在服务端与前端的不同应用场景。但这些并不是 Serverless 仅有的应用场景,随着Serverless 技术的普及,它将会应用到更广泛的场景中。希望读者在了解了 Serverless 架构的特性和优缺点后,能够基于这些特点,探索 Serverless 的更多应用场景。
在接下来的两章中,我们将分别介绍 Serverless 与目前主要的服务端技术和前端技术之间的关系,以便于我们更好地理解 Serverless 在这些技术中的位置,从而决定它们的使用方式。