一文详解为什么 Serverless 比其他软件开发方法更具优势?
本文定义并解释了 Serverless 与其他应用程序架构的不同之处,然后“证明”了 Serverless 应用程序架构在实施得当的情况下会优于非 Serverless 架构。最后总结了很多经验法则,帮助架构师和开发人员实现 Serverless。
关键要点
- Serverless 应用程序不涉及操作服务器,并且应用程序的运行时间完全委托给了服务提供者。
- Serviceful Serverless 应用程序是一种尽可能利用第三方服务实现后端功能的应用程序。
- Serviceful Serverless 应用程序的主要优点是,与其他方式构建的应用程序相比,它们需要的后端代码数量要少很多。
- 更少的代码意味着更少的技术债务,更好和更一致的持续软件开发速度,并为普通开发人员提供更好的可维护性。
- 基础设施即服务的兴起引发了一种新的软件开发最佳实践(“云原生”),Serverless 也是如此。你不可能将云原生应用程序迁移到功能即服务(FaaS)平台,并期望它们会获得最好的设计。
大多数关于 Serverless 应用程序的文章都没有提供足够的细节来解释为什么 Serverless 比其他软件开发方法更具优势,或者在解释为什么 Serverless 比非 Serverless 解决方案更好时显得有点杂乱无章。本文解释了 Serverless 与其他应用程序架构的不同之处,然后“证明”了 Serverless 应用程序架构在实施得当的情况下会优于非 Serverless 架构。最后总结了很多经验法则,帮助架构师和开发人员实现 Serverless。本文对作者在 QCon NY 2018 和 Serverlessconf SF 2018 大会上提出的概念和示例进行了扩展。
我们在优化什么?
为一般开发者考虑
如果说软件正在蚕食世界,那么企业的成功在越来越大的程度上依赖于自身构建和部署软件的能力。由于大多数现代软件通常都是作为软件即服务(SaaS)提供的,因此软件开发是永久性的:企业需要随着时间的推移增加软件开发资源。因此,能够长期推动良好软件开发速度的企业应该胜过那些无法做到这一点的企业。
如果我们想要成为成功的软件开发企业,我们应该想办法让普通软件开发人员能够更容易地维护软件。让企业无法保持合理开发速度的主要痛点通常被描述为“技术债务”,但其背后的原因通常与短期思维(写代码时不考虑可维护性)或对未来开发人员的能力假设有关(假设写代码的团队会一直是维护代码的团队)。
代码越少,复杂性越低
如果我们想要让软件变得长期可维护,需要遵循两个基本策略:更少的定制代码和更少的复杂性。通常,维护较少的代码比维护较多的代码会更容易。请注意,这里是指定制代码。使用文档化程度较高且维护良好的库或服务可以避免自己编写和维护代码所带来的痛苦。
使软件更易于维护的另一个策略是降低代码库的复杂性。或许,圈复杂度概念是衡量复杂性的最佳方式。想象一下,要为最终用户启动和运行一个特定的路由,需要涉及系统管理、数据库管理、网络管理和 IT 运营。减少所需的组件数量就可以提高可维护性。
Serverless
今天,在开发新软件时,通过更少的代码和更低的复杂性来提高长期软件可维护性的最佳方法是使用 Serverless 软件架构。
定义
关于 Serverless 的定义存在很多争议(比如“但仍然有服务器存在”、“它是个糟糕的名字”),但我认为,当你理解了它的含义,就会知道这个名字其实是很有意义的:
- 不涉及服务器操作
- 我们不对运行时间负责
Serverless 是我们过去 25 年来在 SaaS 中走的最后一步,因为我们已经渐渐将越来越多的职责交给了服务提供商。我们已经从自有数据中心转向了主机托管设施,从专用托管到基础设施即服务,从虚拟机到容器,但是它们都需要我们做一些个性化的系统工作。最初,我们遇到了很多问题,即使使用容了器,仍然需要打修补和处理第三方应用程序(例如 Web 服务器)故障。
Serverless 将个性化服务器操作的数量降低到零,因为一切都是多租户的,并由服务提供商负责提供(注意:根据我的定义,在自己的 Kubernetes 集群上运行功能并不是“Serverless”)。在 Serverless 架构中,你不需要处理硬件设置、成本支出、操作系统安装和修补、应用程序安装和修补,或第三方应用程序故障。
“不操作服务器”的必然结果是“我们不对运行时间负责”。运行时间——除非是你自己的代码出了问题——取决于服务提供商。对于很多人来说,这是很可怕的(也许是因为他们不相信服务提供商,也许是因为他们担心他们会因此丢掉饭碗),但如果你的目标是减少代码和复杂性,只要服务提供商是可靠的,那么这就是实现目标的最佳方式。
Serverless 的好处
并非所有 Serverless 架构都是一样的,有时候,Serverless 应用程序有可能比典型的三层式单体应用程序更难维护。但是,如果实施得当,Serverless 应用程序将提供比其他无法不使用 Serverless 的应用程序架构更大的优势:更少的相互依赖、更少的技术债务、一种有效的微服务架构,以及独立的生产等效环境。
恰当的 Serverless 架构存在较少的相互依赖性,因为整体应用程序的复杂性较低。Serverless 应用程序包括应用程序前端代码、后端功能、第三方服务(例如 Twilio、Algolia),以及这些功能和服务的配置。Serverless 应用程序不处理操作系统、第三方服务器应用程序(如 Web 服务器)或任何特定于单个虚拟机或容器的部署代码。因此,相对于基于虚拟机或容器应用程序的开发者,Serverless 应用程序开发者被迫等待另一个团队或其他团队成员的人数会更少。
在所有的应用程序架构中,Serverless 应用程序拥有的代码量最少。我们将在下一节中通过一个示例来演示如何通过利用最有效的服务来消除代码。
尽管 Martin Fowler 建议在尝试将应用程序分解为微服务时先从单体架构开始,但随着我们转向具有自动可扩展后端功能的胖客户端,同时也获得了可靠的微服务架构。如果我们的应用程序需要扩展给越来越多的用户使用,我们在扩展应用程序方面的麻烦就会少很多,因为我们已经将前端与功能分开,并且功能之间也是相互分离的。
最后,由于 Serverless 应用程序是按照使用计费的,免除了空闲成本,因此,相比在虚拟机或容器架构中为所有开发人员提供单个开发环境,为每个开发人员提供单独且与生产环境等效的开发环境将更加便宜。这有助于提升开发人员的开发速度(如果有人搞坏自己的环境并不会阻碍到其他人)、应用程序部署代码(如果可能,自动部署所有人的代码),以及减少因环境差异造成的 bug。
一个例子
让我们来看一个特定的应用程序示例,Hacker News 上的“keepingscore”认为这是一个不能也不应该通过 Serverless 方式构建的应用程序:
“文章的作者认为后端只是一个简单的带有身份验证的持久层。我是一家旅游公司的后端团队负责人。我们需要在 Android 应用程序上呈现航班和酒店列表,但不是通过查找数据库的方式来实现的,而是通过数百个 SOAP 和 REST 调用将来自多个平台的航空公司和酒店数据拼接在一起。这个是前端无法处理的。即使可以,我也不想在 iOS、Android、Web 和我们的内部支持门户之间复制这种逻辑。”
应用程序
基于上面的描述,我们假设有一个由一个网站和两个移动应用程序前端(Android 和 iOS)组成的应用程序,它们与提供用户管理的后端发生交互,用户能够搜索、购买和分享航班和酒店行程,还提供了一个内部会计所必需的报告层。
更具体地说,我们可能需要考虑以下一些高级功能:
- 用户管理:
- 注册、登入登出、修改密码;
- 保存用户首选项。
- 用户界面:搜索和购买:
- 自动填充搜索条件(例如机场代码);
- 提交搜索条件;
- 支付。
- 用户界面:分享和修改:
- 查看,与其他用户分享行程;
- 更改(包括取消)行程。
- 后端:搜索和购买:
- 并行调用多个 API;
- 合并响应,应用过滤器;
- 购买行程。
- 后端:报告:
- 运行有关购买、行程的分析报告。
现在,我们将介绍不同的架构选项,说明 Serverless 架构能够提供其他架构无法提供的优势。请注意,我们不需要太过关注客户端,因为我们可以假设所有客户端代码在这些不同的架构中都是相同的,唯一不同的是后端是如何实现的(另请注意,最终用户并不关心后端如何实现)。
架构 1:典型的三层架构
在典型的现代三层架构中,应用程序服务器需要使用一个带有缓存层的关系数据库。从服务器运维角度来看,即使使用了容器,仍然需要处理的一些问题:配置和修补 Web 服务器和数据库软件、加速镜像、预热镜像,以及容器或虚拟机故障转移。如果使用虚拟机或自己的机器,需要做的事情会更多。
此外,驱动前端所需的代码通常都是定制的。我通过查看各种示例应用程序来估计代码行数,并估计后端的代码行数如下:
架构 2:纯粹的 FaaS
亚马逊在 2014 年推出 Lambda 时,Serverless 应用程序架构看起来很像上面那样,基本上将我们架构 1 中的单体应用程序分解为一系列功能(微服务),它们相互调用并与持久层交互,以此来响应客户端的请求。但是,我不认为这种架构是对三层架构的重大改进,虽然它确实有一些好处,但它也存在潜在的复杂性。
从好的方面来看,这种架构消除了三层架构所需的服务器操作。它是一种真正的 Serverless 架构。
不过,部署所有这些微服务并让它们之间相互调用可能会出现大问题。功能即服务(FaaS)并不像应用程序的功能那样,它们具有更高的调用开销,并且如果出现无限循环将耗费大量成本(因为每次调用都是收费的)。调试 FaaS 嵌套也比在调试器中单步调试应用程序要困难得多。最后,在这个示例中,你并没有真正消除很多代码,因此驱动应用程序可维护性的核心要求并不在于此。
架构 3:Serviceful Serverless
这是一种更好的 Serverless 架构,我称之为 Serviceful Serverless 应用程序。在该架构中,不一定是唯一或不一定与标准功能(例如用户管理和认证)不同的应用程序的各个部分由托管服务(例如,AWS Cognito、Auth0、Google Firebase Auth)来处理。
等等,这怎么可能?
一些从业者对上表的第一反应是怀疑,他们不相信可以减少这么多代码,因为如果可以这样的话,那么这种架构的灵活和可扩展程度足以在新需求出现时持续开发新功能。在本文中,我只能尽我最大努力来说服你,这些架构确实提供了可扩展、极低代码量的应用程序,你最好通过尝试的方式来了解它的好处。
有一些东西可以帮助人们从高层次理解如何实现代码行数的减少和可扩展性的提升。首先,可以先看看示例应用程序是怎么样运行的。AWS 已经构建了一个组日历应用程序(截至 2018 年 11 月),Web 版本(React)包含了 702 行 JavaScript 代码,原生 Android 版本包含了 508 行 Java 代码。如果你刚好在想着如何入门,AWS Amplify 和 Google Firebase 提供的选项可让你在不到一个小时内启动并运行这种应用程序。
其次,Serviceful Serverless 应用程序的一个关键创新是,以前需要通过代码完成的很多事情现在可以在服务配置中完成——根据服务的不同,有时候使用了图灵不完备的语言。图灵不完备的语言非常适合用作配置,并且最终会减少很多代码。
最后,你始终可以选择按照以前的方式完成工作,在必要时只需要编写自己的代码,而不是利用现有的服务。换句话说,Serviceful Serverless 架构中最糟糕的情况是,你可以按照以前的方式来完成应用程序的某些部分。但对于应用程序的其他部分,你可以将绝大多数工作交给不需要你构建或维护的服务。
有关 Serviceful Serverless 的更多详细信息
Serviceful Serverless 的一个关键部分是有一个托管服务处理来自客户端的 API 调用,并根据需要将它们路由到各种服务和功能,而不是为后端功能提供一个非常轻量的“网关”。这类服务的最佳例子可能是 AWS AppSync,尽管 Google Firebase 和 Hasura 也提供了 API Hub 服务(请注意,这些服务不同于现在的 FaaS 服务,因此在选择它们之前请先进行了解和测试!)。
这些 API Hub 服务的一个巨大好处是你可以通过它们直接将大多数请求路由到持久层,也可以将请求从持久层路由出去,而无需定制代码。在上面以 FaaS 为中心的架构和三层应用程序中,每个数据库读取和写入都需要通过你开发的应用程序代码。在上述的 Serverless 架构中,API Hub 可以处理其中的很多请求——包括细粒度访问控制(请参阅 https://docs.aws.amazon.com/appsync/latest/devguide/security.html#amazon-cognito-user-pools-authorization 和 https://firebase.google.com/docs/database/security/user-security)。
通过为所有后端功能使用托管服务,以及利用 API Hub 处理数据存储的读写请求,定制代码的行数急剧下降,并实现了应用程序的可维护性,而这是其他架构无法实现的。
反驳论点
与使用其他替代方案一样,你总是无法避免风险。从典型的三层应用程序架构(或多个双层微服务)迁移到 Serviceful Serverless 架构有助于提升应用程序的可维护性,但存在一些新的风险。我相信,在绝大多数情况下,这些风险是一种超过可接受的权衡,但我不想忽视它们。
我们不对运行时间负责
在我对 Serverless 的定义中,我将正常运行时间称为不是我们能够控制的东西。数据显示,主要的 IaaS 提供商在保持运行时间方面远远优于一般(甚至高于平均水平)的 IT 运营团队,因此,为他们支付远低于内部运营团队的费用来保持 Serverless 应用程序的运行(包括处理自动故障转移)对我来说完全不是个事。
也就是说,如果你构建一个依赖于很多不同服务提供商的应用程序,并尽可能接近 100%的运行时间,随着服务提供商数量的增加,应用程序在未来某个时候发生故障的可能性也会增加。通常情况是提供商的数量越少越少。此外,明智的做法是提升应用程序处理非必要服务可用性的弹性。
供应商锁定
如果没有人喋喋不休地关注供应商锁定,服务的差异化就无从谈起。当然,有一些残酷的案例表明,IT 供应商利用企业对他们的依赖,将产品的价值榨取到无以复加的地步。但是,如果你担心供应商锁定,至少应该要说服自己,摆脱特定供应商锁定并没有那么困难。
对于上面的 Serviceful Serverless 应用程序架构,你可以通过加入之前被免掉的定制代码迁移出这种架构。也就是说,迁移成本从 11,000 行代码变成了 36,500 行代码。如果你认为编写 25,000 行代码代价非常高(确实是这样,因为你必须维护所有新代码!),那么你支付给供应商的费用或许是合理的。但如果你认为编写代码成本更低,那么你就可以编写代码。此外,编写代码并不困难,因为他们已经提供了很好的 API 文档,并且你已经拥有可以调用这些 API 的代码。
换句话说,通过使用 Serviceful Serverless 架构,你可以避免自己编写代码,因为有另一个团队在为你编写代码,你只需要付给他们费用。如果你认为费用太高,可以在内部使用它们,并自行开发代码。这与数据库或虚拟化层的锁定不同,无状态应用程序代码更容易迁移。
经验法则
采用新架构概念最困难的地方在于了解你的实现方式对不对。我有三个“经验法则”可供你参考,用来判断应用程序是否正确利用了 Serviceful Serverless 的优势。
厚客户端,不是厚中间层
你希望将通用逻辑(如上例中的酒店 / 航班搜索)放在后端,并打算将十年前开发的应用程序中间层的很多内容转到服务(例如用于图像处理的 Cloudinary 或用于搜索的 Algolia)或胖客户端(例如 JavaScript 框架)中。绝大多数定制代码应该放在客户端交互层中。这并不意味着你就不会有后端代码(例如上面示例中提到的 API 集成),能够控制所有最终用户交互的细节通常来说是有价值的,但自己开发可在其他地方购买的后端功能几乎是没有价值的。
功能是粘合剂,不要相互调用
如上所述,在 Serverless 架构内,功能之间的互相调用是一个错误的做法。从调试的难度到调用的开销,再到无限循环的成本,如果你能够消除造成圈复杂度的原因,那说明你很有钱。相反,你的定制代码功能应该是服务之间的粘合剂,例如接受客户端请求,从各种 API 中提取数据,将其合并为紧凑的数据结构,并将它们发送给数据存储和客户端。
自定义研究,而非自定义代码
如果你正在构建 Serviceful Serverless 应用程序,那么你需要花费更多的时间进行研究。这是因为你将基于服务实现更多的应用程序功能,并且需要验证选择了正确的服务。你还需要找出将服务与应用程序集成的正确方法。因此,你可以考虑花几天甚至几周时间编写概念验证代码,并测试不同的选项,而不是花一两个小时寻找你可能会用到的软件包。
换一种说法(两个方程式):
2 周研究 + 1 天开发 → N 行代码需要维护
1 天研究 + 2 周开发 → 10N 行代码需要维护
十倍代码行数就是十倍的技术债务,这意味着未来开发速度会越来越慢,越不可预测,普通开发人员也无法很好地维护系统。
关于作者
Joe Emison 是一位连续技术联合创始人,在 3 月份创办了他的第五家公司 Branch。他之前的项目包括 BuildFax(被 DMGT 收购)、Spaceful(被 Xceligent 收购)、BluePrince(被 Harris Computer 收购)和 EphPod(被 Wind Solutions 收购)。此外,他还为其他公司提供软件开发和云迁移方面的咨询,其中包括很多 DMGT 产品组合。Joe 毕业于威廉姆斯学院,获得英语和数学学位,并拥有耶鲁大学法学院的法律学位。
查看英文原文:
https://www.infoq.com/articles/serverless-sea-change