系统架构-Serverless(Baas & Faas)无服务器计算
Serverless并不神秘,用一个简单的例子就可讲明。我们设计了一个AI应用,可以识别出图片中人物的人种,我们把它作为一种SaaS服务架设在公共云上提供给客户使用,其典型的后端架构设计如下:
在该架构中,我们购买的云主机上运行了Tomcat Web Server,用于承载Java编写的AI应用。用户通过API上传图片。受限于云主机的本地存储空间,为了满足大量客户同时上传图片,AI应用实现了一个存储网关将图片导入公共云的对象存储。图片导入完成后,AI应用从对象存储读入图片进行识别,并将结果存入公共云的数据库中(例如RDS),用户使用API查询结果。 AI应用上线一段时间后受到了用户的欢迎,越来越多的公司开始使用该服务。根据统计数据,大多数公司在上午9点~11点、下午2点~5点集中上传图片,为了满足该时间段的突发访问量,我们设置了公共云的 Auto-Scaling策略,在访问增加时动态创建更多的云主机来响应客户。AI应用的架构演化成:
- 在这个架构中,我们需要做如下事情:
- 管理云主机。我们要关心CPU数量、内存大小、IP地址等等系统级的配置。同时还要关心云主机的操作系统,为部署AI应用拟定策略。操作系统和Tomcat的安全补丁也不能忽视,否则竞争对手可能雇佣黑客来攻击我们的系统。
- 配置公共云的Auto-Scaling的策略,应对高峰期突发访问量。
- 使用公共云的对象存储和数据库。
- 编写AI应用。 要完成这些工作,我们既要开发AI应用,又要营运支撑业务(例如管理云主机生命周期、管理操作系统)。这是当前架构的现实:为20%的核心业务营运80%的支撑业务。
- 下面用Serverless架构改写AI应用:
- 多个运行AI应用代码的进程被启动,并发处理用户上传的图片。
- 在Serverless架构的AI应用中,我们只需要做两件事情:
- 使用公共云的对象存储和数据库。
- 用公共云的Serverless框架编写AI应用。
与之前的架构相比,我们不再营运云主机、操作系统、Tomcat,同时也不需要配置Auto-Scaling Group,公共云的Serverless框架会在每个图片上传完成后启动一个进程运行AI应用,自动实现水平扩展。我们终于只需要关心核心业务了,用Serverless框架支持的语言(例如AWS Lambda就支持Java, Python和JVM系语言)编写AI应用,一切非核心业务都外包给了公共云营运商。
我们的Serverless AI应用用到了两种技术。首先使用了公共云提供的对象存储和数据库服务,统称为BaaS(Backend as a Service,后端即服务)。其次用了Lambda框架,称为FaaS(Functions as a Service,函数即服务)。
使用BaaS和FaaS是Serverless应用的基本特征,符合这两个基本特征的应用可称为Serverless应用。
是BaaS,不是PaaS
AI应用用到了对象存储和数据库,将来或许还会用到消息队列。直观感觉是在使用PaaS,为什么还要造一个新词BaaS?技术圈有太多令人混淆的术语了。
BaaS并非PaaS,它们的区别在于:PaaS需要参与应用的生命周期管理,BaaS则仅仅提供应用依赖的第三方服务。典型的PaaS平台需要提供手段让开发者部署和配置应用,例如自动将应用部署到Tomcat容器中,并管理应用的生命周期。BaaS不包含这些内容,BaaS只以API的方式提供应用依赖的后端服务,例如数据库和对象存储。BaaS可以是公共云服务商提供的,也可以是第三方厂商提供的,例如Facebook收购的Parse就是著名的MBaaS提供商(Mobile Backend as a Service)。从功能上讲,BaaS可以看作PaaS的一个子集,即提供第三方依赖组件的部分。
FaaS是Serverless的核心
AI应用最初是一个典型Java程序,它可能使用Spring这样的技术,因为我们需要一个框架确保程序的各个组件能够被正确加载,需要MVC来保证REST API被正确的Controller处理。AI应用部署在Tomcat容器中,运行在云主机上,7 x 24小时运行,我们提供不间断的服务。在夜里12点到早晨8点,几乎没有用户使用,但我们还得让它待在那里,防止深夜偶尔使用的用户得到一个503错误而误会AI服务不稳定。我们为购买的云主机付钱,尽管一半的时间它的CPU使用率几乎为0,但没有公共云是按CPU使用率计费的,不工作的时间也得付钱。我们必须关心Auto-Scaling Group的配置,如何准确的配置Auto-Scaling策略是一个技术活,需要长期的经验积累,在早期我们不得不多部署一些空闲的云主机以保证服务不会因Auto-Scaling的配置不当而拥塞。
用Serverless架构改写了AI应用后,这些痛苦就通通消失了。Spring框架和Tomcat去掉了,用Lambda的Java SDK,只需要实现一个Function Handler处理图片上传完成这个事件,这跟写一个Callback一样简单。在Function Handler中调用图片识别的相关逻辑,然后调用数据库的REST API存储结果。也不用构建MVC,不用配置Tomcat的XML文件,我们将存储网关这个功能完全去除掉了,因为用户可以直接上传图片到对象存储。
AI应用不用7 x 24小时运行了,没有用户上传图片时它只是一份编译好的代码。当用户图片上传完成时,FaaS会为AI应用启动一个新的进程执行代码。该进程在代码执行完成后自动销毁。我们只需为代码执行的这几十秒钟付钱,节省了很多开支。
最后我们无需操心Auto-Scaling的问题,FaaS会在需要的时候自动扩展。
- 这些就是FaaS的核心,从上面的例子里面可以归纳出它的特点:
- FaaS运行的是后端代码而不是整个后端程序。例如AI应用仅仅包含处理图片上传完成这个事件的逻辑,并不是一个完整的后端程序,而是一段后端代码。
- 代码通过事件触发。由于不再有一个长期运行的进程等待或轮询用户请求,代码只能通过特殊的事件触发。这些事件由FaaS框架定义,例如上传文件到对象存储、消息队列收到一条新的消息、API Gateway收到一个新的API请求等。
- 代码的生命周期很短。例如我们的AI应用,从收到事件后Function Handler被调用开始,到调用返回结束,不会有常驻内存的进程运行。此外公共云提供商还会限制代码执行的时间,超出时间后执行代码的进程会被强行销毁。例如AWS的Lambda可执行的最长时间为5分钟。
- 代码必须做到彻底无状态,两次调用间不能共享内存状态。我们的AI应用最早使用了一个全局变量统计处理的图片数,每处理完一张图片该计数器就加一。使用FaaS后我们不能再用任何全局变量或内存数据结构(例如Hashmap)在调用间共享数据,因为代码运行在独立的进程中,无法访问对方的内存地址空间。于是我们对代码进行了改造,将全局计数器放到了公共云的Redis服务中,这为代码增加了额外的复杂性。
- 水平扩展不再是需要担心的问题,FaaS会为每个事件和请求运行一份新的代码。
- 应用的部署方式从上传、配置整个程序变成上传一份打包代码的文件(例如Jar文件或一个Zip文件)。
Serverless为我们带来了什么
对比传统架构,用Serverless架构改写的AI应用具有显著的优势。我们不再运维任何云主机和操作系统,甚至不再运维Tomcat这样的Web容器,只需要专注于代码本身,所有配置、应用生命周期管理的工作都由FaaS框架负责。公共云的出现让我们从物理硬件管理中解放出来,Serverless架构让我们进一步从操作系统管理中解放出来,第一次真正专注于核心业务。
业务也变得更加敏捷了。我们只需要编写核心业务相关的代码,例如AI应用中图像识别的部分。无需编写任何加载、部署、配置应用的代码,例如不再需要配置systemd在系统启动时加载应用。
水平扩展也不是问题。正如前面反复提及的,FaaS框架会为每一个事件、每一个API请求都启动一份新的进程执行代码。这跟传统应用的线程池方式类似,每个请求都在一个单独的线程中执行,区别在于线程之间共享同一内存地址空间,FaaS的进程间不共享任何内存。与线程池有最大线程数限制类似,FaaS框架通常也限制了最大进程数,例如AWS Lambda在一个Region默认能执行的最大并发调用是600,也就是说我们的AI应用最多能在600个进程中同时执行。
最后,也是最重要的,Serverless架构为我们节省了大量开支。我们只需为AI应用运行的时间付钱,无需为应用等待请求的时间付钱。水平扩展的粒度从原来的云主机细化到进程,节省了额外的开支,不用再购买闲置的云主机来抵消Auto-Scaling的配置不精确带来的影响。业务的敏捷性提高也降低了营运成本,我们不再需要精通操作系统配置和管理的营运人员,不仅节省了人力成本,也节省了应用从开发到上线的时间。
Serverless不是银子弹,是后端小程序的未来
serverless架构在某些应用场景的优势如此明显,有些支持者已经开始炒作它会成为颠覆性的云计算新架构了。技术圈向来如此,一些人总在孜孜不倦的寻找包治百病的灵药,和解决一切问题的银子弹。“All design is about tradeoff”,Serverless也不是银子弹,它有独特的优势,而这些优势也带来了不可避免的局限。
为每个事件/请求启动一个全新的进程运行代码是FaaS的核心,进程的启动延时是Serverless面临的第一个问题。取决于编写应用的语言,启动延时可以是10毫秒(如简单的Python应用),也可以是1分钟(复杂的Java应用)。这样的延时对于realtime的程序是难以接受的。目前Serverless应用通常运行在公共云的多租户环境中,启动延时还受系统负载影响,很难保证应用在规定时间内被运行。公共云提供商目前没有对Serverless提供相应的SLA保证,笔者写这篇文章的时候,AWS Lambda还没有相关的SLA条款。
Serverless无法用于高并发应用,为每个请求启动一个进程开销太高。例如双十一支付宝高峰期每秒处理的交易数为8.59万笔,如果使用Serverless架构,意味着我们的系统内每秒有8.59万个进程被创建又被销毁,这是难以负担的开销。
Serverless应用无法常驻内存,运行的时间是受限的。如果你的应用无法在数分钟内完成的工作,那Serverless不是你的选择,例如AWS Lambda给予进程的最长运行时间是5分钟,超时后进程将被强制终止。这对程序设计提出了挑战,例如我们的AI应用必须优化到在5分钟内完成复杂图像的识别。我们也不能编写执行长时间IO操作的应用,例如对对象存储中1T的数据进行复杂编码。
Serverless调用之间不能共享状态让编写复杂程序变得极度困难。无状态是互连网应用追求的目标,例如满足“12要素”的应用。但Serverless将无状态进行的更加彻底,在不同的调用之间无法共享内存状态,例如使用hashmap。我们的AI应用中统计已处理图片总数的全局计数器在传统架构中只是一个全局变量,但在Serverless架构中它变成存储在内存数据库(Redis)中的一条记录,更新成本、保证原子性等因素让我们的编码变得数倍复杂。对于大多云原生的互联网应用来说,这种彻底的无状态架构是一个巨大的挑战,而对于动辄有几十万、上百万行代码的、充满了状态的企业应用来说,Serverless的无状态改造几乎是一个无法完成的任务。
熟练的微服务的架构师,对将业务拆分成一个个单独的服务非常熟悉,也有不少的经典书籍(例如《Building Microservices: Designing Fine-Grained Systems》)指导我们如何做。但即使是他们,在面对Serverless架构时也会感到头痛,如何将业务拆分成成百上千个运行在独立进程、运行时间受限的函数是巨大的挑战。而是否需要如此细粒度的拆分是需要回答的第一个问题。有些问题或许变成无解难题又或成本极高,例如分布式数据库事务。
上面都是Serverless架构的一些固有局限,它们源于Serverless架构的特点,很难随着时间的推移、技术的完善而解决。除此之外,作为一个新的技术,Serverless还面临着集成测试困难、Vendor Lock-in、调试监控困难、版本控制等诸多不足,每一项都会成为采用Serverless架构的阻碍。
由于这些局限性,Serverless架构不会成为复杂应用的架构首选,相反,它应该是后端小程序的未来。
云端的应用有大量的小程序场景,例如识别一张图片、对一段音频/视频进行编解码、对IOT设备的请求返回一小段数据、将客户提交的工单通过邮件通知客服人员等等。这些基于事件触发的小程序在传统架构中实现起来是相对复杂的,你往往需要为20%的核心业务运营80%的支撑业务。Serverless完美的解决了这些问题,它可以成为复杂应用的一种补充架构。我们可以将无状态的、事件触发的业务拆分成Serverless应用,让整个架构变得更加的简洁和高效。
Serverless也在不断演变,例如AWS最近引入的Step Functions就尝试解决调用间共享状态的问题,其效果有待观察。
Serverless不是传统的PaaS
Serverless跟PaaS之间的界线比较模糊,很多人认为Serverless是PaaS的一种,笔者也倾向于认为Serverless是特殊的PaaS形态。
Serverless由BaaS和FaaS两部分构成,BaaS负责提供业务的依赖服务,FaaS负责业务的部署和生命周期管理,从这个意义上来看,Serverless的角色跟PaaS一样。与传统PaaS的区别在于,传统PaaS是以程序为粒度管理应用的生命周期,而Serverless是以函数粒度管理应用生命周期。传统PaaS中的应用为常驻内存的进程,而Serverless应用运行完即销毁。此外,使用传统PaaS,用户仍需要关心水平扩展,例如如何配置Auto-Scaling Group,但Serverless没有这个问题,水平扩展是架构天然自带的功能。
Serverless和微服务
Serverless和微服务没有直接关系,但两者有相似之处,例如都需要做业务拆分、强调无状态、具有敏捷特性等。Serverless在很多方面比微服务粒度更细,要求也更严格。例如微服务以服务为边界拆分业务,Serverless以函数为边界拆分业务;微服务可以有跨调用的内存状态共享,Serverless要求调用彻底无状态。此外,Serverless依赖BaaS提供第三方依赖,而微服务可以自由选择第三方依赖来源,例如使用本地搭建的传统中间件栈(如本地MySql和消息总线)。
Serverless和容器
Serverless和容器是苹果和桔子的比较,不在一个平面上。Serverless是一种软件设计架构,容器是软件架构的承载者。虽然没有公开资料,但我们可以推测类似于AWS Lambda这样的Serverless框架使用了某种程度的容器技术,否者难以实现语言无关和毫秒级的启动。尽管已经有一些开源项目使用Docker实现Serverless中的FaaS部分,笔者不认为AWS Lambda这样的公共Serverless框架直接使用了Docker,一定是一种更为轻量级、体积更小的容器技术,我们或许可以将它称为Nano-Container。
Serverless对私有云有意义吗?
对于私有云来说,现在将业务迁往Serverless架构还为时过早。首先Serverless是从公共云中演化出来的新型架构,适用于运行在公共云上的小程序。而私有云更多承载的是老而笨重的传统业务,难以用Serverless架构改造。其次Serverless依赖BaaS,在私有云中搭建和运维BaaS成本都不低,使用公共BaaS服务又受限于网络带宽和延时,容易导致系统不稳定。
随着企业应用的进一步云化、开源Serverless框架的成熟,私有云的Devops场景也可以采用Serverless作CI/CD,例如目前Jenkins承担的大部分工作都可以用Serverless替代,如用FaaS框架对应Jenkins本身,上传的代码对应Jenkins Job中的Bash脚本,将原来的Jenkins API触发Job改为触发FaaS中的代码。