无服务器计算(Severless computing,简称 Serverless)现在是软件架构圈中的热门话题,国外三大云计算供应商(Amazon、Google 和 Microsoft)都在大力投入这个领域,涌现了不计其数的相关书籍、开源框架、商业产品、技术大会。到底什么是 Serverless?它有什么长处/短处?我希望通过本文对这些问题提供一些启发。
一些示例
界面驱动的应用(UI-driven applications)
我们来设想一个传统的三层 C/S 架构,例如一个常见的电子商务应用(比如在线宠物商店),假设它服务端用 Java,客户端用 HTML/JavaScript:
在这个架构下客户端通常没什么功能,系统中的大部分逻辑——身份验证、页面导航、搜索、交易——都在服务端实现。
把它改造成 Serverless 架构的话会是这样:
这是张大幅简化的架构图,但还是有相当多变化之处:
消息驱动的应用(Message-driven applications)
再举一个后端数据处理服务的例子。假设你在做一个需要快速响应 UI 的用户中心应用,同时你又想捕捉记录所有的用户行为。设想一个在线广告系统,当用户点击了广告你需要立刻跳转到广告目标,同时你还需要记录这次点击以便向广告客户收费(这个例子并非虚构,我的一位前同事最近就在做这项重构)。
传统的架构会是这样:“广告服务器”同步响应用户的点击,同时发送一条消息给“点击处理应用”,异步地更新数据库(例如从客户的账户里扣款)。
在 Serverless 架构下会是这样:
这里两个架构的差异比我们上一个例子要小很多。我们把一个长期保持在内存中待命的任务替换为托管在第三方平台上以事件驱动的 FaaS 函数。注意这个第三方平台提供了消息代理和 FaaS 执行环境,这两个紧密相关的系统。
解构 “Function as a Service”
我们已经提到多次 FaaS 的概念,现在来挖掘下它究竟是什么含义。先来看看 Amazon 的 Lambda 产品简介:
通过 AWS Lambda,无需配置或管理服务器(1)即可运行代码。您只需按消耗的计算时间付费 – 代码未运行时不产生费用。借助 Lambda,您几乎可以为任何类型的应用程序或后端服务(2)运行代码,而且全部无需管理。只需上传您的代码,Lambda 会处理运行(3)和扩展高可用性(4)代码所需的一切工作。您可以将您的代码设置为自动从其他 AWS 服务(5)触发,或者直接从任何 Web 或移动应用程序(6)调用。
状态
当牵涉到本地(机器或者运行实例)状态时 FaaS 有个不能忽视的限制。简单点说就是你需要接受这么一个预设:函数调用中创建的所有中间状态或环境状态都不会影响之后的任何一次调用。这里的状态包括了内存数据和本地磁盘存储数据。从部署的角度换句话说就是 FaaS 函数都是无状态的(Stateless)。
这对于应用架构有重大的影响,无独有偶,“Twelve-Factor App” 的概念也有一模一样的要求。
在此限制下的做法有多种,通常这个 FaaS 函数要么是天然无状态的——纯函数式地处理输入并且输出,要么使用数据库、跨应用缓存(如 Redis)或者网络文件系统(如 S3)来保存需要进一步处理的数据。
执行时长
FaaS 函数可以执行的时间通常都是受限的,目前 AWS Lambda 函数执行最长不能超过五分钟,否则会被强行终止。
这意味着某些需要长时间执行的任务需要调整实现方法才能用于 FaaS 平台,例如你可能需要把一个原先长时间执行的任务拆分成多个协作的 FaaS 函数来执行。
启动延迟
目前你的 FaaS 函数响应请求的时间会受到大量因素的影响,可能从 10 毫秒到 2 分钟不等。这听起来很糟糕,不过我们来看看具体的情况,以 AWS Lambda 为例。
如果你的函数是 JavaScript 或者 Python 的,并且代码量不是很大(千行以内),执行的消耗通常在 10 到 100 毫秒以内,大函数可能偶尔会稍高一些。
如果你的函数实现在 JVM 上,会偶尔碰到 10 秒以上的 JVM 启动时间,不过这只会在两种情况下发生:
你的函数调用触发比较稀少,两次调用间隔超过 10 分钟。
流量突发峰值,比如通常每秒处理 10 个请求的任务在 10 秒内飙升到每秒 100 个。
前一种情况可以用个 hack 来解决:每五分钟 ping 一次给函数保持热身。
这些问题严重么?这要看你的应用类型和流量特征。我先前的团队有一个 Java 的异步消息处理 Lambda 应用每天处理数亿条消息,他们就完全不担心启动延迟的问题。如果你要写的是一个低延时的交易程序,目前而言肯定不会考虑 FaaS 架构,无论你是用什么语言。
不论你是否认为你的应用会受此影响,都应该以生产环境级别的负载测试下实际性能情况。如果目前的情况还不能接受的话,可以几个月后再看看,因为这也是现在的 FaaS 平台供应商们主要集中精力在解决的问题。
API 网关
我们前面还碰到过一个 FaaS 的概念:“API 网关”。API 网关是一个配置了路由的 HTTP 服务器,每个路由对应一个 FaaS 函数,当 API 网关收到请求时它找到匹配请求的路由,调用相应的 FaaS 函数。通常 API 网关还会把请求参数转换成 FaaS 函数的调用参数。最后 API 网关把 FaaS 函数执行的结果返回给请求来源。
AWS 有自己的一套 API 网关,其他平台也大同小异。
除了纯粹的路由请求,API 网关还会负责身份认证、输入参数校验、响应代码映射等,你可能已经敏锐地意识到这是否合理,如果你有这个考虑的话,我们待会儿就谈。
另一个应用 API 网关加 FaaS 的场景是创建无服务器的 http 前端微服务,同时又具备了 FaaS 函数的伸缩性、管理便利等优势。
目前 API 网关的相关工具链还不成熟,尽管这是可行的但也要够大胆才能用。
工具链
前面关于工具链还不成熟的说法是指大体上 FaaS 无服务器架构平台的情况,也有例外,Auth0 Webtask 就很重视改善开发者体验,Tomasz Janczuk 在最近一届的 Serverless Conf 上做了精彩的展示。
无服务器应用的监控和调试还是有点棘手,我们会在本文未来的更新中进一步探讨这方面。
开源
无服务器 FaaS 的一个主要好处就是只需要近乎透明的运行时启动调度,所以这个领域不像 Docker 或者容器领域那么依赖开源实现。未来肯定会有一些流行的 FaaS / API 网关平台实现可以跑在私有服务器或者开发者工作站上,IBM 的 OpenWhisk 就是一个这样的实现,不知道它是否能成为流行选择,接下来的时间里肯定会有更多竞争者出现。
除了运行时的平台实现,还是有不少开源工具用以辅助开发和部署的,例如 Serverless Framework 在 API 网关 + Lambda 的易用性上就比它的原创者 AWS 要好很多,这是一个 JS 为主的项目,如果你在写一个 JS 网关应用一定要去了解下。
再如 Apex——“轻松创建、部署及管理 AWS Lambda 函数”。Apex 有意思的一点是它允许你用 AWS 平台并不直接支持的语言来实现 Lambda 函数,比如 Go。
什么不是 Serverless
在前文中我定义了 “Serverless” 是两个概念的组合:“Backend as a Service” 和 “Function as a Service”,并且对后者的特性做了详细解释。
在我们开始探讨它的好处和弊端之前,我想再花点儿时间在它的定义上,或者说:区分开那些容易和 Serverless 混淆的概念。我看到一些人(包括我自己最近)对此都有困惑,我想值得对此做个澄清。
对比 PaaS
既然 Serverless FaaS 这么像 12-Factor 应用,那不就是另一种形式的 Platform as a Service 么?就像 Heroku?对此借用 Adrian Cockcroft 一句非常简明的话:
如果你的 PaaS 能在 20ms 内启动一个只运行半秒钟的实例,它就叫 Serverless。— Adrian Cockcroft
换句话说,大部分 PaaS 应用不会为了每个请求都启动并结束整个应用,而 FaaS 就是这么做的。
好吧,然而假设我是个娴熟的 12-Factor 应用开发者,写代码的方式还是没有区别对么?没错,但是你如何运维是有很大不同的。鉴于我们都是 DevOps 工程师我们会在开发阶段就充分考虑运维,对吧?
FaaS 和 PaaS 在运维方面的关键区别是伸缩性(Scaling)。对于大多数 PaaS 平台而言你需要考虑如何伸缩,例如在 Heroku 上你要用到多少 Dyno 实例?对于 FaaS 应用这一步骤是完全透明的。即便你将 PaaS 配置为自动伸缩,也无法精细到单个请求级别,除非你有一个非常明确稳定的流量曲线可以针对性地配置。所以 FaaS 应用在成本方面要高效得多。
既然如此,何必还用 PaaS?有很多原因,最主要的因素应该是工具链成熟度。另外像Cloud Foundry 能够给混合云和私有云的开发提供一致体验,在写就本文的时候 FaaS 还没有这么成熟的平台。
对比 NoOps
Serverless 并非“零运维”——尽管它可能是“无系统管理员”,也要看你在这个 Serverless 的道路走多深。
“运维”的意义远不止系统管理,它还包括并不限于监控、部署、安全、网络、支持、生产环境调试以及系统伸缩。这些事务同样存在于 Serverless 应用中,你仍旧需要相应的方法处理它们。某些情况下 Serverless 的运维会更难一些,毕竟它还是个崭新的技术。
系统管理的工作仍然要做,你只是把它外包给了 Serverless 环境。这既不能说坏也不能说好——我们外包了大量的内容,是好是坏要看具体情况。不论怎样,某些时候这层抽象也会发生问题,就会需要一个来自某个地方的人类系统管理员来支持你的工作了。
对比存储过程即服务
还有一种说法把 Serverless FaaS 看做“存储过程即服务(Stored Procedures as a Service)”,我想原因是很多 FaaS 函数示范都用数据库访问作为例子。如果这就是它的主要用途,我想这个名字也不坏,但终究这只是 FaaS 的一种用例而已,这样去考虑 FaaS 局限了它的能力。
我好奇 Serverless 会不会最终变成类似存储过程那样的东西,开始是个好主意,然后迅速演变成大规模技术债务。— Camille Fournier
但我们仍然值得考虑 FaaS 是否会导致跟存储过程类似的问题,包括 Camille 提到的技术债。有很多存储过程给我们的教训可以放在 FaaS 场景下重新审视,存储过程的问题在于:
尽管不是所有存储过程的实现都有这些问题,但它们都是常会碰到的。我们看看是否适用于 FaaS:
第一条就目前看来显然不是 FaaS 的烦恼,直接排除。
第二条,因为 FaaS 函数都是纯粹的代码,所以应该和其他任何代码一样容易测试。整合测试是另一个问题,我们稍后展开细说。
第三条,既然 FaaS 函数都是纯粹的代码,版本控制自然不成问题;最近大家开始关心的应用打包,相关工具链也在日趋成熟,比如 Amazon 的 Serverless Application Model(SAM)和前面提到的其他 Serverless 框架都提供了类似的功能。2018 年初 Amazon 还开放了 Serverless Application Repository(SAR)服务,方便组织分发应用程序和组件,也是基于 AWS Serverless 服务构建的。