编辑推荐: |
本文主要介绍了围绕云函数的架构去做一些架构上的解析以及腾讯云在云函数的函数冷启动上的优化经验。
本文来自于腾讯云,由火龙果软件Linda编辑、推荐。 |
|
今天很开心有机会跟大家分享现在比较流行的Serverless Computing技术,也就是无服务器计算的技术,云函数是其中一个实现的方向,今天主要围绕云函数的架构去做一些架构上的解析以及腾讯云在云函数的函数冷启动上的优化经验。
首先,很直观地看一个例子,FaaS能做什么?假如说你要做一个类似于个人博客的小程序的话,除了需要把前端的代码提交到微信的平台,还需要去编写后端的服务代码。看看传统的做法:首先编写完代码以后要购买虚拟机,去进行一些OS环境的部署,包括相关开发库,ngnix,web框架等,部署完之后需要把代码发布上去,更长远来看你可能需要一个代码包发布的系统,在架构层面可能除了实现业务逻辑代码之外,还要考虑代码实现具备容灾的能力,负载均衡的能力,具备这些能力之后什么时候去扩容,这也是非常难的事情。在线上运营的时候业务指标的监控、服务器的监控,流量控制、权限管理、安全防护都是需要重点考虑的问题,如果遇到运行库或操作系统方面的安全漏洞,还需要进行打补丁升级。除了代码之外需要做的事情是非常多的。
同样的业务场景在FaaS平台上开发者需要做什么事情呢?只需要把业务的代码以函数的方式去实现出来,在一个函数的平台上把它发布上去,后面所有的环境问题、发布的管理等等都不需要去考虑。FaaS让开发者更聚焦业务逻辑,把其他的事情留给平台。
看一下企业IT这几十年的演化,从最开始的租用机房,硬件的采购到部署到线上运营,企业所有的事情都需要操心,后面到云计算的时代,IaaS、PaaS从基础设施上提供了一些能力,近几年的FaaS做了更高度的抽象。整体的发展趋势是以提升企业效率,降低企业支出的趋势去发展的。
刚刚提了Serverless和FaaS的概念,我再回顾一下这些概念。Serverless其实并没有一个很明确的定义,字面上的意思是没有服务器,但并不是说后台没有服务器,而是说开发者不需要关注服务器层面的事情。Serverless会分成两种场景,一是FaaS另外一个BaaS,两者有一些区别,FaaS的场景下后台的代码还是需要开发者去开发的,只不过是以函数的方式去开发,BaaS的场景下,云厂商会针对各种各样的业绩逻辑去做一些公共的提取,比如说对象存储,短信服务,或者登录的服务,让开发者不需要开发后台的服务。
FaaS不仅仅是函数,因为在实际运营的过程中,发现只提供一个运行的环境是不够的,因为这种计算模式开发者已经一定程度上改变了开发的习惯,从原来的编写代码以及代码的持续集成到业务运营方面都不会一样,所以函数又会延伸出研发流程上的需求,但是由于时间上的关系,今天仅仅是探讨函数计算方面的架构。
FaaS跟很多软件的设计一样,是分层的设计模型,比如PaaS构建在IaaS之上,FaaS构建在PaaS和IaaS之上,在他们的基础上,提供代码调试、发布、CI/CD,线上运营的能力等等,现在主流的公有云的FaaS平台和开源的FaaS平台,是基于K8S或容器构建的。下面是腾讯云SCF的函数平台架构,分为两部分,蓝色是控制平面的模块,黄色是数据平面的模块。控制平面流程通过一个消息队列解耦各个模块,我们以创建一个函数为例,开发者通过API创建一个函数对象之后,这个事件发布到消息队列就完成了API层面的事情,消息队列上有很多的模块监听,有网络的模块,函数运行在云的环境里面必然需要跟公网或者跟云的私有网络资源进行访问,所以有一个网络打通的模块。二是跟代码相关的,开发者的代码发布上来以后,我们需要对代码做一些检查,这个模块安装相关的代码包依赖部署在一起,把代码再次打包放在内部的代码系统里面。AutoScale和Scheduler这两个是调度方面的两个模块,比如说函数创建完以后,这两个调度模块知道这个事件之后,可能会提前部署函数运行的环境,避免调用的时候才开始准备环境造成函数的冷启动。
因为在调用的路径里面,并发量是非常大,我们会把函数创建变更,通过消息队列同步到Invoker模块,在函数调用的时候直接通过本地缓存查询元数据进行相关校验,转发。到了黄色这一块,我们有一个公共的接入层,函数因为部署完最终还是要调用才会有效果,首先我们定义出接入的入口,函数可以通过一些业务的客户端直接调用以及函数跟云的其他产品打通,可以通过他们的一些产品能力来触发函数的执行。接入层提供鉴权,并发控制的能力;还有就是容器的池子复用,接入层鉴权这些逻辑完了之后直接发请求到底层虚拟机上面的容器处理,因为代码跑到容器上面,代码还需要访问公网的服务或者其他的虚拟机或者数据库的资源,中间经过网关,网关也会重点介绍,这一块也是今年腾讯云在函数里面做的比较大的优化。
函数执行完之后,还有一些数据需要采集收集,比如说函数执行的时间,消耗的内存以及函数的输出日志信息,调用完以后会同步到右边几个模块,按量计费,也有一些独立的计费系统去做一些计费能力。
同时,讲一下调用跟函数之间是一个什么样的关系,在函数产品下的管理粒度或者调用的粒度都是函数,所以函数运行起来就是一个一个函数的实例,客户端并发的请求过来就会对应后端的一个容器,当它有三个请求并发处理的时候就会有三个容器,如果此时来了第四个请求,这个时候前面的三个请求没有执行完的话,就需要进行新的容器启动的过程。从这个结构来看,就可以知道函数这个架构是非常弹性的,当你没有请求的时候,这些容器全部缩到0,当你请求量增长起来,可以很快速扩展上去。
从刚刚的一些介绍可以看到,平台有非常多的函数,可能很多函数的并发量也是非常高的,每个请求对应一个容器资源的部署,挑战是非常大。可能以前用虚拟机的时候可能并不会去关注每秒钟能创建多少台虚拟机,也不会关注创建一个虚拟机到底是五秒还是十秒。但是在FaaS平台下对基础设施的要求就完全不一样了。腾讯云函数做了大量的优化,包括调度层面,轻量级虚拟机等做了很多的优化,今天不会一一展开,就重点在冷启动方向去聊一下腾讯云函数在冷启动调度层面做的优化工作。
开发者提交代码之后你不知道他调不调用,函数第一次调用会有一个函数冷启动,把网络的环境全部打通,这个函数才能提供服务。为什么会重点聊冷启动?因为如果你没有优化好这部分,可能对于一些比较关键的产品首次启动会产生超时,体验非常不好,以前开发者本地运行函数的时候,并不会关注本地函数执行多少毫秒和微妙,但是在云函数场景下就不一样了,云函数有一个部署的过程;无论是公有云的平台上还是开源方案上,冷启动都是值得不断探讨话题和优化的方向。
要理解怎么去优化冷启动的过程首先要看一下冷启动包括哪些步骤。客户端发起一个请求之后,到了Invoker接入层,先判断函数实例是否已经创建好,创建好的话这个调用是毫秒级别的,如果没有的话要走冷启动的过程。冷启动又包含几个关键的阶段,首先就是要创建一个容器出来,现在容器创建是秒级别,这个时间是比较长的。当你创建完容器以后,还没有代码,因为代码在代码仓库上你需要去下载,你是否已经缓存过,如果缓存过就不需要做下载,代码下载部署到容器里面,函数要对外访问还需要打通一些网络的过程,在那么大的规模下网络打通的过程是涉及到大量路由数据的下发,现在的效果基本上是秒的级别。针对这几个领域,我们接下来会分别去看一个一个怎么优化。
如果已经有一个函数实例复用就不用再冷启动,如何做代码缓存,预创建,最后是如何做预启动,在函数发生调用之前就把环境准备好,这样就规避到冷启动的问题。
函数实例复用,一个数据用完以后就不要太急着销毁它,因为有可能下次要使用,我们在接入层对函数实例进行一定程度的复用保留。那么保留多长时间合适?保留太短可能效果不太好,保留太长平台有那么多函数实例可能占用的资源太多。根据我们以往的资料来看,保留三分钟能解决95%的问题,三小时能解决99%的问题,保留三天也不能100%解决的。保留的时长完全看具体的场景,如果这个函数的架构是在私有云上面部署,可能底层的容器或者虚拟机的成本已经产生在那里,大可以复用得更久一些。保留的时间也不是固定不变,经过我们的分析发现,有些函数它是计划任务,可能每天调一次或者每个小时调一次,是可预期的,这种函数执行完可以立马把它资源回收掉。还有就是在晚上的话,可能就是零零星星的调用,复用时间太长的话对平台资源消耗也比较大,说到弹性,不单是开发者能享受到,函数平台自身也需要具备弹性的能力,当晚上业务低峰的时候,函数平台可以把大量平台退掉,这个对成本也是很大的节约。
容器预创建的优化,在预创建有一个预创建池子,只是空的容器,并不包含代码,有一个存容器的资源池,也需要考虑这个池的规模到底要多大,跟刚刚也是一样,成本方面的考虑,做得太小可能突然来了一些突发的请求会把这个池子消耗掉,太大又是一个成本的问题。
还有代码的缓存,函数平台会有一个代码的管理仓库,在容器已经准备好的情况下就会涉及到代码的下载过程,代码缓存我们做了两个优化,热点代码二级缓存,在容器的Node本地缓存。二是机房内缓存,缓存需要资源调度的配合,找到代码在哪里,可以修改我们资源调度模块,当它要创建某一个函数实例可能需要看这个函数的代码在哪里,减少代码的下载。
转发网关,现在传统的函数平台都是函数容器里边绑定弹性网卡去访问开发者其他的资源,这个网络的部署需要秒的级别,我们在公有云的一些同类产品也体验过,基本上从一个响应时间来看,也可以猜测到是这样时间的过程。今年又做了优化的特性,把弹性网卡绑在函数容器,转移到集中网关上,做了这个优化以后,效果也是非常明显的,冷启动时间从秒级到毫秒级,极大的减少弹性网卡的消耗时间。转移到网关的方案就只需要消耗两个IP就够了。
预启动的问题,这个是非常难的,我们也在不断地优化,要做好预启动首先需要解决三个问题:一是如何确定是否需要扩容,我们现在做得还比较简单,先通过简单的分析来看它是否需要扩容,可以做很微量的容器保留。我们也会往机器学习做探索,通过函数历史的运营数据做训练;二是提升扩容请求响应时间,当函数数量非常大的时候,能否做到实时地去分析所有的函数下一个五秒是否需要扩容,这是非常难的,我们现在也对这个模块做了优化以后,基本上能做到五秒对全量函数做一次分析。三是降低函数实例启动时间,解决冷启动问题的一部分,如果这个问题不解决的话,有可能扩容系统已经算出来扩容,但是你可能耗太多时间去启动。不是说所有的函数都需要预扩容,我们也做了一些分析,比如说一些异步的场景,比如异步调用场景,并不是客户的同步调用,二是计划任务,或者说间接触发,比如说你上传一个图片到存储系统,触发你函数去执行,可能并不需要做到实时扩容的。
还有函数并不仅仅是一个个独立使用的实体,通常可以串起来使用,是有一个调用链,如果函数A已经有缓存情况下,B和C冷启动,最终会花费大量的时间在冷启动方面。A对B和C的依赖我们是知道的,我们提前把这些函数启动起来。
还有几种场景是可预知的扩容,比如说函数A不断被调用,这个时候更新了代码之后,新的代码就需要在新的容器创建起来,我们知道前面老的那个代码跑了多少个容器,创建了新的容器再把流量切换过去就可以了。比如说函数A有两个版本你先把新的版本先切20%的流量过去,配置了切换的规则以后,平台会提前把原来A版本20%需要的容器先启动起来。还有函数平台本身的升级,也可以先在其他节点上把函数实例创建出来,再回收原节点的实例。
刚刚说了很多的平台优化,那么在开发者方面又能做哪些优化?我们经过总结发现四个方面可以做:一是代码精简,可能很多代码都会依赖很多杂七杂八的库,可能并不需要使用到,导致整个代码包非常大,做一定的裁减的话,哪怕要下载效率也会得到很大的提升。二是资源复用,比如说函数里面会有DB的连接,这些连接的建立是需要消耗时间的,可能十几毫秒的级别,如果这个连接的对象放在函数的栈变量,第一次请求完了以后,对象是会被销毁的,达不到连接复用,可以把连接的对象放到函数的外面做成全局的变量,连接是可以得到很好的复用,也缩短函数执行的时间。三是公共剥离,因为平台在做代码缓存的时候肯定会分析哪些代码更有缓存的价值,当代码被使用得非常多,被依赖的话肯定会增加代码的热度增加缓存的效果。四是保持活跃,开发者很聪明,他们知道函数平台上面如果一段时间不调用,平台会把这个资源销毁掉,有些开发者可能就定时调一下,让它一直存活。
最后看一些业务的案例。相册应用的场景从客户端上传照片,传统的做法需要部署服务器,但是函数的场景下触发的逻辑并不需要开发者开发,只需要配置一下就可以。一旦触发函数以后,函数可以把图片压缩成不同的格式,这样可以提供客户端再去下载。还有典型的消息队列处理,无论是Kafka还是CMQ,可能你对接不同的消息组件也是要做变更。平台会把消息队列产生的消息以函数的格式去经过一层转换,转到函数里面让他们去执行。这种场景下就比较适合做大数据的处理,比如说日志处理,以及异步业务逻辑,就不需要去部署消费者的程序。
还有计划任务,我们知道开发的系统有很多计划运行的场景,比如说备份函数、对帐函数、轮询函数,完全兼容Crontab的语法,避免单机故障。
刚刚说了很多搞FaaS的价值,优化了我们业务的架构,提升研发的效率,降低了成本,但是这些都是表面上的价值,它的最本质价值是让企业更高效地去做创新,前天晚上有一个想法,第二天早上可以去写一个函数去验证你的想法。
Q:您好我有两个疑问:一是函数编程码,之前没有接触过的,这个语言相当于你们自己定义的一套语言方式,根据你们写的文档去写函数?还是什么样的?
A:函数的开发有一些规范,函数无非就是输出和输入,中间的逻辑平台不会去关注,输入和输出平台只是做了对象的定义,无论是直接调用还是消息队列或者一些计划任务,其实它的格式都是一样的。
Q:二是去写一个函数,出现的问题怎么去定义问题?
A:函数平台不单单是计算,也可以提供一系列的能力,既然你问到我也可以简单说一下,比如说调试,你在线网发现有问题的时候,不需要关注负载类似的问题,你要关注业务跑慢了出了什么问题,在整个函数调用链可以看到每个耗时,你出现的问题可以帮你发现出来。
Q:您好,我想问的是在前面开始讲冷启动解决方案的时候,如果所有的开发者这样去用的话,对你们平台压力应该是很大,你们有没有什么方案应对情况?
A:压力我们觉得完全没有问题,这就是正常的函数调用,所以不担心压力问题,因为系统一定要去解决,前面的很多的优化都是解决冷启动的问题,最后提的是开发者有自己的思路去规避问题。
|