Serverless自动弹性伸缩的特性让应用具备了无限扩展的能力,但基于FaaS的实现也带来了很大的副作用,即冷启动,冷启动是代码在处理业务功能之前的额外开销。
既然冷启动会影响Serverless的性能,能否应用低延迟高并发场景呢?
Serverless的性能优化核心就是减少冷启动
深入理解Serverless冷启动是优化Serverless应用性能的前提。
函数启动过程示意图
冷启动经过多个步骤,耗时较长。
使用链路追踪工具比如AWS的X-Ray,阿里云的链路追踪,Lambda,函数计算可以查看冷启动耗时执行时间从而判断和分析Serverless应用的性能。
函数计算链路追踪截图
- • InvokeFunction 表示函数执行总时间
- • ClodStart 表示函数冷启动时间
- • PrepareCode 表示函数冷启动过程中下载代码或下载自定义镜像的时间
- • RuntimeInitialize是执行环境启动的时间,包括启动容器和函数运行环境
- • Invocation 表示执行函数时间
上图说明这是执行冷启动的过程,因为热启动没有ClodStart、PrepareCode、RuntimeInitialize。
整个函数执行耗时553ms,冷启动486ms,由此可见,冷启动对函数性能影响很大。
什么时候函数是冷启动或者热启动呢
函数第一次执行的时候一定是冷启动,但后面的请求不一定都是热启动,这与触发函数执行的事件是串行还是并行有关。
串行访问
以http触发器为例,如果所有用户的请求都是串行的,则只有第一次的请求是冷启动。
通过charles等工具来模拟用户连续请求该接口的情况。把并发设置为1发起100个请求,函数就会被顺序调用100次,这100个请求就是串行的。
第一个请求耗时700多毫秒,该请求的响应体中返回了requestId,根据requestId在链路上查询到的第一个请求确实是冷启动。
后面的请求只耗时了40毫秒左右,都是热启动。
对于某些性能要求不高的厂家来说,这100个请求中,只有一个请求是冷启动,影响的用户是1%也是可以忍受的。
并发访问
前10个请求都是冷启动。
网站一天之内流量有波峰波谷的,流量突增(比如团购订餐业务,可能每天中午、晚上流量突增;促销活动,在活动开始前流量突增;社交软件,遇到重大新闻时流量突增)就意味着FaaS平台不得不添加更多的实例来支持更大的并发并且新增实例时都会有冷启动,这就对用户体验有较大影响了。
如何优化Serverless的性能
- • 避免函数冷启动
- • 减小代码体积
- • 提升函数吞吐量
- • 选择合适的编程语言
避免冷启动
对函数进行预热
预热就是指你通过定时任务,在真实请求到来之前对函数发起请求,使函数提前初始化。真实请求就是使用已经初始化过的函数实例去执行代码。
比如11点58通过定时任务请求需要预热的API,12点的时候真实的请求使用热启动的函数。
使用特定的请求头来标记预热请求,这样就可以把它和正常的用户请求分开,还可以不对热请求做任何处理。
函数预热
函数预热是彻底消除冷启动时间,代价就是需要维护预热的逻辑并且提前预热的函数并没有处理用户的请求而持续的占用资源。
使用预热的场景
是否使用预热方案,既要考虑业务场景,也要平衡性能和成本,如果你的应用对延迟要求很高,比较秒杀业务,就可以使用预热功能。
使用预留资源
此外有些Faas平台(比如函数计算)也提供了预留资源的功能,可以为你的函数实例持续保留。需要手动去创建释放函数运行的资源。使用预留资源,就不用发预热请求了,但使用成本会高些。
减少代码体积
函数冷启动的第一个步骤就是下载代码。减少代码体积,可以避免引入不必要的依赖,不要加载不需要的代码。对SDK进行精简,对代码进行压缩,甚至只构建需要执行的代码。
对Node.js来说尤为重要,因为Node.js的依赖目录node_moudles通常体积非常大,非常冗余。
可以使用ncc构建代码依赖,减少体积提升性能。
大多Faas都支持单实例多并发
为了提升函数的吞吐量,一个实例可以同时处理多个请求,能够减少函数实例的生成进而减少冷启动。
假设3个并发请求同时需要处理,当函数并发为1的时候,FaaS平台就会生成3个函数实例来处理这3个请求,每个函数实例处理一个请求需要经过3次冷启动。
当函数并发为10,则只会生成一个函数实例,来处理3个并发。
单实例单并发
单实例单并发的请求下函数实例只能处理一个请求,处理完毕,才能处理下一个请求。
单实例多并发
单实例多并发可以同时处理多个请求。从实例的生命周期也可以看的出来,单实例多并发,实例执行时间更短,这样成本自然也就更低。
单实例处理多个请求代码示例
第一个请求到来,函数冷启动,会先下载代码,然后初始化一个Node.js的运行环境,接下来就会执行初始化代码,主要就是引入mysql依赖包,初始化数据库连接,最后执行handler方法。
第二个请求到来函数就是热启动了,这个时候函数就会重复使用上一个运行环境,会重复使用上次的执行上下文,包括mysql依赖,数据库连接。
热启动的时候只会执行handler函数,基于热启动的特点可以把初始化逻辑都放在handler函数之外,只需要第一次冷启动来执行初始化就能提升函数性能。
而单函数多并发就进一步的放大了这个特点,基于单函数多并发Serverless应用就可以更好的支持低延迟高并发场景。
除了为函数设置并发减少冷启动次数之外,不同编程语言的冷启动时间也不尽相同,Node.js和Python比Java等静态语言启动时间少很多。Java冷启动慢主要是因为需要启动庞大的虚拟机并且将所有的类加载到内存中初始化。
选择冷启动时间短的编程语言,可以大幅提升应用性能