导语:从云计算到云原生,再到Serverless架构,从IaaS到PaaS,再到FaaS+BaaS,技术的升级与迭代从未停止过前进的脚步。随着时间的发展,Serverless架构凭借着弹性伸缩和按量付费的特点,逐渐的被更多人所关注,并成为更多项目技术选型的首选架构,甚至被UC Berkeley认为是云时代的默认编程范式。本文将会从Serverless架构的发展历程作为引入,对Serverless架构的定义、特性进行分析,并从开发者的角度,对Serverless架构目前所面临的挑战进行更为深入的探索,对如何开发Serverless应用进行经验分享。通过本文,可以进一步的了解什么是Serverless架构,以及Serverless架构下如何进行应用开发等。
自世界第一台通用计算ENIAC诞生以来,计算机科学与技术的发展就从未停止前进的脚步,尤其是近些年计算机的发展更是日新月异,有不断突破和进化的人工智能领域,有5G带来更多机会的物联网领域,还有“可信”的区块链技术,当然也有不断更新、不断迭代,不断走进“寻常百姓家”的云计算。
说起云计算,就不得不说被认为是“真正意义上的云计算”:Serverless架构。自2012年,Iron.io的副总裁Ken Form在文章Why The Future of Software and Apps is Serverless中首次提出了无服务器的概念,并指出:即使云计算已经逐渐兴起,但是大家仍然在围绕着服务器转。不过,这不会持续太久,云应用正在朝着无服务器方向发展,这将对应用程序的创建和分发产生重大影响。到2019年,UC Berkeley发表论文Cloud Programming Simplified: A Berkeley View on Serverless Computing,并在文章中犀利断言:Serverless 将会成为云时代默认的计算范式,将会取代 Serverful 计算,因此也意味着服务器 - 客户端模式的终结。Serverless架构用7年时间,完成了从“新的观点”向“万众瞩目的架构”的转身。
图1 Serverless架构发展简图
见图1,从2012年,Serverless概念被正式提出,到2014年AWS带领Lambda开启了Serverless的商业化,再到2017年各大厂商纷纷布局Serverless领域,再到2019年,UC Berkeley发文断言“Serverless将引领云计算的下一个十年”,Serverless架构正朝着更完整,更清晰的方向发展。随着5G时代的到来,Serverless将会在更多领域发挥至关重要的作用。时至今日,Serverless架构已经凭借其交付的降本提效理念,及其带给业务的技术红利,被更为广泛地采纳,逐渐在越来越多的业务中发挥着重要价值。
默认编程范式的技术革命
作为一种新的技术架构、新的编程范式,Serverless无论是作为技术选型被选择,还是作为架构基础被应用,都值得开发者用全新视角去看待。与传统架构不同的是,Serverless在应用全生命周期强调的是一种NoServer的理念,主张“将更专业的事情交给更专业的人来做,开发者可以付出更多精力在业务逻辑之上,付出更少的代价在服务器等底层资源上”,因此,Serverless架构具备诸多特性。例如在一个Serverless应用中,开发者可以付诸更多精力在业务代码部分,此时FaaS平台中的Function(函数)是什么意思?是编程中提到的函数还是一种全新的抽象概念?再例如,因为业务方无需关注服务器等底层运维工作,所以在不同的业务流量下,服务的弹性能力将交给云厂商实现,此时弹性伸缩带来的实例数量增减和状态管理,将会发生怎样的变化?大家所说的Serverles架构是无状态的,指的是无法在实例中存储状态,还是前后两次调用没有关系,不会受到状态的影响?
以Serverless架构中常见的名词“函数”为例。众所周知,Serverless架构被认为是FaaS与BaaS的结合,所谓的FaaS就是Functions as a Service,此处的“Function(函数)”经常会成为业务迁移上Serverless架构的重要阻碍:传统业务迁移到Serverless架构,是要将业务拆成函数的粒度吗?这里的Function(函数)指的是什么?
针对这个问题,需要将Serverless架构当作一种新的编程范式来看待,其中“函数”也将是一种更加抽象的概念,在Serverless架构下,函数即服务中的函数,更多代替的是一种资源粒度,这种粒度在实际项目中的表现也是复杂多变的,更为抽象的:
- 它表示一个单纯的函数,或非常简单的一个方法。
- 它是一个相对完整的功能或几个方法的结合,例如登录功能。
- 它是几个功能的结合,形成一个简单的模块,例如登陆/注册模块。
- 它是一个框架,例如在一个函数中,放下整个框架,如Express、Django等。
- 它是一个完整的服务,例如某个blog系统部署到一个函数中,对外提供服务。
因此,无论是传统业务迁移到Serverless架构,还是新的业务要基于Serverless架构开发,都无需过于纠结函数的概念,一般情况下,可以根据业务的实际情况,对函数具体的指带进行含义赋予,可以参考以下两个原则。
- 资源相似原则:判定某个业务中,对外暴露的接口是否对资源消耗类似。例如某个后端服务,对外暴露10个接口,其中9个接口的内存只需要128M,超时只需要3秒,而另一个接口需要2048M的内存与60秒的超时。此时可以认为前9个接口是资源相似的,可以放在“一个函数中实现”,最后一个单独来放到一个函数中实现。
- 功能相似原则:当业务中功能概念定义相差非常大的时候,不太建议将这些功能放在一起。例如某个聊天系统,有一个聊天功能(Websocket),还有一套注册/登录功能,如果此时将两者融合,在一定程度上会增加项目的复杂度,也不宜后期管理。此时可以考虑拆分成聊天函数和注册/登陆函数。
其实在Serverless架构下,是否要将一个应用拆分,或者拆分成何等程度,是一种哲学问题,需要根据自身业务情况具体分析。如果业务拆的太细,将会面临函数太多,不易于管理的问题。业务出现问题,不便于排查具体情况,会导致很多模块,配置重复使用。在一定情况下会让冷启动变得比较频繁。反之,如果业务耦合严重,将容易产生比较大的资费,在高并发的情况下,也容易出现流量限制问题,而且更新业务代码可能有较大风险,还不便于调试。
再以Serverless架构中的“无状态性”为例,继续探索编程范式的技术革命带给业务开发的新“习惯”。在UC Berkeley发表的论文中,针对Serverful和Serverless进行了比较详细的总结,“Serverless架构弱化了存储和计算之间的联系。服务的储存和计算被分开部署和收费,存储不再是服务本身的一部分,而是演变成了独立的云服务,这使得计算变得无状态化,更容易调度和扩缩容,同时也降低了数据丢失的风险”。在CNCF的Serverless Whitepaper v1.0,总结Serverless架构的适用场景时说:“短暂、无状态的应用,对冷启动时间不敏感的场景”。由此可见,与Serverful不同的是,Serverless架构下更多强调的是“无状态性”,这种无状态性是由其架构特性所带来的,并对其应用场景有一定的影响。那么,Serverless架构下的无状态性到底指的是什么?
其实,所谓的无状态就是没有状态的意思,也就是说Serverless架构由于其天然分布式的结构,并没有办法在单实例中保存某些永久状态,因为所有的实例都会面临被销毁的结局;一旦实例被销毁,那么之前存储在实例状态中的数据将会消失。但这并不意味函数的前后两次触发互无影响。就目前来看,各个云厂商的FaaS平台均存在着实例复用的情况,也就是说即使长久来看实例会被释放,但并不能确保每次都会被释放。因为在某些情况下,实例是可以被复用进而降低冷启动等带来的负面影响,此时函数的无状态性就不存粹了:
• 函数计算的实例都不适合长期保存某个状态,因为该状态可能因为并发导致不一致,也可能因为释放导致丢失。
• 由于实例存在复用的情况,即使我们确信函数计算实例不能长久存储状态,也不能忽略实例复用时,前一请求残留状态对本次的影响。
综上所述,Serverless架构的无状态性,在一定程度上指的是Serverless架构的函数实例不适合持久化文件、数据等内容,要以无状态的态度对待,但是开发者也不能忽略实例复用时“有状态”对业务的影响,合理利用实例复用有助于提升业务的性能。
Serverless架构作为一种“技术革命”,在成为“云计算时代默认的编程范式”的道路上还有一定的距离,虽然上面举例说明了一些“技术革命”所带来的思路升级,开发习惯转换,但实际上在Serverless架构普及过程中,仍然有着其他的注意事项,需要开发者关注。
Serverless架构在不断成长
时至今日,云计算的发展已经取得了巨大的进步,尤其是Serverless架构的不断发展,更是在降低成本的同时,大大提升了应用的开发效能,正如《云原生发展白皮书(2020年)》中所认为的:Serverless架构使得用户能够专注在价值密度更高的业务逻辑的开发上。也正如UC Berkeley论文中所表述的:Serverless架构按需提供无限计算资源;消除云用户的前期承诺;根据需要在短期内支付使用计算资源的能力;通过资源虚拟化简化操作并提高利用率;通过复用来自不同组织的工作负载来提高硬件利用率。
随着时间的发展,Serverless架构的优势越发明显,其带给业务的技术红利也越发诱人,甚至在2020年的云栖大会上,Serverless被再次断言“将会引领云计算的下一个十年”。即便如此,Serverless仍然面临诸多挑战。在UC Berkeley的文章中,就针对Serverless架构总结出了包括Abstraction challenges、System challenges、Networking challenges、Security challenges等在内的若干挑战。从开发、应用角度来看,在接触Serverless架构之后,开发者们仍然会面临一定的问题,其中最为直接的就包括冷启动、厂商锁定严重以及安全性等多方面挑战。
以冷启动为例(见图2)。在Serverless架构具备弹性伸缩特性时,也引入了新的问题:弹性伸缩性能问题,即冷启动问题严重。所谓的冷启动问题,是Serverless架构在弹性伸缩时可能会触发环境准备(初始化工作空间)、下载文件、配置环境、加载代码和配置、函数实例启动等完整的实例启动流程,导致原本数毫秒/数十毫秒即可得到响应的函数需要在数百毫秒/数秒才能得到响应。
图2 Serverless架构请求与冷启动简图
通常情况下,解决冷启动的问题或者降低冷启动带来的影响分为两个途径,云厂商途径和开发者侧的途径。云厂商侧的解决方案通常包括包括实例的复用,实例的预热以及资源池化等几个部分。而在开发者侧,则可以根据Serverless架构的原理,以及厂商提供的额外能力,进行项目优化,降低冷启动危害,例如在上文中,提及的函数冷启动的流程(见图3)。
图3 冷启动流程简图
在函数启动的过程中,其中一个阶段是加载代码,当所传的代码包过大,或者文件过多,导致解压速度过慢,就会将代码加载时间拉长,进而导致冷启动时间变久。设想一下,当有两个压缩包,一个是只有100KB的代码压缩包,另一个是200MB的代码压缩包。两者同时在千兆的内网带宽下理想化(即不考虑磁盘的存储速度等)下载,即使最大速度可以达到125MB/S,前者的下载速度不到0.01秒,后者则需要1.6秒。除了下载时间之外,还有文件的解压时间,那么两者的冷启动时间可能就相差2秒。一般情况下,一个传统的Web接口,如果要2秒以上的响应时间,是很多业务不能接受的。
因此,在打包代码时要尽可能地降低压缩包大小。以Node.js项目为例,打包代码包时,可以采用Webpack等方法,压缩依赖包大小,进一步降低整体代码包的规格,提升函数的冷启动效率。
除此之外,各个云厂商为了降低冷启动的频繁出现,还提供了实例复用的策略,所谓的实例复用,就是当一个实例完成一个请求,它并不会释放,而是进入“静默”状态。在一定时间范围内,如果有新的请求被分配过来,则会直接调用对应的方法,而不需要再初始化各类资源。这个过程极大降低了函数冷启动的情况出现。所以在实际的项目中,有一些初始化操作,可以考虑实例复用,例如:
- 机器学习场景下,在初始化的时候加载模型,避免每次函数被触发都加载模型带来的效率问题,提高实例复用场景下的响应效率。
- 数据库等链接操作,可以在初始化时进行链接对象的建立,避免每次请求都创建链接对象。
- 其他一些需要首次加载时下载文件,加载文件的场景,在初始化时进行这部分需求的实现,提高实例复用的效率。
各个云厂商提供的预留实例等功能也可以作为降低冷启动危害的一个重要途径,即在一些情况下,FaaS平台没办法根据业务的复杂需求,自动进行高性能的弹性伸缩,但业务方可以对其进行一定的预测。例如,某团队要在凌晨举办秒杀活动,那么该业务对应的函数可能在秒杀活动之前都是沉默状态,在秒杀活动时突然出现极高的并发请求,此时即使是天然分布式架构,本身自带弹性能力的Serverless架构,也很难迎接该挑战。因此在活动之前,可以由业务方手动进行实例的预留,比如在次日零时前,预留若干实例以等待流量峰值到来,次日23时活动结束,释放所有预留实例。
虽然预留模式在一定程度上违背了Serverless架构的精神,但在目前的业务高速发展过程与冷启动带来的严重挑战下,预留模式依然逐渐被更多厂商所采用,也被更多开发者、业务团队所接纳。虽然预留模式在一定程度上会降低冷启动的发生次数,但也并非可以完全杜绝冷启动。同时,在使用预留模式时,配置的固定预留值会导致预留函数实例利用不充分,因此,云厂商们通常会提供定时弹性伸缩和指标追踪弹性伸缩等多种模式进一步解决预留所带来的额外问题。
Serverless架构除了冷启动问题严峻之外,还面临着其他诸多挑战,例如厂商锁定严重(Serverless架构在不同厂商的表现形式不同,包括产品的形态、功能的维度、事件的数据结构等),所以一旦使用了某个厂商的Serverless架构,通常意味着也需要使用该云厂商的FaaS部分和相对应的配套后端基础设施。后续想进行多云部署、跨云厂商迁移等将会困难重重,成本极高。
众所周知,函数是由事件触发的,因此FaaS平台与配套的基础设施服务所约定的数据结构往往会决定函数的处理逻辑。如果每个厂商相同类型的触发器所规约的事件结构不同,那么在进行多云部署、项目跨云厂商迁移时就会面临巨大的成本。以AWS Lambda、阿里云函数计算以及腾讯云云函数为例,可以通过对对象存储事件的数据结构进行对比(见图4)。
图4 不同云厂商事件结构差别对比图
通过对比,不难发现三个云厂商关于同样的对象存储触发器的数据结构是完全不同的,这就导致我们在进行对象存储事件关键信息时获取方法不同,例如同样是获取触发对象存储事件的原始IP:
- 按照AWS的Lambda与S3之间规约的数据结构,获取路径为 sourceIPAddress = event["Records"][0]["requestParameters"]["sourceIPAddress"]。
- 按照腾讯云的SCF与COS之间规约的数据结构,获取路径为 sourceIPAddress = event["Records"][0]["event"]["requestParameters"]["requestSourceIP"]。
- 按照阿里云的FC与OSS之间规约的数据结构,获取路径为 sourceIPAddress = event["events"][0]["requestParameters"]["sourceIPAddress"]。
由此可引申出,当开发者开发一个功能,在不同云厂商所提供的Serverless架构中实现,涉及到的代码逻辑、产品能力均是不同的,甚至包括业务逻辑的开发、运维工具等也完全不同。所以想要跨厂商进行业务迁移、业务的多云部署,将会面临极高的兼容性成本、业务逻辑的改造成本、数据迁移的风险、多产品的学习成本等。
综上所述,由于目前没有完整的、统一的且被各个云厂商所遵循的规范,导致不同厂商的Serverless架构与自身产品、业务逻辑绑定严重,非常不利于开发者跨云容灾和跨云迁移。目前来看,Serverless架构厂商锁定严重的问题,也是如今开发者抱怨最多,担忧最多的问题之一,当然就该问题而言,无论是CNCF或者是其他组织、都努力在上层通过更规范、更科学的手法进行完善和处理。
当然,除了上述冷启动问题、厂商锁定严重问题,Serverless架构还存在学习资料不完备、最佳实践不全面、受到攻击自我保护策略不完善等诸多问题,但好在全球的云厂商和Serverless的工程师们,都在努力通过各种手段解决。就像针对冷启动问题提出了实例复用、预留模式;针对厂商锁定严重,上层提出相对应的兼容规范,通过Serverless Devs等开发者工具屏蔽部署差异等;针对被攻击的风险提出实例限制的策略等。可以这样认为,随着时间的发展,Serverless架构也在不断完善、不断成长。
用Serverless思想开发Serverless应用
作为一种新的编程范式、天然分布式架构,Serverless带来的还有开发思路的转换。换句话说,如果想要更好地感受到技术红利赋能生产力的提升,让应用更好地适配Serverless架构,那么就要用Serverless思想开发Serverless应用。
以上传文件为例,在传统Web框架中,上传文件是非常简单和便捷的,例如Python的Flask框架:
f = request.files['file']
f.save('my_file_path')
- 但是在Serverless架构下,却不能直接上传文件,因为一些云平台的API网关触发器会将二进制文件转换成字符串,不便直接获取和存储;API网关与FaaS平台之间传递的数据包有大小限制,很多平台被限制在6M。FaaS平台大都是无状态的,即使存储到当前实例中,也会随着实例释放而导致文件丢失。
因此,传统框架中常用的上传方案不太适合在Serverless架构中直接使用,在Serverless架构上传文件的方法通常有两种。
- 第一种是BASE64后上传,持久化到对象存储或者是NAS中,这种做法可能会触及API网关与FaaS平台之间传递的数据包有大小限制,所以一般使用这种方法上传头像等小文件。
- 第二种上传方法是通过对象存储等平台来上传,因为客户端通过密钥等信息将文件直传到对象存储存在一定风险,所以可以考虑图5所示的方案。通常情况是客户端发起上传请求,函数计算根据请求内容进行预签名操作,并将预签名地址返回客户端,客户端再使用指定的方法进行上传,上传完成后,可以通过对象存储触发器等对上传结果进行更新等。
图5 Serverless架构下图片上传方案
除了上传文件之外,在Serverless架构下,进行数据和状态持久化,也需要用新思路对待。由于FaaS平台是无状态的,并且用过之后会被销毁,因此文件如果需要持久化,并不能直接持久化在实例中,可以选择持久化到其他服务中,例如对象存储、NAS等。同时,在不配置NAS的情况下,FaaS平台通常情况下只有/tmp目录具有可写权限,所以部分临时文件可以缓存在/tmp文件夹下。
另外,传统框架迁移到Serverless架构也要进行诸多升级,例如很多框架具有异步、定时任务等能力,在Serverless架构下,就需要根据FaaS平台的规范,对部这一部分功能进行额外处理。
以Python Web框架Tornado为例,在Serverless架构下,函数计算是请求级别的隔离,所以可以认为这个请求结束了,实例就有可能进入到“静默”状态。而在函数计算中,API网关触发器通常是同步调用,这就意味着当API网关将结果返回客户端时,整个函数就会进入“静默”状态,或者被销毁,而不是会继续执行完异步方法。当然,如果使用者需要异步能力,可以参考云厂商所提供异步方法,以阿里云函数计算为例(见图6),阿里云函数计算为用户提供了一种异步调用能力,当函数的异步调用被触发后,函数计算会将触发事件放入内部队列中,并返回请求ID,而具体的调用情况及函数执行状态将不会返回。如果用户希望获得异步调用的结果,则可以通过配置异步调用目标来实现。
图6异步调用原理简图
Serverless架构下的应用开发和传统Serverful架构下的应用开发,区别还不止上述内容,还有动静分离、业务科学拆分等诸多差异。这些差异往往是由Serverless架构和Serverful架构的特性区别直接带来或间接带来的,掌握好这些Serverless应用的开发思想,进行Serverless应用开发,在一定程度上可以提升业务的安全性、稳定性、健壮性。
总结
从IaaS到PaaS再到SaaS,再到如今的Serverless,云计算的发展在近十余年中发生了翻天覆地的变化,从虚拟空间到云主机,从自建数据库等业务到云数据库等服务,云计算的发展非常迅速。同样Serverless架构的发展也是迅速的。
这些年,Serverless不仅是技术架构逐渐升级和完善,其概念也愈发明确,其目标和方向也逐渐清晰、明朗起来,包括Serverless架构不断迭代、不断完善的过程;也包括学术界、工业界对Serverless架构热忱的期望过程,更包括Serverless架构交付诱人的技术红利,为业务团队带来降本提效的优质体验,
诚然,当今的Serverless架构还不完善,但不可否定的是,Serverless架构正迅速强大,正在让应用开发更简单、让业务运维更便捷、让生产力提升、让成本下降。Serverless架构作为技术革命的先驱,从默认编程范式到生产力的过程中,不仅需要我们适应它,也需要它来适应开发者。