原文地址:https://martinfowler.com/articles/serverless.html
作者:Martin Fowler, Mike Roberts
1. 摘要
无服务器架构是一种应用程序设计方法,它合并了第三方“Backend as a Service”(BaaS)提供的服务,和/或运行在FaaS(Functions as a Service)平台中的用户代码。使用这样的思路并结合一些类似spa(单页应用)的应用,设计出的架构消除了对传统常驻服务器组件的大部分需求。无服务器架构将受益于显著降低的操作成本、复杂性和工程领先时间,但也会因依赖于服务提供商和相对不成熟的支撑技术而增加成本。
2. 引言
“Serverless computing”,或简称为“Serverless”,是软件架构世界中的一个热点主题。三大云厂商- Amazon, Google, and Microsoft都对无服务器架构进行了重点布局。我们已经看到许多许多书籍、开源项目、会议和软件厂商在致力于这个领域。但是什么是无服务器架构,它是否值得研究?通过这篇文章中我希望能够抛砖引玉。
3. 什么是无服务器架构
对于无服务器架构没有一个清晰的视图。对于初学者,它包含两个不同但重叠的区域:
- Serverless最初用于描述那些完全包含第三方的、云托管的、管理服务器端逻辑和状态的应用和服务的应用程序。这些都是典型的“富客户端”应用程序(考虑单页的web应用程序,或者移动应用程序),它们使用可访问云数据库的庞大生态系统(如Parse、Firebase)、身份验证服务(例如Auth0、AWS Cognito)等等。这些类型的服务以前被描述为““Backend as a Service”,在本文中,我使用“BaaS”作为简写。
- 无服务器架构也意味着应用程序开发人员仍然需要编写服务器端逻辑的应用程序,但是,与传统的体系结构不同,它是在无状态计算容器中运行的。这些容器是事件触发的、临时的(可能只持续一次调用就挂),并全管理由第三方完。有人也把其称之为“函数即服务”或“FaaS”。AWS Lambda是目前最流行的FaaS平台之一,当然也有一些其它的实现平台。
在本文中,我们将主要关注FaaS。它不仅是一个更新和更着名气的领域,而且它与我们常用的技术架构思想有很大的不同。
BaaS和FaaS在它们的操作属性上是相关的(如没有资源管理),并且经常一起使用。大型云供应商都有“无服务器架构的产品组合”,包括BaaS和FaaS产品(例如,here’s Amazon’s Serverless是亚马逊的无服务器产品页面。Google的Firebase BaaS数据库通过使用Google Cloud Functions for Firebase提供显式的FaaS支持)。
更多的公司也开始涉足于这两个领域。Auth0开始使用实现了许多用户管理方面功能的BaaS产品,并随后创建了配套的FaaS服务Webtask。现在该公司已经提供了 Extend,这使得其他SaaS和BaaS公司能够轻松地将FaaS功能添加到现有产品中,以创建一个统一的无服务器架构产品。
3.1. 两个示例
3.1.1. UI驱动的应用
让我们考虑一个传统的三层面向客户的系统,它具有服务器端逻辑。一个典型的电子商务应用就是很好的例子就是...我可以举在线宠物商店的例子吗?
传统架构将如下图所示。如它在服务器端用Java或Javascript实现的,在客户端使用HTML+Javascript实现:
使用这种体系结构,客户端可能相对薄弱,系统中的许多逻辑功能如身份验证、页面导航、搜索、事务,都要由服务器应用程序实现。
如果使用无服务器架构,看起来系统是这样的:
尽管上图是一个简化后的视图,但我们仍从其中看到许多变化:
1. 我们看到原来应用中的鉴权逻辑已经被第三方的Baas服务所替代(如Auth0)。
2. 使用另一个Baas服务,我们可以看到客户端可以直接访问我们一个数据库子集(完全由第三方提供,例如Google Firebase)。相比于之前在服务端访问数据库资源,可以在客户端通过实施不同的安全策略来访问数据库。
3. 前面的两点引导出了重要的第三点:在这个宠物店服务器端的一些应用逻辑已经转移到了客户端,如维护一个用户会话,从数据库中读取数据并将其转换为可用的视图。客户端变成了一个SPA(单页面应用)。
4. 我们也希望在服务端保留一些与用户体验相关的功能,例如访问大量的数据并做密集计算。在我们的宠物店商店中,这样的一个功能可能是“查询”。不像原来架构中需要一直在运行的服务器,可以实现一个响应经过API网关的HTTP请求的FaaS函数达到这一目的。客户端和服务端的“查询”功能都是从同一个产品数据库中读取数据。如果选择AWS Lambda作为FaaS平台,可以将原来的查询代码原封不动地移植过来,因为Lambda支持java和javascript语言。
5. 最后,可以用另一个单独的FaaS函数来替代我们的“购买”功能,出于安全考虑,选择将它放在服务器端,而不是在客户端重新实现它。它也依赖于一个API网关。在使用FaaS时,将不同的逻辑需求分解为单独部署的组件是一种非常常见的方法。
退一步说,这个示例展示了另一个对无服务器架构而言非常重要的观点:在传统架构中,所有流、控制和安全性都由中央服务器应用程序管理,而在无服务器架构中,没有处理这些的中央控制器。相反,我们倾向于通过对组件的编排完成目标(choreography over orchestration),将每个组件都设计为更易被架构感知,这种设计思路在微服务方法中也很常见。
这种设计方法有很多好处。正如Sam Newman在他的“ Building Microservices" 这本书中指出的那样:以这种方式构建的系统通常“更灵活,更易于改变”,既可以对于整体,也可以通过对组件进行系统的更新;具有更好的划分关注点;还有一些令人着迷的成本效益,Gojko Adzic在 这次精彩的演讲中讨论了这个问题。
当然,这样的设计也需要权衡:它需要更强大的分布式监控,并且依赖于底层平台安全能力。更重要的是,相比于单块系统,在我们的头脑中环绕着更多的灵活组件,而且增加多个后端组件的带来的复杂性会让我们考虑其所带来的灵活性和降低成本是否值得。如何权衡需要依赖于具体应用上下文。
3.1.2. 消息驱动的应用
另一个例子是后端数据处理服务。
假设您正在编写一个以用户为中心的应用程序,它需要快速响应UI请求,其次,它需要捕获正在发生的所有不同类型的用户活动,以便进行后续处理。考虑这样一个在线广告系统:当用户点击一个广告时,需要快速地将他们重定向到广告目标。与此同时,你需要收集对这个广告的点击事件以便于向广告客户收费(这个例子是真实的,我以前在Intent Media里的团队有这样的需求,他们以一种无服务器方式实现了这个需求)。
使用传统方法,体系结构可能如下图所示。“Ad Server”同步响应用户(未显示在图中),并将“click message”事件发送到一个消息通道。然后,这个消息由一个更新数据库的“click processor”应用程序异步处理,例如,用来根据统计信息减少广告客户的预算。
如果使用无服务器架构,看起来是这样的:
你能看出其中的区别吗?与我们的第一个示例相比,这里的体系结构的变化要小得多——这就是为什么异步消息处理对于无服务器技术来说非常流行的原因。我们用FaaS函数替换了一个常驻的消息消费者应用程序。这个函数在云平台供应商提供的事件驱动上下文中运行。请注意,云平台供应商应同时提供message broker和FaaS环境——这两个是紧密联系的。
FaaS环境也可以通过实例化函数代码的多个副本来并行处理多条消息。这可能是我们迁移原来代码时需要考虑的一个新问题。
3.2. 解密FaaS
我们已经提到了FaaS,现在是时候深入研究它真正的含义了。为了做到这一点,我们来看看亚马逊的FaaS产品Lambda的开头描述。在这段描述中已经添加了一些标记。
...
AWS Lambda允许您在没有配置或管理服务器的情况下运行代码。(1)…使用Lambda,您几乎可以运行任何类型的应用程序或后端服务(2)这些程序都是零管理,只要上传你的代码,Lambda就会处理、运行(3)和缩放(4)你的代码以实现高可用性。您可以设置您的代码从其他AWS服务自动触发(5),或者直接从任何web或移动应用程序调用(6)。
...
1. 从根本上说,FaaS运行后端代码不需要考虑服务器系统或常驻应用程序的管理。如果回到之前的点击处理示例,FaaS将取代点击处理服务器(可能是一个物理机器,但肯定是一个特定的应用程序),它不需要一个指定的服务器,也不需要一个常驻的应用程序。
2. FaaS产品不需要对特定的框架或库进行编码。当涉及到语言和环境时,FaaS函数是常规的应用程序。例如,AWS Lambda函数可以在Javascript、Python、Go、任何JVM语言(Java、Clojure、Scala等)或任何.NET语言中实现“first class”。然而,您的Lambda函数也可以执行与它的部署组件捆绑在一起的另一个进程,因此实际上可以使用任何能编译成Unix进程的语言(参见Apex)。
FaaS函数还是具有特定架构限制的,特别是在状态和执行时间方面。我们很快就会讲到。
我们仍然考虑一下点击处理示例。在迁移到FaaS时,惟一需要更改的代码是“main方法”(启动)代码,它将被删除。还有就是顶层消息处理程序(“消息侦听器接口”实现)的特定代码,但可能仅需要更改一下方法签名。代码的其余部分(例如,写入数据库的代码)在FaaS世界中没有什么不同。
3. 部署方式与传统系统非常不同,因为不需要使用服务器应用程序来运行。在FaaS环境中,将函数代码上载到FaaS提供商,提供商则负责提供所有需要的资源、实例化vm、管理流程等其他一切必要的内容。
4. 水平扩展是完全自动的、有弹性的,并且由提供商管理。如果您的系统需要并行处理100个请求,那么平台会自动处理这种伸缩性。执行函数的“计算容器”是暂态的,FaaS何时创建和销毁它们纯粹由运行时需求驱动。最重要的是,使用FaaS,提供商处理所有底层资源供应和分配——用户根本不需要集群或VM管理。
让我们回到点击处理器。比如说我们今天的点击率不错,顾客们点击的广告数量是平时的十倍。对于传统的架构,我们的点击处理应用程序能够处理这个问题吗?例如,我们是否开发了能够一次处理多个消息的应用程序?如果我们这样做了,应用程序的一个运行实例是否足以处理这样的负载?如果运行多个进程,自动伸缩配置需要手动配置吗?有了FaaS,所有这些问题都已经得到了回答,当然所编写函数需要适配并行伸缩机制, FaaS实现自动处理所有伸缩需求的机制。
5. FaaS中的函数通常由提供商定义的事件类型触发。Amazon AWS中的触发方式包括S3(文件/对象)更新、时间(预定任务)和添加到消息总线的消息(例如,Kinesis)。
6. 大多数提供商还允许函数作为入站HTTP请求的响应触发函数;在AWS中,通常通过使用API网关来实现这个功能。在前面的宠物商店示例中,使用了一个API网关来进行“搜索”和“购买”功能。函数还可以通过平台提供的API直接调用,但这是一种相对少见的用法。
3.2.1. 状态
当涉及到本地(机器/绑定实例)状态时,FaaS函数有很大限制。如存储在内存变量的数据,或者写入本地磁盘的数据。不能保证这种状态数据会在多个调用中保持,而且更重要的是,不能假定一次函数调用的状态可用于同一函数的另一次调用。因此,FaaS函数通常被描述为无状态的,更准确地说,任何需要持久的FaaS函数状态都需要在FaaS函数实例之外实施。
对于FaaS函数来说,它们是天然无状态的,如对那些提供对输入进行纯功能转换输出的应用。但对另一些应用来说,这可能会对应用程序架构产生很大影响,尽管不是唯一的——“Twelve-Factor app”概念也有完全相同的限制。这种面向状态的函数通常会使用数据库、跨应用的缓存(如Redis)、或网络文件/对象仓库(如S3)用于在请求之间存储状态,或者提供处理请求所需的更多输入。
3.2.2. 执行时间
FaaS函数通常对每次调用允许运行的时间有限制。目前,AWS Lambda函数对事件响应的“超时”最多为5分钟,然后会终止。微软Azure和Google云功能也有类似的限制。
这意味着FaaS函数不适合某类长时间运行任务。因而需要重新设计架构——可能需要创建几个不同的协同FaaS函数,而在传统环境中,可能有一个常驻运行任务进行协调和执行。
3.2.3. 启动延迟和“冷启动”
一个FaaS平台需要一些时间在处理每个事件之前初始化一个函数实例。这种启动延迟对于一个特定的功能可能会有很大的不同,根据不同上下文,可能在几毫秒到几秒之间。这听起来的确糟糕,但是让我们花点时间研究一下AWS Lambda的例子。
Lambda函数的初始化是一个“热启动”过程,也是一个“冷启动”过程:重用一个处理先前事件的Lambda函数实例及其宿主容器是一个“热启动”过程;创建一个新的容器实例、启动一个函数宿主进程是一个“冷启动”过程。很明显,启动延迟主要是这些“冷启动”所带来的。
冷启动延迟依赖于许多因素:使用的语言;使用的库;代码数量;使用的Lambda函数环境变量;是否需要连接VPC资源等等。但这些因素中的大部分都是开发者可控的,因此通常可以优化这些因素以降低启动延迟。
与冷启动时间同样重要的是冷启动频率。例如,如果一个函数每秒处理10个事件,每个事件的处理时间为50ms,你将会看到一次冷启动可处理100,000–200,000个事件。但如果一个事件需要一个小时来处理,你将会看到每次处理一个事件都需要一次冷启动,因为Amazon的每个Lambda函数实例只持续数分钟。了解这些可以帮助你了解冷启动是否会产生影响,以及是否想要执行“保留”函数实例,以避免它们被释放。
是否需要关注冷启动带来的影响依赖于应用程序应用形态与业务类型。我的一个Intent Media团队使用JAVA实现了一个异步消息处理Lambda app,这个app每天处理上亿的消息,但是团队并不关心这个组件的启动延迟。也就是说,如果你正在编写一个低延迟的交易应用程序,可能不希望在这个时候使用FaaS平台。
不管你是否认为你的应用程序是否存在这样的问题,都应该使用类似于生产环境的负载来测试性能。如果用例现在不起作用,可在几个月内再次尝试,这也是FaaS供应商不断改进的主要领域。
关于冷启动的更多细节,可参考我的另一篇文章。
3.2.4. API网关
我们之前对无服务器架构关注的一个方面是“API网关”。一个API网关是一个HTTP服务器,其中路由和端点是在配置中定义的,并且每条路由都与一个资源相关联,以处理该路由。在无服务器架构中,这样的处理程序通常是FaaS函数。
当一个API网关接收到一个请求时,它会找到匹配请求的路由配置,并且,在一个由 FaaS-backed支持的路由的情况下,将调用相关的FaaS函数以响应原始请求。通常,API网关将允许从HTTP请求参数映射到FaaS函数中的输入参数,或者允许整个HTTP请求通过(通常作为一个JSON对象)。FaaS功能将执行它的逻辑,并将结果返回给API网关,后者又将这个结果转换为HTTP响应,并将其传回给原始调用者。
Amazon Web Services有自己的API网关(稍微有点混乱地命名为“API网关”),其他供应商也提供类似的功能。Amazon的API网关是一个BaaS(是的,BaaS!)服务,是一个配置的独立外部服务,但你自己不需要提供和运行它。
除了纯粹的路由请求之外,API网关还可以执行身份验证、输入验证、响应码映射等等。(如果你认为这真的是一个好主意,你的感觉会刺痛你的想法!我们稍后再考虑这个问题。)
带有FaaS功能的API网关的一个用例是:以一种无服务器的方式创建基于http的前端微服务,进行FaaS函数的扩展、管理和其它功能。
当我第一次写这篇文章的时候,Amazon API网关的工具,还是非常不成熟的。但现在这些工具已经有了很大改进。像AWS API网关这样的组件并不是很“主流”,但希望它们会继续改进。
3.2.5. 使用工具
上面关于工具成熟度的评论也适用于无服务器的FaaS。在2016年,情况相当糟糕;到2018年,我们已经看到了显著的改善,我们预计工具将会变得更好。
在FaaS世界中,有几个值得注意的“开发者用户体验”的例子值得一看。首先是Auth0 Webtask,它在工具中将开发者用户体验放置了极高的优先级。其次是微软的Azure函数产品,微软一直将Visual Studio的紧密反馈循环放在其开发产品的首位,而Azure函数也不例外。考虑到来自云触发事件的输入,Azure函数提供了本地调试功能的能力,这是非常特别的。
一个仍然需要显著改善的领域是监控。
3.2.6. 开源
到目前为止,我主要讨论的是商用提供商的产品和工具。大多数无服务器应用程序都使用了这些服务,但还是存在一些开源项目的。
在无服务器中,开源最常见的一般是FaaS工具和框架,尤其是流行的 Serverless Framework,它的目标是使使用AWS API网关和Lambda变得比使用AWS提供的工具更容易。它还提供了大量的跨提供商工具抽象,一些用户认为这是有价值的。类似工具的例子包括Claudia和Zappa。另一个例子是Apex,这一点特别有趣,因为它允许你使用除Amazon直接支持的语言之外的语言开发Lambda函数。
不过,在开源工具派对上,大厂商自己也不会落后。AWS自己的部署工具SAM(Serverless Application Model)也是开源的。
专用FaaS的主要好处之一是不必担心底层的计算基础设施(机器、vm、甚至容器)。但是如果你想要关心这些事情呢?也许你有一些云供应商不能满足的安全需求,或者已经购买了一些并不想丢弃的服务器机架。在这些场景中,开源软件能够帮助你运行自己的“服务器化”的FaaS平台吗?
是的,已经有大量这方面的案例了。在开源FaaS方面的领导者是IBM(它的OpenWhisk项目已经成为一个Apache项目)。微软在这方面的产品是 Azure Functions 平台。许多其他自托管FaaS实现都使用了底层容器平台(如Kubernetes)。在这个领域,我们有必要研究一下像Galactic Fog, Fission和 OpenFaaS这样的项目。这是一个技术飞速发展的时代,我建议可以看看云计算联盟(CNCF)Serverless Working Group 所做的工作。
3.3. 什么不是无服务器架构
在这篇文章中,我将无服务器架构描述为两个概念的结合:BaaS和FaaS。并且详细讨论了后者。为了更精确地了解我所认为的无服务器技术关键属性(以及为什么我认为即使像S3这样的老牌服务也属于无服务器架构),我推销一下我的另一篇文章:Defining Serverless。
在开始关注无服务器架构的优缺点之前,我想再花一点时间来定义一下什么不是无服务器架构。
3.3.1. 与PaaS比较
考虑到无服务器的FaaS函数非常类似于 Twelve-Factor 应用, 它们只是像Heroku这样的PaaS的另一种形式吗?我引用 Adrian Cockcroft的一段话来回答这个问题。
If your PaaS can efficiently start instances in 20ms that run for half a second, then call it serverless. -- Adrian Cockcroft
换句话说,大多数PaaS应用程序不会为了响应一个事件而波及整个应用,而FaaS平台却是这样的。
如果我是一个优秀的Twelve-Factor应用程序开发人员,虽说并不一定会影响我对应用程序的编写与架构设计工作,但在操作方式上会产生很大影响。因为我们都是深受DevOps影响的工程师,我们对运营考虑得与开发一样多,对吧?
FaaS和PaaS之间的关键操作差异是应用的伸缩性。一般而言,使用PaaS,仍然需要考虑如何扩展(例如Heroku,你要考虑运行多少个Dynos?),有了FaaS应用程序后这些工作完全透明。即使我们将PaaS应用程序设置为自动伸缩,也无法对单个请求级别进行这样的操作(除非有一个非常具体的流量配置文件),因此,当涉及运维成本时,FaaS应用程序的效率要高得多。
考虑到这个优点,为什么还要使用PaaS呢?原因有很多,但最大的影响因素是工具。仍然有人在使用像Cloud Foundry这样的PaaS平台,能够在混合云上提供相同的开发体验;但直到现在,还没有一个类似功能的成熟FaaS出现。
3.3.2. 与容器比较
使用无服务器架构FaaS的一个原因是为了避免在操作系统级管理应用程序进程。如Heroku这样的PaaS服务也提供了这种功能。容器是现在流行的一种抽象技术,Docker是这种技术的代表。Mesos和Kubernetes这样的容器管理系统,从操作系统级部署中抽象出单个应用程序,越来越受欢迎。在这条道路上,我们可以看到像Amazon ECS和EKS这样的云托管容器平台,以及Google Container Engine,它们就像无服务器FaaS一样,让团队不用再去管理自己的服务器主机。容器技术的发展推动了无服务器FaaS。
我觉得PaaS与FaaS的区别仍然与容器有关:对于无服务器FaaS扩展是自动管理的、透明的、细粒度的,这与我前面提到的自动资源配置和分配有关。而传统的容器平台仍然需要事先配置好所管理集群的大小和形态。
我认为目前容器技术仍然不成熟和稳定,尽管它正变得越来越好。
当然,现在容器平台中也可以实现自动伸缩功能,如Kubernetes的"Horizontal Pod Autoscaling"特性,而 AWS Fargate也推出了“无服务容器”。
正如我们所看到的,在无服务器FaaS和容器托管两者之间,管理和扩展特性之间的差距缩小了,如何选型最后仅仅归结为应用风格和应用类型的问题:如FaaS可能被认为是构建拥有不同事件类型的事件驱动组件的更好选择,而容器被看作是构建拥有多个入口点、同步请求驱动组件的更好选择。
我期望在相当短的一段时间内,更多应用程序和团队将同时使用这两种架构方法,并且看到这种应用模式的出现将是令人着迷的。
3.3.3. 免维护(NoOps)
无服务器架构并不意味着“没有操作系统”。
“Ops”的含义不仅仅包括服务器管理。它还意味着监控、部署、安全、网络、支持,以及日常的一些生产调试和系统扩展。这些问题仍然存在于无服务器架构应用中,仍然需要使用策略来处理。在无服务器架构应用中进行运维在某些方面甚至更困难。
无服务器架构的应用中仍然存在系统管理员,只不过对于开发者而言变得不可见,相当于开发者将对系统的管理外包给了无服务器架构的提供商。
3.3.4. 存储过程作为服务
我看到其它一些观点:无服务器FaaS是“存储过程即服务”。这是因为许多FaaS函数的例子(包括我在本文中使用的一些函数)的确都是与数据库紧密集成的小段代码。如果这就是我们所能使用的FaaS全部,则这个名字是准确的,但实际上这些只是FaaS能力的一个子集。
考虑一下存储过程带来的问题:
1. 通常需要软件提供商定义的特定语言,或在指定框架上对某一个语言的扩展。
2. 因为存储过程的执行依赖于数据库的上下文,因此非常难以测试。
3. 版本控制很麻烦。
让我们看看这些特点是否也在FaaS上出现:
问题1不会在FaaS中出现,所以我们可以马上把它从列表中删除。对于问题2,因为我们处理的仅仅是代码,所以单元测试肯定和其他代码一样简单,集成测试问题稍后会讨论。对于问题3,由于FaaS函数只是代码,因此版本控制没有问题,而且相关工具也不断成熟,如Amazon的 Serverless Application Model(SAM)和前面提到的无服务器框架。在2018年初,亚马逊甚至推出了一个“Serverless Application Repository”(SAR),为组织提供了一种分发应用和应用组件,构建AWS Serverless 服务。