代码包优化
在各个云厂商的FaaS平台中,都有着对代码包大小的限制,抛掉云厂商对代码包的限制,就单纯地说代码包的规格可能会产生什么影响,通过函数的冷启动流程可以看到:
在函数启动的过程中,有一个过程是加载代码的过程,那么当所上传的代码包过大,或者说文件过多导致解压速度过慢,就会直接导致加载代码这个过程变长,进一步直接导致冷启动时间变久。
可以设想一下,当有两个压缩包,一个是只有100KB的代码压缩包,另一个是200MB的代码压缩包,两者同时在千兆的内网带宽下理想化(即不考虑磁盘的存储速度等)下载,即使最大速度可以达到125MB/S,那么前者的下载速度只有不到0.01秒,后者需要1.6秒,除了下载时间之外,还有文件的解压时间,那么两者的冷启动时间可能就相差2秒。一般情况下,一个传统的Web接口,如果要2秒以上的响应时间,实际上对很多业务来说是不能接受的,所以在打包代码时就要尽可能地降低压缩包大小。以Node.js项目为例,打包代码包时,可以采用Webpack等方法,来压缩依赖包大小,进一步降低整体代码包的规格,提升函数的冷启动效率。
合理进行实例复用
在各个云厂商的FaaS平台中,为了更好地解决冷启动的问题,为了更合理地利用资源,是存在“实例”复用情况的。所谓的实例复用,就是当一个实例完成一个请求,他并不会释放,而是进入一个“静默”的状态,在一定时间范围内,如果有新的请求被分配过来,则会直接调用对应的方法,而不需要再初始化各类资源等,这个过程很大程度上降低了函数冷启动的情况出现。为了验证,可以创建两个函数:
函数1:
# -*- coding: utf-8 -*- def handler(event, context): print("Test") return 'hello world'
函数2:
# -*- coding: utf-8 -*- print("Test") def handler(event, context): return 'hello world'
在控制台多次点击测试按钮,对这两个函数进行测试,判断其是否在日志中输出了“Test”,可以统计结果:
第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 第六次 | 第七次 | 第八次 | 第九次 | |
---|---|---|---|---|---|---|---|---|---|
函数1 | 有 | 有 | 有 | 有 | 有 | 有 | 有 | 有 | 有 |
函数2 | 有 | 无 | 无 | 有 | 无 | 无 | 无 | 无 | 无 |
根据上面表格情况,可以看到,其实实例复用的情况是存在的,因为函数2并不是每次都会执行入口函数之外的一些语句。根据函数1和函数2,也可以进一步思考,如果print("Test")语句是一个初始化数据库连接,或者是加载一个深度学习的模型,是不是函数1的写法就是每次请求都会执行,而函数2的写法是可以存在复用已有对象的情况?
所以在实际的项目中,有一些初始化操作,是可以按照函数2来进行实现的,例如:
- 机器学习场景下,在初始化的时候加载模型,避免每次函数被触发都会加载模型带来的效率问题,提高实例复用场景下的响应效率;
- 数据库等链接操作,可以在初始化的时候进行链接对象的建立,避免每次请求都创建链接对象;
- 其他一些需要首次加载时下载文件,加载文件的场景,在初始化的时候进行这部分需求的实现,可以在实例复用的时候效率更高;
单实例多并发
众所周知,各云厂商的函数计算通常是请求级别的隔离,即当客户端同时发起三个请求到函数计算,理论上会产生三个实例来进行应对,这个时候可能会涉及到冷启动问题,可能会涉及到请求之间状态关联问题等,但是部分云厂商提供了单实例多并发的能力(例如阿里云函数计算),该能力允许用户为函数设置一个实例并发度(InstanceConcurrency),即单个函数实例可以同时处理多少个请求。
如图所示,假设同时有3个请求需要处理,当实例并发度设置为1时,函数计算需要创建3个实例来处理这3个请求,每个实例分别处理1个请求;当实例并发度设置为10时(即1个实例可以同时处理10个请求),函数计算只需要创建1个实例就能处理这3个请求。
单实例多并发的优势:
- 减少执行时长,节省费用。例如,偏I/O的函数可以在一个实例内并发处理,减少实例数从而减少总的执行时长。
- 请求之间可以共享状态。多个请求可以在一个实例内共用数据库连接池,从而减少和数据库之间的连接数。
- 降低冷启动概率。由于多个请求可以在一个实例内处理,创建新实例的次数会变少,冷启动概率降低。
- 减少占用VPC IP在相同负载下,单实例多并发可以降低总的实例数,从而减少VPC IP的占用。
单实例多并发的应用场景时比较广泛的,例如函数中有较多时间在等待下游服务的响应的场景就比较适合使用该种功能,但是单实例多并发也并不适合全部应用场景,例如当函数中有共享状态且不能并发访问的场景,单个请求的执行要消耗大量CPU及内存资源的场景,就不适合使用单实例多并发这个功能。
预留实例与冷启动优化
预留模式通过预留适量函数实例来响应函数调用请求,降低冷启动的发生次数,为时延敏感的在线业务提供更好的服务响应。所谓的预留实例指的是在一些情况下,FaaS平台没办法根据业务的复杂需求,自动进行高性能的弹性伸缩,但是业务方却可以对其进行一定的预测。例如,某团队在此是凌晨要举办一次秒杀活动,那么该业务对应的函数可能在秒杀活动之前都是沉默状态,在秒杀活动时突然出现极高的并发请求,此时即使是天然分布式架构,本身自带弹性能力的Serverless架构,也是很难高性能的迎接该挑战。所以,在活动之前,可以由业务方手动进行实例的预留,例如在次日零时之前,预留若干实例以等待流量峰值到来,次日23时活动结束时,释放所有预留实例。
虽然预留模式在一定程度上违背了Serverless架构的精神,但是在目前的业务高速发展过程中与冷启动带来的严重挑战下,预留模式还是逐渐的被更多厂商所采用,也被更多开发者、业务团队所接纳。虽然预留模式在一定程度上会降低冷启动的发生次数,但是也并非可以完全杜绝冷启动,同时在使用预留模式时,配置的固定预留值会导致预留函数实例利用不充分,所以此时,云厂商们通常还会提供定时弹性伸缩和指标追踪弹性伸缩等多种模式进一步解决预留所带来的额外问题。
定时弹性伸缩
由于部分函数有明显的周期性规律或可预知的流量高峰,所以可以使用定时预留功能来提前预留函数实例。所谓的定时弹性伸缩,指的是开发者可以更加灵活地配置预留的函数实例,在指定时间将预留的函数实例量设定成需要的值,使函数实例量更好地贴合业务的并发量。如图35所示,配置了两个定时操作,在函数调用流量到来前,通过第一个定时配置将预留函数实例扩容至较大的值,当流量减小后,通过第二个定时配置将预留函数实例缩容到较小的值。
指标追踪弹性伸缩
由于在生产条件下,往往周期性的函数并发规律并不容易预测,所以在一些复杂情况下就需要通过一些指标进行实例预留设定。例如阿里云函数计算所拥有的指标追踪弹性伸缩能力就是通过追踪监控指标实现对预留模式的函数实例进行动态伸缩。这种模式通常被用于以下场景:函数计算系统周期性采集预留的函数实例并发利用率指标,使用该指标并结合您配置的扩容触发值、缩容触发值来控制预留模式函数实例的伸缩,使预留的函数实例量更好的贴合资源的真实使用量。
如图所示,指标追踪弹性伸缩根据指标情况每分钟对预留资源进行一次伸缩,当指标超过扩容阈值时,开始以积极的策略扩容预留模式的函数实例量,最快速度将函数实例量扩容至目标值;当指标低于缩容阈值时,开始以保守的策略缩容预留模式的函数实例量,小幅度向缩容目标值贴近。如果在系统中设置了伸缩最大值和最小值,此时预留的函数实例量会在最大值与最小值之间进行伸缩,超出最大值时将停止扩容,低于最小值时将停止缩容。