作者:王雷,授权CSDN发布。原文地址:点击这里
题记:从去年开始,无服务器(Serverless)的后端开发逐渐被越来越多公司所接受,硅谷的很多公司都把后端服务迁移到AWS的Lambda平台。阿里云推出FaaS(函数即服务)的产品,国内也有一些公司开始试水Serverless。Serverless和IaaS的模式相比,不但能完全免除Infrastructure的运维工作,而且由于其函数(任务)的弹性分配机制,能节省大量成本。对于后端架构和开发人员来说,了解Serverless的基本编程思想,为接下来的架构迁移做好准备,是非常必要的。
内容较多,分两篇文章讲解Serverless开发的各个方面,本文是第一篇。
首先,Serverless并不是不用服务器了。这个术语只是通俗地描述了用抽象的任务处理和调度技术来管理服务器的方法。在2012年的时候,“serverless”曾经定义如下:
“Serverless”不意味着不再涉及服务器。只是意味着开发人员不再需要考虑太多了。计算资源以服务的方式被使用,我们不必管理物理容量或限制。服务提供商承担了大部分管理服务器,数据存储和其他基础架构资源的责任。让开发人员将重点从服务器级别转移到任务级别。无服务器解决方案让开发人员专注于他们的应用程序或系统需要做什么,消除了后端基础架构的复杂性。
在当时,“Serverless”一词并不十分受欢迎, Hacker News上面的评论就充分证明了这一点 。随着许多Serverless平台的引入和微服务、事件驱动架构被人们逐渐接受,这种负面评价渐渐消退了。
示例
用一个例子可以方便我们讨论Serverless的概念。我们将使用Serverless Pipeline的来处理电子邮件和检测垃圾邮件,这是一个事件驱动的系统,因为当电子邮件进来时,它将触发一系列特定于该电子邮件的作业或函数。
在此管道中,你可能会定义任务来解析电子邮件中的文本,图像,链接,邮件属性和其他嵌入对象。每个项目或元素可能具有不同的处理要求,这又需要一个或多个单独的任务,甚至其自己的处理流程或序列。例如,拿图像处理来说,可能你要经过多个不同处理单元分析图像链接,以确定图像的内容和合法性。取决于消息评分结果(是否垃圾邮件),然后将采取各种措施,这又涉及其他serverless函数。
在任务层面思考
无服务器环境中的扩容单位是任务(task)或工作(job)。它是围绕特定工作负载进行有限处理的一个实例(instance)。任务处理自从有编程以来就存在,所以有可能看起来没什么新鲜。但是考虑到处理工作负载的高度分布的性质和抽象的方式,在多个层次上了解这个过程是有必要的。
同步与异步
虽然处理任务的性质(无论是同步还是异步)更多是一个服务平台的问题,但是也是在任务级别考虑的重要因素。传统的作业处理系统大部分是异步的,这意味着调用进程不会与执行任务处理的实例保持持久连接。工作将排队等候,因此,他们可能不会立即运行。调用函数和处理器之间唯一的连接是将任务排队,然后等待运行。(请注意,某些平台可能允许获取内部任务状态,但也是通过API调用而不是持续连接。)
许多新的无服务器平台允许进行同步处理,从而维持连接,并且客户端等待处理结束才继续执行。同步处理的优点是可以直接从处理平台获得结果,而在异步处理中,获得结果步骤必须独立完成。我将在平台部分中详细介绍。一般规则是同步处理适用于轻量级功能(类似于获取天气信息的API调用),而异步处理适用于更长时间,更复杂的处理作业(音频转录或批量处理一组事件等),以及启动任务的(应用程序/组件/函数)和处理结果的(应用程序/组件/函数)不同的情况。
无状态
无论处理方式如何,开发微服务和无服务器功能的核心原则之一是每个服务或功能都应被视为无状态的。 无状态指每个任务用于处理独立的、不同的请求,任务内部包含足够的信息来实现该请求。服务和函数不应在内部存储任何全局的软件配置或状态。任何配置数据都应该来自函数外部,通常作为函数负载(payload)的一部分,或通过平台内的配置功能来获取。该函数仅作为计算资源使用,仅为处理单个工作负载而存在。
另外,任务应该有一个明确的开始状态和结束状态,并且服务、函数应该以相同的方式处理每个有效负载。一个原则是,如果微服务或无服务器函数试图做太多事情,那就是糟糕的设计;而整洁的微服务和函数应该符合“单一责任原则”(Single Responsibility Principle)。思考无服务器函数的一个好方法是,每个函数应该有唯一的一个变化维度。换句话说,如果一个函数可以用多个方式扩展,那就应该把它分成多个函数。
在我们使用的例子中,每个电子邮件都是一个单独的事件,因此每个邮件都将有一个单独的任务序列。每个任务将接受一个有效负载,其中包含要处理的任务或函数数据。
短时性
无服务器功能也是短暂的。这意味着它们只在一段有限的时间内存在。无服务器应用程序的基础主要在于事件处理、为这些事件服务而进行的任务处理。强大的容器技术的出现使得它能够在分布式环境中处理任务,并在运行时决定运行的位置。
换句话说,任务处理基本上等价于容器处理,平台以任务为单位启动或者删除容器。例如,电子邮件处理中的每个任务只能对特定电子邮件执行特定操作。完成后,任务和容器应立即终止。
可能存在需要持久或长时间运行的进程的情况,如应用程序服务器或API服务器的情况,但这些不是Serverless的典型应用。在大多数情况下,你觉得可能需要长时间运行的任务,其实有可能避免这种开销。例如,无服务器平台、消息队列或其他组件可能组合起来能够满足任何路由需求。同样,计划任务也能够提供定期状态检查或周期处理。一个例子是整合和处理来自各种IoT设备的流数据:如果把数据保存在一个或多个队列或数据库中,则计划作业可以周期性运行,查看每一块数据并启动一个或者多个子任务处理、整合这些数据。
请注意,在同步和/或实时无服务器处理情况下,容器在每个任务之后可能不会终止,主要是出于能上的考虑。容器可以在任务切换时持续生存,但是它们的状态和存储将被擦除和重置,使得每个任务或事件处理循环被隔离,保证短时性。
幂等
幂等性是构建到微服务和无服务器功能的关键属性。幂等的概念,是能够每次运行相同的任务并获得相同的结果,它也能保证多个相同的请求具有与单个请求相同的效果。当任务运行高度并发和异步的方式时,这对设计至关重要。在任何作业处理环境中,由于任何原因,任务可能无法完成,原因很多:服务器崩溃,资源限制,第三方服务超时,任务超时等。
在其他情况下,任务可能会完成,但客户端可能已发起相同负载的重复请求。一个例子是注册消息超时的队列,因为任务可能仍然在处理请求(因此没有及时删除或取消保留消息)。因此,队列可能会触发该消息/负载的另一个处理请求。
如果在这种状态下,任务继续工作并处理有效负载,将其放在队列中或将其写入数据库,则可能会产生不良影响,特别是存在事务(transaction)的时候。例如,可能会出现两个重复订单。因此,对于相同的请求负载,确保只有一个请求被处理是至关重要的。正是由于这个原因,在无服务器平台中工作的开发人员在处理请求之前,或在写入或输出结果之前,都要小心检查避免重复。
以一位开发者朋友的话来说,另一种思考方式是“想像一下,如果服务器崩溃,而任务在中间处理过程中,任务被重试。或者如果它只是排队或安排了两次。需要做些什么来确保你不会覆盖数据,添加重复的事务,或者通常会因为重新运行而丢弃事务。“ 很简单,在处理循环中的相应点检查和验证,以确保工作尚未执行。
多语种开发
Polyglot编程是指以多种语言编程。在无服务器编程的情况下,它是指以多种语言编写和执行任务的能力。虽然每个函数只可能用一种语言,但一般来说无服务器平台应该能够处理不同的语言写的函数。这意味着它也应该提供很大程度的代码独立性,使得开发人员可以透明地工作,而不用担心操作系统和服务器级依赖性。
当然,这样做的优势是能够使用合适的工具做正确的工作。或者,使用正确的团队做正确的工作。在开发过程中经常出现一个现象:如果你有一个锤子,你会把一切都看成是钉子。使用单一语言,解决问题会受到很多限制。开发人员可能会努力使用该语言的代码包来适应他们的需求,实际上其他语言的其他库可以更好地服务于这个目的。
要计算贝叶斯统计信息或进行机器学习,您可能希望使用以C,C++,Python或Java编写的软件包。同样,您使用的开发团队可能会精通一种针对特定语言编写的特定Web爬行软件包。能够利用这些知识和经验,可以缩短发展周期,减少项目迟发或失败的风险。
请注意,一些较新的无服务器平台目前只支持少数几种语言,但毫无疑问这个情况在今年(2017)能够迅速改变。
兼容性
无服务器任务需要考虑到两个兼容性的设计。第一个是任务之间的兼容性,第二个是跨版本兼容。毫无疑问,把功能分成无服务函数的时候,您需要组件之间有健壮的合同规范。可以通过常见的身份验证和传输协议解决部分问题,但好的API需要开发人员负责定义有意义且易于理解的输入和输出数据格式。
除了干净的界面外,开发人员还需要解决版本控制问题。例如,假设函数X在平台内运行,并且调用函数Y。如果功能Y已经更新而函数X并不知道,并且函数Y的设计不够健壮,则在处理时可能会失败,或者可能产生不正确的结果(或者导致预期原始结果的任务中的下游故障)。
与传统打包发布代码一样,无服务器任务中的更改和更新可能会波及应用程序其他部分。在打包发布的情况下,这些冲突可能会通过打包和编译工具很早被捕获。然而,通过微服务器和无服务器编程,它可能只能通过运行服务(最好在测试或Staging阶段)来发现、解决问题。
这意味着,开发人员不但要保证任务无状态、任务的独立性,还需要注意的是设计良好的接口,而且还要解决向后兼容性,并以小心谨慎的方式发布更新。采用Semantic Versioning能解决一部分问题,它也不是灵丹妙药,但随着这个版本约定的传播,它确实为可能正在使用您创建的任务的其他开发人员提供了一些保证。
这种兼容性和一致性的需求也意味着对生产中运行的任务,测试应该持续进行。幸运的是,对于无服务平台,该功能在很大程度上已经是自带的。由于无服务器任务本质上是可执行的,因此该功能为持续测试提供了几乎独立的框架。然后,只要提供各种测试输入,设置常规测试就可以了。如果无服务器平台提供计划作业,那这个问题就解决了。
平台级别的问题
分布式基础设施的高并发作业处理是一件复杂的事情。其中包含许多组件:服务器、处理和监视作业的控制器、自动缩放和管理服务器的控制器,在整个服务器集群中分配作业的控制器、缓冲作业的队列、确保作业完成和/或重试的其他组件、有助于维持服务级别(service level)的关键任务。本节将讲解一下这些层次,以便了解无服务器平台运行中的重要方面。
吞吐量
吞吐量一直是计算机处理领域的重要指标,代表处理事件、请求和工作负载的速度。在无服务器架构的上下文中,要讨论吞吐量就不得不谈谈延迟和并发。基本上,无服务器架构确实比传统应用程序和大型Web应用程序更容易提高吞吐率,因为它提供了更好的资源利用率。
资源的成本和利用效率是做无服务器的重要原因。如果您是一个拥有大量应用程序/API/微服务的大公司,您目前正在全天候使用计算资源,而且100%的时间都在使用,无论它们程序是否在运行。使用FaaS基础架构,您可以24*7全天候运行应用程序,您可以根据需要执行任意数量的应用程序的函数,并共享所有相同的资源。理论上,您可以把浪费减少到几乎为零,同时仍然提供快速响应时间。对于FaaS提供商来说,这种成本节省将转移给最终用户开发商。对于企业来说,这可以极大减少资本支出和运营费用。
还可以这样看:离散的任务具有自包含的特性,可以在无服务架构通用平台中的任何地方、任何时间运行。这与独立的单一应用程序(monolith)形成对比:对于单一程序来说,运营团队必须花费大量的时间来考虑对哪些应用进行扩展,何时扩展以及如何扩展。
任务和项目图
下图显示了无服务器平台上单个帐户的一系列任务。黄线表示帐户的所有任务,其他曲线表示帐户中的各个项目。项目曲线代表一个微服务或特定的一组应用程序功能。几年前,这些项目会被构建为传统的Web应用程序,并作为一个长期运行的应用程序进行托管。但是,您可以看到,每个服务或功能集具有不同的工作负载特性。在应用程序级别管理这些服务比在无服务器平台中的任务级别管理要复杂得多,更不用说,扩展无服务任务,而不是更复杂的应用程序服务器能节省更多资源。
所有任务(应用程序视图)与特定任务(无服务器视图)
延迟
在无服务世界中决定吞吐量的主要因素就是延迟和并发。延迟指的是开始处理任务所需的时间;并发意味着可以随时运行的独立任务的数量。(还有其他因素,比如任务处理时间长度,但是开发人员的主要关注点往往在于,在任何给定的时间内,可以启动多少工作或可以处理多少事件。换句话说,单个任务的性能优化是另外一个话题。)
对于作业处理延迟的要求有很大不确定性。事务(transaction)相关事件可能需要立即处理,而其他事件可能放松延迟要求,一般来说大约数秒钟或几分钟。更有甚者,有些任务延迟在数分钟或数小时内都可以接受。就像汽车世界的60迈加速时间和马力等指标,通常情况下,最高的性能不仅不必要,而且优化它会浪费资源并适得其反。在考虑任务和服务时,重要的是尽早定义延迟的需求,因为这将会影响您如何构建任务和管道,以及确定对于无服务器平台的期望。
考虑延迟的一种方式是在对任务分类。粗略将处理任务分为三类:实时处理,后台处理和批处理。
实时处理(<1秒)
实时处理的概念因人而异,可以是瞬间完成,也可以是人类能感知的实时完成。它包含启动任务的延迟和任务持续时间。任务启动延迟一般标准通常在20ms的范围内。然而,任务处理时间是一个难以确定的度量,一般的指导原则应该是人类反应的预期时间,通常从开始到结束(包括延迟)不到1秒。
对于分布式云处理来说,这速度要求的确高,特别是当你考虑网络延迟,容器开销和任务工作负载波动(即队列中可能有多少任务)时。这种类型的处理需要在无服务器平台和无服务器应用程序中选择正确的体系结构。服务器需要针对特定的一组任务进行优化,预先装载容器,严格定义任务内存和处理器资源限制。需要使用这些约束来构建应用程序,并监控负载,以维持SLA在可接受范围。
实时处理 - 处理具有低延迟和快速响应速率的作业
后台处理(秒/分钟级别)
后台处理一般用于描述在主事件或响应循环之外处理的事件驱动工作负载,它们对时间较为敏感,需要随时扩展。虽然处理要求可能不需要在毫秒级的延迟,但是这种任务通常需要在几秒钟内完成。
对社交网络的更新,pdf的生成,ETL处理,流数据输入的处理,图像的处理,音频或视频的转录以及其他媒体处理需求,所有这些例子都是后台处理的应用场景。在开始使用无服务器计算的时候,这种方式更为常见,因为它是基于传统的任务和工作队列的方式。
后台处理 - 在请求/响应循环之外处理的作业
批处理(分钟和小时级别)
批处理与以前的定义没有区别:处理大量重要的、相对独立、时间要求不高的关键任务。现在的区别是,批处理不需要等待白天(或晚上)特定的时间开始运行,或者依赖于一组专门针对工作负载条件的强大服务器。批处理可以在多个地区的数千个核心的机器上、在任何时间进行。云中批量处理与传统系统的运行模式可能相同,但任务的形式以及和自包含的特性与以前相比有显著不同。
批量处理 任务延迟分布
下图显示了特定时间点无服务器平台上的任务延迟或等待时间。请注意,y轴使用对数刻度。数字表明,绝大多数任务的等待时间都很少。
并发
并发是指可以在任何一个时间执行的类似任务的数量。阈值可以由无服务器平台来规定,或者,它可以是基于应用程序自身的限制,比如延迟时间超过了限定的阈值,或者资源有限,或者过高的并发可以导致下游服务的失败等情况。尤其对于数据库来说,在某个时间点运行的任务太多可能会超过连接限制。
容器技术允许在单个服务器中处理多个任务。五个,十个,二十个,五十个或更多个任务可以在单个服务器上运行,因此并发级别或阈值可能非常大,因为底层基础架构可支持几乎无限扩展的容量。许多无服务器平台能够自调整以满足不同的并发需求(白天大,夜间小),或应对在特定时间段内访问的爆发增长。这避免了大量的配置工作,同时仍然满足并发SLA。
请注意,我们使用术语并发性(concurrency),这和并行性(parallel)不同。它们是相关但截然不同的概念。在编程中,并发是独立执行过程的组合,而并行性是同时执行计算。并发是一次性处理很多事情,而并行是同时处理很多事情。
任务流图
下图显示了某个无服务器平台的特定时间和区域的每分钟任务流量。黄线表示任务开始,绿线表示任务完成,红线表示错误和超时。
内存限制
在任务级别考虑开发工作可以让您从许多基础设施的顾虑中解脱出来,但是您也不会摆脱资源限制的制约。内存和处理时间限制在无服务器平台中仍然扮演重要的角色。因为任务可能在容器中运行,所以它们受到内存(以及可能在容器或平台级别执行的IO,端口和其他限制)的限制。
目前,大多数平台的限制比较严格,所以开发人员需要意识到他们任务的内存要求,并保持在平台的限度内。您可能必须通过减少分配给任务的数据分片的大小来限制任务可以处理的数据量(即通过更多任务/更大的并发来解决问题)。这也意味着开发时需要意识到任务如何使用内存,确保使用正确的数据结构来避免不必要的内存分配。正如您将要分析(profile)应用程序的一部分以确保最佳性能和内存使用一样,您将要在无服务器环境中执行相同操作,尽管在这种情况下,它是以逐个任务和逐个服务的方式进行的。
请注意,大多数无服务器平台将提供本地临时读/写数据存储供任务使用(在任务结束时被擦除)。有效使用这种类型的存储可以减少分配大块内存的需要。键值数据存储的有效使用也是无服务器编程的重要组成部分。
在未来,无服务器系统可能能够对任务进行分析,并进行适当的路由来实现可变内存需求,现在还没有这样的平台。然而,一些无服务器平台确实提供不同的内存配置,以适应内存密集型任务。这种任务的类型可以包括图像,音频和视频处理,大文件处理和科学数据分析。在这种情况下,一旦通过配置或请求头部数据或通过分析来识别工作负载,平台就可以将任务路由到高级存储器处理集群,或者即时调整容器/函数的资源限制。
处理时间限制
任务需要多长时间处理会对系统的吞吐量产生重大影响。即使使用自动缩放功能,持续高数量的长时间运行的任务最终将消耗并阻止其他任务在其所需的时间范围内运行。正是由于这个原因,无服务器平台通常对任务可以处理多长时间有严格的限制。AWS Lambda允许最大处理时间为300秒,IronWorker最大可能是3600秒。
一些无服务系统可能能够提供较宽松的处理时间限制。一种方法是隔离长时间运行的任务,将其路由到专用群集,并通过自动缩放或调高最大阈值来提供足够的资源。
处理时间限制可能会对如何构建无服务器工作流程产生重大影响。很像内存限制,处理时间限制可以影响任务可能处理的数据量。开发人员可能需要减少输入数据的大小,并增加任务并发性。任务可能需要被分解成更多离散的功能。它也可能导致开发人员必须考虑可能阻塞(block)的外部资源请求或操作,这些操作有可能会导致任务超时。
如果服务和功能的架构正确,内存和处理限制不是什么大问题。大多数无服务器平台的资源限制通常足以满足大多数处理需求(某些平台可用的限制有所增加)。考虑本文提到的注意事项将有所帮助。一旦无服务器处理的使用达到“AHA”的时刻:上传代码并观察它们以高并发性运行的时候,你的思维很可能已经适应无服务平台,你已经掌握如何构建您的功能和工作流程。
下图显示了http://Iron.io平台在特定时间段内的任务持续时间的分布情况。如上图所示,垂直计数轴使用对数刻度,这意味着短时任务代表了任务总量的绝大部分。每个任务是独立于其他任务运行的无状态和短暂的工作单元。
同步与异步
前面介绍了同步处理和异步处理 。同步处理是在无服务器任务执行时与调用进程维护的连接。处理完成后,响应将发送回调用进程。异步处理是调用函数发送处理请求的地方,但在处理运行时不会阻塞。
许多新的无服务器平台允许进行同步处理。同步处理的优点是可以直接从处理平台获得结果,而在异步处理中,获得结果必须作为独立的任务来完成。
请注意,以同步处理模式运行可能会引入额外的异常或失败问题。由于调用进程可能阻塞,因此无服务器平台能够处理发送给它的任务的规模至关重要。如果不能,则调用应用可能会阻塞,远远超出预期的等待时间。另外,错误恢复机制(即任务重试)和度量收集可能不像异步模式那样有效,原因在于异步任务往往可以做出妥协。
因此,开发人员可能需要构建异常处理来处理阻塞,以及记录日志和作业处理分析,以确保任务按照要求执行。
全天候聚焦IaaS/PaaS/SaaS最新技术动态,深度挖掘技术大咖第一手实践,及时推送云行业重大新闻,一键关注,总览国内外云计算大势!