2017年,Serverless和函数计算越来越火,被称为"下一代云计算架构"。AWS Lambda已经成熟商用,阿里云函数计算(Function Compute)也在2017年正式商用了,腾讯云、华为云也纷纷推出了自己的函数计算服务。
我们团队一直关注新技术,在2017年阿里云函数计算正式商用后,就开始试用,把一些非核心的、低频的、事件驱动的功能,比如图片处理、定时任务、Webhook处理、数据同步等,迁移到函数计算上,希望能降低成本,提高效率,减少运维,不用再管理服务器。
但是,理想很丰满,现实很骨感。在试用的过程中,我们踩了很多坑,遇到了很多问题,有的问题很诡异,很难排查,让我熬夜到很晚才解决。今天就来分享一下我的阿里云函数计算踩坑记,希望能给正在用或者打算用函数计算的朋友一些参考,少踩坑,少熬夜。
一、函数计算的基本概念
在讲踩坑之前,先简单介绍一下函数计算的基本概念,方便大家理解。
函数计算(Function Compute,简称FC),是阿里云提供的Serverless计算服务,用户只需要编写代码,上传到函数计算,配置触发器,函数计算就会根据请求,自动运行代码,自动扩缩容,用户不需要管理服务器,不需要关心运维,只需要为实际运行的时间付费,没有请求的时候不收费。
函数计算的核心概念:
- 函数(Function): 用户编写的代码,是函数计算的基本单元,每个函数完成一个特定的功能。
- 服务(Service): 函数的集合,同一个服务下的函数,可以共享配置,比如环境变量、日志配置、权限配置等。
- 触发器(Trigger): 触发函数运行的事件源,比如HTTP触发器、OSS触发器、定时触发器、日志触发器、表格存储触发器等,当事件发生时,自动触发函数运行。
- 事件(Event): 触发器传递给函数的输入数据,函数根据事件数据,进行处理,返回结果。
- 实例(Instance): 函数运行的环境,函数计算会根据请求量,自动创建和销毁实例,自动扩缩容。
- 冷启动(Cold Start): 函数第一次运行,或者长时间没有请求后,需要创建新的实例,加载代码,初始化运行环境,这个过程叫冷启动,会有一定的延迟。
- 按量付费: 函数计算按实际运行的时间付费,以100ms为单位,没有请求的时候不收费,非常适合低频、突发的场景。
函数计算的优势:
- 免运维: 不需要管理服务器,不需要关心操作系统、补丁、扩容等运维问题,只需要编写代码。
- 自动扩缩容: 根据请求量,自动扩缩容,请求多的时候自动增加实例,请求少的时候自动减少实例,不用担心流量高峰。
- 按量付费: 只需要为实际运行的时间付费,没有请求的时候不收费,成本很低,适合低频、突发的场景。
- 事件驱动: 支持多种触发器,可以很方便地和其他云服务集成,比如OSS、表格存储、日志服务、消息队列等,构建事件驱动的架构。
函数计算的劣势和坑:
- 冷启动延迟: 函数长时间没有请求后,再次请求会有冷启动延迟,影响用户体验。
- 资源限制: 函数有内存、CPU、超时时间、临时存储空间等限制,不适合长时间运行、大内存的任务。
- 调试困难: 函数运行在云端,本地调试不方便,出了问题很难排查。
- 依赖安装: 函数的依赖,需要打包上传,或者在线安装,有些依赖有系统级的依赖,安装很麻烦。
- 无状态: 函数是无状态的,不能在内存中保存数据,需要用外部存储,比如数据库、缓存、对象存储等。
- 网络访问: 函数默认不能访问VPC内的资源,需要配置VPC访问,而且网络访问有一些限制。
了解了基本概念,下面就来讲讲我们踩过的那些坑。
二、我们的使用场景
我们团队主要把函数计算用在以下几个场景:
- 图片处理: 用户上传图片到OSS后,触发函数,自动生成缩略图、水印、不同尺寸的图片,保存到OSS。
- 定时任务: 每天定时运行的任务,比如数据统计、报表生成、数据备份、数据清理等,用定时触发器触发函数运行。
- Webhook处理: 第三方服务的Webhook,比如GitHub的Webhook、支付回调、消息通知等,用HTTP触发器触发函数,处理Webhook请求。
- 数据同步: 不同系统之间的数据同步,比如把MySQL的数据同步到Elasticsearch,把OSS的数据同步到其他存储,用触发器或者定时任务触发函数运行。
- 低频API: 一些低频的、简单的API,比如验证码发送、短信发送、邮件发送等,用函数计算实现,不用专门部署服务,成本低,免运维。
这些场景,都比较适合函数计算,事件驱动,低频,运行时间短,不需要长期运行,用函数计算确实能降低成本,减少运维。但是,在使用的过程中,我们踩了很多坑,下面就来详细讲讲。
三、踩过的坑和解决方案
坑1:冷启动延迟,用户体验差
问题描述: 我们把图片处理的功能迁移到函数计算后,发现用户上传图片后,第一次生成缩略图很慢,要等好几秒,甚至十几秒,用户体验很差。但是后续的请求就很快,几十毫秒就返回了。
经过排查,发现是冷启动的问题。函数计算的实例,在长时间没有请求后,会被回收,再次有请求的时候,需要创建新的实例,加载代码,初始化运行环境,这个过程就是冷启动,会有几秒的延迟。我们的图片处理函数,用了Node.js,依赖比较多,冷启动时间更长,有时候要5-10秒。
冷启动对用户体验影响很大,特别是用户直接感知的功能,比如图片处理、API请求等,用户等好几秒,体验很差。
解决方案:
- 预热函数: 用定时触发器,每隔几分钟(比如5分钟),调用一次函数,保持实例不被回收,避免冷启动。这个方法简单有效,但是会增加一点费用,因为定时调用也会计费,不过费用很低,几乎可以忽略。
- 减少依赖: 优化函数的代码,减少不必要的依赖,只保留需要的依赖,减小代码包的大小,加快冷启动的速度。比如,我们的图片处理函数,原来用了一个很大的图片处理库,后来换成了更轻量的库,冷启动时间减少了一半。
- 选择合适的运行时: 不同的运行时,冷启动时间不一样,比如Python、Node.js的冷启动时间比较短,Java、.NET的冷启动时间比较长。如果对冷启动敏感,可以选择冷启动时间短的运行时。
- 预留实例: 阿里云函数计算提供了预留实例的功能,可以预留一定数量的实例,一直运行,不会被回收,完全避免冷启动。但是预留实例是收费的,即使没有请求也会收费,成本比较高,适合对冷启动要求很高的场景。
- 异步处理: 如果是用户不直接感知的功能,比如图片处理、数据同步等,可以用异步处理,用户上传后立即返回,后台异步处理,用户不需要等待,冷启动的影响就不大了。
我们最终用了预热函数+减少依赖的方案,定时每5分钟调用一次函数,同时优化了代码,减少了依赖,冷启动时间从5-10秒降到了1-2秒,用户体验好了很多,成本也增加得很少。
坑2:内存限制,函数OOM被杀死
问题描述: 我们有一个数据处理的函数,需要处理比较大的数据,有时候会处理几MB甚至几十MB的数据。一开始,我们给函数配置了128MB内存,结果函数经常OOM(Out Of Memory),被系统杀死,返回错误。
一开始我们以为是代码有内存泄漏,优化了很久,还是OOM。后来才发现,函数计算的内存,是函数运行环境+代码+数据的总内存,不是只算代码的内存。128MB内存,运行环境本身就要占几十MB,剩下的内存不多,处理大数据的时候,很容易OOM。
而且,函数计算的CPU是和内存绑定的,内存越大,CPU越多,128MB内存只有0.1核CPU,处理大数据的时候,不仅内存不够,CPU也不够,处理很慢,容易超时。
解决方案:
- 增加内存配置: 根据函数的实际需求,配置合适的内存,不要为了省钱,配置太小的内存。内存大了,CPU也多了,处理速度更快,反而可能更省钱,因为运行时间短了,总费用可能更低。我们把数据处理函数的内存,从128MB增加到了512MB,OOM的问题就解决了,处理速度也快了很多,总费用反而增加得不多。
- 流式处理: 处理大数据的时候,不要一次性把所有数据都加载到内存里,要用流式处理,边读边处理,减少内存占用。比如,处理大文件的时候,用流的方式读取,一行一行处理,不要一次性读取整个文件。
- 分批处理: 如果数据量很大,可以分批处理,每次处理一部分,处理完一批再处理下一批,避免一次性处理太多数据,导致内存不够。
- 使用外部存储: 中间结果不要存在内存里,要存到外部存储,比如OSS、数据库、缓存等,处理的时候再读取,减少内存占用。
- 监控内存使用: 开启函数计算的监控,查看函数的内存使用情况,根据内存使用情况,调整内存配置,避免OOM。
经验:函数计算的内存配置,不要太小,要根据实际需求配置合适的内存,内存大了,CPU也多了,处理速度更快,反而可能更省钱。
坑3:超时限制,函数被强制终止
问题描述: 我们有一个定时任务函数,需要处理大量的数据,运行时间比较长,有时候要跑好几分钟。一开始,我们给函数配置了60秒超时,结果函数跑到60秒的时候,被系统强制终止了,任务没跑完,数据只处理了一半。
后来我们把超时时间改成了300秒(5分钟),结果还是有时候会超时,因为数据量有时候很大,处理时间超过5分钟。我们想把超时时间改得更长,结果发现函数计算的超时时间,最大是600秒(10分钟),不能更长了。
函数计算是为短时间、事件驱动的任务设计的,不适合长时间运行的任务,超时时间最大10分钟,超过10分钟的任务,函数计算不适合。
解决方案:
- 增加超时时间: 如果任务运行时间在10分钟以内,可以把超时时间配置大一点,最大600秒,避免函数被强制终止。
- 优化代码,提高处理速度: 优化代码,提高处理速度,减少运行时间,比如用更高效的算法,批量处理,并发处理等,让任务在超时时间内跑完。
- 分批处理: 如果数据量很大,可以把任务拆分成多个小任务,分批处理,每次处理一部分,处理完一批,再触发下一批,避免单次运行时间太长。比如,用函数计算处理数据,每次处理1000条,处理完后,触发下一个函数,处理下1000条,这样每个函数的运行时间都很短,不会超时。
- 异步任务+状态存储: 对于长时间运行的任务,可以用异步任务的方式,函数接收任务后,立即返回,后台异步处理,把任务状态存到外部存储(比如数据库、Redis),用户可以查询任务状态。但是要注意,函数本身还是有超时限制的,后台处理也不能超过10分钟。
- 使用其他服务: 如果任务运行时间超过10分钟,函数计算就不适合了,应该用其他服务,比如阿里云的批量计算(BatchCompute)、容器服务(Container Service)、ECS等,这些服务没有10分钟的超时限制,适合长时间运行的任务。
我们最终把定时任务拆分成了多个小任务,分批处理,每个函数处理一部分数据,处理完后触发下一个函数,这样每个函数的运行时间都在1分钟以内,不会超时,任务也能完整跑完。
坑4:依赖安装,系统级依赖装不上
问题描述: 我们有一个图片处理函数,需要用一个图片处理的库,这个库依赖了系统级的库,比如libpng、libjpeg、imagemagick等。我们在本地开发的时候,Mac上装了这些依赖,代码运行正常,但是打包上传到函数计算后,运行报错,说找不到这些系统库。
函数计算的运行环境,是一个精简的Linux系统,只安装了一些基本的系统库,很多系统级的库都没有安装,而且用户没有root权限,不能随便安装系统库。一些有系统级依赖的包,在函数计算上运行不了,需要自己把依赖打包进去,或者用其他方式。
这个问题让我熬夜到很晚,一开始不知道怎么回事,本地运行正常,上传到云端就报错,排查了很久,才发现是系统级依赖的问题。
解决方案:
- 使用纯语言实现的库: 尽量选择纯语言实现的库,没有系统级依赖的库,这样打包上传后,就能直接运行,不需要安装系统库。比如,图片处理,我们后来换成了一个纯JavaScript实现的轻量图片处理库,没有系统级依赖,上传后直接运行,很方便。
- 把依赖打包进去: 如果必须用有系统级依赖的库,可以把依赖的.so文件,一起打包到代码包里,然后在代码里设置LDLIBRARYPATH,让程序能找到这些.so文件。这个方法比较麻烦,需要在和函数计算相同的环境下编译依赖,否则可能因为系统版本不兼容,运行不了。
- 使用层(Layer): 阿里云函数计算提供了层(Layer)的功能,可以把公共的依赖,打包成层,多个函数可以共享使用,不需要每个函数都打包依赖。可以把系统级依赖,打包成层,函数引用这个层,就能使用这些依赖。
- 使用自定义运行时: 函数计算支持自定义运行时,可以自己构建运行环境,安装需要的系统库,然后打包成镜像,用自定义运行时运行。这个方法比较灵活,但是也比较复杂,需要熟悉Docker和函数计算的自定义运行时。
- 使用其他云服务: 如果依赖很复杂,函数计算搞不定,可以考虑用其他云服务,比如阿里云的容器服务、ECS等,这些服务可以自由安装依赖,环境更灵活。
我们最终用了纯语言实现的库,替换了原来有系统级依赖的库,问题就解决了,虽然功能少了一点,但是够用了,而且部署简单,维护方便。
坑5:临时存储空间,重启就没了
问题描述: 我们有一个函数,需要下载一些文件,处理后再上传,处理过程中,把临时文件存在了/tmp目录下。一开始运行正常,但是有时候会报错,说找不到临时文件。
经过排查,发现是函数计算的临时存储空间的问题。函数计算的实例,有一个临时存储空间,默认是512MB,挂载在/tmp目录下,函数运行的时候,可以往这个目录写文件。但是,这个临时存储空间,是和实例绑定的,实例被回收后,临时存储空间也会被清空,下次冷启动的时候,是一个新的实例,/tmp目录是空的,之前写的文件就没了。
而且,函数计算是无状态的,不同的请求,可能会路由到不同的实例,每个实例的/tmp目录是独立的,A实例写的文件,B实例是看不到的。所以,不能把需要持久化的数据,存在/tmp目录下。
解决方案:
- 临时文件存在/tmp: 如果只是函数运行过程中的临时文件,处理完就上传或者删除,不需要持久化,可以存在/tmp目录下,没问题,但是要注意,函数运行结束后,这些文件可能就没了,不要指望下次运行还能用到。
- 持久化数据存外部存储: 如果需要持久化的数据,一定要存到外部存储,比如OSS、数据库、缓存、表格存储等,不要存在/tmp目录下,也不要存在内存里,因为函数是无状态的,实例被回收后,数据就没了。
- 注意临时存储空间大小: 函数计算的临时存储空间,默认是512MB,如果需要更大的临时存储空间,可以配置更大的,最大可以到10GB,但是要注意,临时存储空间是和内存绑定的,内存越大,临时存储空间越大。
- 清理临时文件: 函数运行结束后,要清理/tmp目录下的临时文件,避免占用太多空间,导致后续的函数运行没有空间写文件。虽然实例被回收后,临时存储空间会被清空,但是如果实例没有被回收,一直复用,临时文件会越积越多,占满空间。
经验:函数计算是无状态的,不要把需要持久化的数据存在内存或者/tmp目录下,一定要存到外部存储。临时文件可以存在/tmp,但是处理完要清理,不要指望下次还能用。
坑6:网络访问,VPC内资源访问不了
问题描述: 我们有一个函数,需要访问VPC内的RDS数据库,一开始配置好了,但是运行的时候,连接不上数据库,超时报错。我们检查了数据库的连接地址、端口、用户名、密码,都没问题,本地能连上,但是函数计算连不上。
经过排查,发现是函数计算的网络访问的问题。函数计算的函数,默认是运行在函数计算的VPC里的,不能直接访问用户VPC内的资源,比如RDS、Redis、ECS等。如果需要访问VPC内的资源,需要配置函数的VPC访问,把函数配置到用户的VPC里,这样函数才能访问VPC内的资源。
而且,配置VPC访问后,函数就不能访问公网了,如果需要同时访问VPC内资源和公网,需要配置NAT网关,让VPC内的函数能通过NAT网关访问公网。
这个问题也让我排查了很久,一开始不知道函数计算默认不能访问VPC内资源,以为是数据库的问题,查了很久才发现是网络配置的问题。
解决方案:
- 配置VPC访问: 如果函数需要访问VPC内的资源(RDS、Redis、ECS等),需要在函数计算的服务配置里,配置VPC访问,选择对应的VPC、交换机、安全组,这样函数就能访问VPC内的资源了。
- 配置NAT网关访问公网: 配置VPC访问后,函数默认不能访问公网了,如果需要同时访问公网(比如调用第三方API、访问公网地址等),需要在VPC里配置NAT网关,配置SNAT,让VPC内的函数能通过NAT网关访问公网。
- 安全组配置: 配置VPC访问后,要注意安全组的配置,函数的安全组,要允许访问VPC内的资源,比如RDS的安全组,要允许函数的安全组访问对应的端口,否则还是连不上。
- 使用公网地址: 如果VPC内的资源有公网地址,也可以不用配置VPC访问,直接用公网地址访问,但是这样会走公网,速度慢一点,也不太安全,而且RDS的公网地址需要单独开通。
- 使用云服务的内网端点: 阿里云的很多云服务,比如OSS、表格存储、日志服务等,都有内网端点,函数计算可以直接通过内网访问这些服务,不需要配置VPC,速度快,也安全。
我们最终配置了VPC访问,把函数配置到了我们的VPC里,同时配置了NAT网关,让函数既能访问VPC内的RDS,又能访问公网,问题就解决了。
坑7:日志监控,出了问题找不到日志
问题描述: 函数计算的函数,运行在云端,出了问题,需要查看日志排查。一开始,我们不知道函数计算的日志在哪里,出了问题,只能看到函数返回错误,但是看不到详细的错误日志,很难排查。
后来才知道,函数计算的日志,是输出到阿里云日志服务(SLS)的,需要配置日志项目和日志仓库,函数的stdout和stderr,会自动收集到日志服务里。但是,我们一开始没有配置日志服务,所以看不到日志。
而且,函数计算的日志,有延迟,不是实时的,有时候函数运行完了,要等几十秒甚至几分钟,才能在日志服务里看到日志,排查问题的时候很着急。
另外,函数计算的监控,也不够完善,很多指标需要自己配置,出了问题,不能很快定位。
解决方案:
- 配置日志服务: 在函数计算的服务配置里,配置日志项目和日志仓库,把函数的stdout和stderr,输出到日志服务,这样就能查看函数的运行日志了。日志服务支持查询、分析、告警,很方便。
- 在代码里打详细日志: 在函数代码里,要打详细的日志,包括函数的入参、出参、处理过程、错误信息、堆栈等,方便出问题的时候排查。日志要结构化,比如用JSON格式,方便日志服务查询和分析。
- 使用日志服务的查询和告警: 利用日志服务的查询功能,快速查询错误日志,定位问题。配置告警,当函数出现错误,或者错误率超过阈值的时候,及时告警,通知开发人员处理。
- 配置函数计算的监控: 函数计算自带了一些监控指标,比如调用次数、错误率、延迟、内存使用等,可以在函数计算的控制台查看,也可以配置云监控告警,当指标异常的时候,及时告警。
- 使用链路追踪: 如果函数调用了其他服务,可以用阿里云的链路追踪(Tracing Analysis),把函数的调用链路串起来,出了问题,可以快速定位是哪个环节出了问题。
- 本地调试: 出了问题,不要只靠云端日志,可以用函数计算的本地调试工具,在本地运行函数,调试代码,复现问题,本地调试比云端更方便,可以打断点,单步调试,查看变量。
我们最终配置了日志服务,在代码里打了详细的结构化日志,配置了告警,出了问题,能很快在日志服务里查到日志,定位问题,方便了很多。
坑8:调试困难,本地和云端环境不一致
问题描述: 函数计算的函数,运行在云端,本地调试很不方便。一开始,我们在本地写好代码,直接上传到云端测试,出了问题,改代码,再上传,再测试,来回折腾,效率很低,而且云端的日志有延迟,排查问题很麻烦。
后来我们用了函数计算的本地调试工具,在本地运行函数,但是发现本地环境和云端环境不一致,本地运行正常,上传到云端就报错,比如依赖版本不一样,系统库不一样,环境变量不一样等,排查起来很麻烦。
而且,函数计算的触发器,比如OSS触发器、定时触发器、日志触发器等,本地很难模拟,测试触发器的功能,只能上传到云端测试,很不方便。
解决方案:
- 使用Fun工具本地调试: 阿里云提供了Fun(Function Compute CLI)工具,可以在本地运行函数,模拟触发器,调试代码,很方便。Fun工具可以拉取云端的配置,在本地模拟函数计算的运行环境,尽量和云端保持一致。
- 使用Docker模拟云端环境: 函数计算的运行环境,是基于Docker的,可以用函数计算提供的Docker镜像,在本地运行函数,模拟云端环境,这样环境就和云端一致了,避免本地和云端环境不一致的问题。
- 环境变量配置: 把配置信息,比如数据库连接地址、密钥、第三方API地址等,都放在环境变量里,不要硬编码在代码里。本地和云端用不同的环境变量配置,这样代码不用改,只需要改配置,就能在本地和云端运行。
- 依赖版本锁定: 在package.json(Node.js)或者requirements.txt(Python)里,锁定依赖的版本,确保本地和云端的依赖版本一致,避免因为版本不一致导致的问题。
- 单元测试: 写单元测试,把核心逻辑和函数入口分开,核心逻辑可以在本地直接运行,单元测试,不需要依赖函数计算的环境,这样大部分问题,在本地单元测试的时候就能发现,不需要上传到云端测试。
- 分环境测试: 配置开发、测试、生产三个环境,代码先在本地开发调试,然后部署到测试环境测试,测试通过后,再部署到生产环境,避免直接在生产环境测试,影响线上业务。
我们最终用了Fun工具+Docker镜像,在本地模拟云端环境,调试代码,同时写了单元测试,核心逻辑在本地测试,大部分问题在本地就能发现,只有触发器相关的功能,才需要上传到云端测试,效率提高了很多。
坑9:并发限制,突发流量被限流
问题描述: 我们有一个Webhook处理的函数,用HTTP触发器,接收第三方服务的回调。有一次,第三方服务做活动,短时间内发了大量的Webhook请求,我们的函数并发量突然增加,结果很多请求被限流了,返回429错误,Webhook请求丢失了,影响了业务。
经过排查,发现是函数计算的并发限制的问题。函数计算默认有并发限制,每个账号,每个区域,并发实例数默认是100,超过这个并发数,就会被限流,返回429错误。而且,单个函数也有并发限制,默认是100,可以调整,但是不能超过账号的总并发限制。
函数计算的自动扩缩容,是需要时间的,不是瞬间就能扩容的,突发的大流量,可能来不及扩容,就会被限流。而且,函数计算的实例,有最大并发数的限制,默认是单实例单并发,也就是一个实例同时只能处理一个请求,请求多了,就需要创建更多的实例。
解决方案:
- 申请提高并发限制: 如果业务需要更高的并发,可以提交工单,申请提高账号的总并发限制,以及单个函数的并发限制,根据业务的峰值流量,配置足够的并发数,避免被限流。
- 预留实例: 对于有突发流量的函数,可以配置预留实例,预留一定数量的实例,一直运行,随时可以处理请求,不需要冷启动,也不会因为突发流量来不及扩容而被限流。但是预留实例是收费的,即使没有请求也会收费,成本比较高。
- 单实例多并发: 函数计算支持单实例多并发,可以配置一个实例同时处理多个请求,这样可以提高实例的利用率,减少需要的实例数量,应对突发流量。但是要注意,函数代码必须是线程安全的,支持并发处理,否则会有问题。
- 异步处理+消息队列: 对于Webhook、事件处理等场景,可以用异步处理,函数接收请求后,先把消息放到消息队列(比如阿里云的消息队列MNS、Kafka等),立即返回,然后用另一个函数,消费消息队列,异步处理。这样,即使突发流量很大,也不会丢失请求,消息队列会缓存消息,消费函数可以慢慢处理,起到削峰填谷的作用。
- 限流和降级: 在函数入口,做限流和降级,当并发量超过阈值的时候,返回友好的错误,或者降级处理,避免函数被打垮。同时,要通知调用方,进行重试,避免请求丢失。
- 监控和告警: 监控函数的并发量、错误率、限流次数,配置告警,当并发量接近阈值,或者出现限流的时候,及时告警,通知开发人员处理,提前扩容,避免影响业务。
我们最终用了异步处理+消息队列的方案,Webhook函数接收请求后,把消息放到消息队列,立即返回,另一个函数消费消息队列,异步处理,这样即使突发流量很大,也不会丢失请求,也不会被限流,问题就解决了。
坑10:费用问题,账单超出预期
问题描述: 我们一开始用函数计算,觉得很便宜,按量付费,没有请求不收费,成本应该很低。但是用了一段时间后,发现账单超出了预期,比我们预想的贵了不少。
经过分析,发现费用超出预期的原因,主要有以下几个:
- 函数运行时间比预想的长: 有的函数,我们以为运行时间很短,只要几十毫秒,但是实际运行的时候,因为冷启动、依赖加载、数据处理等原因,运行时间要几秒,费用就增加了。
- 内存配置太大: 有的函数,我们为了避免OOM,配置了很大的内存,比如1GB、2GB,但是实际内存使用很少,大部分内存都浪费了,但是费用是按配置的内存算的,不是按实际使用的内存算的,所以费用增加了。
- 调用次数比预想的多: 有的函数,比如定时触发器、OSS触发器,调用次数比我们预想的多,比如OSS触发器,每次上传文件都会触发,包括我们测试的时候上传的文件,也会触发,调用次数多了,费用就增加了。
- 公网流量费用: 函数访问公网,或者函数返回的数据,走公网,会产生公网流量费用,这个费用不便宜,特别是返回的数据量大的时候,费用会很高。
- 日志服务费用: 函数的日志,输出到日志服务,日志服务也是收费的,日志量大的时候,费用也不少。
- 预留实例费用: 为了避免冷启动,我们配置了预留实例,预留实例是按小时收费的,即使没有请求也会收费,24小时运行,一个月下来,费用也不少。
解决方案:
- 优化代码,减少运行时间: 优化函数代码,减少不必要的处理,提高处理速度,减少运行时间。运行时间越短,费用越低,因为函数计算是按运行时间付费的。
- 合理配置内存: 根据函数的实际内存使用情况,配置合适的内存,不要配置太大,也不要太小。内存配置太大,浪费钱;配置太小,容易OOM,而且CPU少,处理慢,运行时间长,反而可能更贵。可以通过监控,查看函数的实际内存使用,调整到合适的大小。
- 减少不必要的调用: 检查触发器的配置,避免不必要的调用。比如,OSS触发器,可以配置前缀和后缀,只处理特定的文件,避免所有文件上传都触发;定时触发器,根据实际需求配置频率,不要太频繁。测试环境的函数,不用的时候可以停掉,避免产生费用。
- 使用内网传输,减少公网流量: 函数访问阿里云的云服务,尽量用内网地址,走内网,不走公网,减少公网流量费用。函数返回的数据,如果量大,可以先存到OSS,返回OSS的地址,让客户端从OSS下载,减少函数的公网流量。
- 优化日志,减少日志量: 合理配置日志级别,生产环境不要打太多debug日志,只打必要的info和error日志,减少日志量,降低日志服务的费用。日志服务可以配置存储时间,不需要永久保存的日志,设置短一点的存储时间,减少存储费用。
- 合理使用预留实例: 预留实例虽然能避免冷启动,但是费用高,要根据实际需求配置,不要配置太多预留实例。只有对冷启动要求很高,而且请求比较稳定的函数,才配置预留实例;请求不稳定,或者对冷启动不敏感的函数,用按量付费的实例就可以了,配合预热函数,成本更低。
- 使用预算和告警: 在阿里云的费用中心,配置预算和告警,当费用达到预算的一定比例的时候,及时告警,通知开发人员,避免费用超出预期。定期查看账单,分析费用构成,找出费用高的地方,优化降低。
我们最终优化了代码,减少了运行时间,合理配置了内存,减少了不必要的调用,用内网传输,优化了日志,合理配置了预留实例,费用就降下来了,在预期范围内。
四、最佳实践
总结一下,使用阿里云函数计算的最佳实践:
- 选择合适的场景: 函数计算适合短时间、事件驱动、低频、突发的场景,比如图片处理、定时任务、Webhook处理、数据同步、低频API等。不适合长时间运行、大内存、高并发、稳定流量的场景,这些场景用容器服务或者ECS更合适。
- 处理冷启动: 用预热函数、减少依赖、选择合适的运行时、预留实例等方式,处理冷启动问题,提高用户体验。
- 合理配置资源: 根据函数的实际需求,配置合适的内存、超时时间、临时存储空间,不要太大,也不要太小,避免浪费或者OOM、超时。
- 无状态设计: 函数是无状态的,不要把需要持久化的数据存在内存或者/tmp目录下,一定要存到外部存储,比如数据库、缓存、对象存储等。
- 错误处理和重试: 函数可能会因为各种原因失败,要做好错误处理,记录详细的错误日志,配置重试机制,避免因为临时故障导致任务失败。但是要注意,重试可能会导致重复执行,函数要保证幂等性。
- 幂等性设计: 函数可能会被重复调用,比如重试、触发器重复触发等,函数要保证幂等性,重复执行不会产生副作用,比如重复插入数据、重复发送消息等。
- 安全配置: 函数的权限,要遵循最小权限原则,只给函数需要的权限,不要给过大的权限。敏感信息,比如密钥、密码等,要放在环境变量或者密钥管理服务里,不要硬编码在代码里,也不要提交到代码仓库。
- 日志和监控: 配置日志服务,在代码里打详细的结构化日志,配置监控和告警,出了问题能快速定位和处理。
- 本地调试和测试: 用Fun工具和Docker,在本地调试函数,写单元测试,大部分问题在本地就能发现,提高开发效率。
- 成本优化: 优化代码,减少运行时间,合理配置内存,减少不必要的调用,用内网传输,优化日志,合理使用预留实例,控制成本,避免账单超出预期。
五、我的感悟
经过这段时间的试用,我对函数计算和Serverless,有了更深刻的认识。
函数计算和Serverless,确实是未来的发展方向,它让开发者不用再关心服务器和运维,只需要专注于业务代码,大大提高了开发效率,降低了运维成本,而且按量付费,成本很低,特别适合创业公司和中小团队,以及低频、突发的业务场景。
但是,函数计算和Serverless,还不够成熟,有很多坑,比如冷启动、资源限制、调试困难、依赖安装、网络访问等,这些问题,需要开发者有一定的经验,才能用好。而且,函数计算不是银弹,不是所有的场景都适合,要根据业务场景,选择合适的技术架构,不要为了追热点而盲目使用。
另外,使用云服务,要注意厂商锁定的问题,函数计算的API和配置,都是阿里云特有的,如果以后要迁移到其他云,或者自建,成本会比较高。所以,在使用的时候,要尽量把业务逻辑和云服务的API解耦,方便以后迁移。
总的来说,函数计算和Serverless,是一个很好的技术,用对了场景,能大大提高效率,降低成本。但是,也要了解它的坑,做好应对,才能用好它,发挥它的优势。
希望我的踩坑经验,能给大家一些参考,让大家在使用函数计算的时候,少踩坑,少熬夜,顺利地把业务跑起来。
写在最后
阿里云函数计算踩坑记:那些让我熬夜的问题。
函数计算和Serverless,是2017年最火的技术之一,被称为"下一代云计算架构",它让开发者不用再关心服务器和运维,只需要专注于业务代码,大大提高了开发效率,降低了运维成本。
但是,函数计算还不够成熟,有很多坑,比如冷启动、资源限制、超时限制、依赖安装、临时存储、网络访问、日志监控、调试困难、并发限制、费用问题等,这些问题,让我踩了很多坑,熬了很多夜。
本文详细分享了我踩过的10个坑,以及每个坑的解决方案,还有函数计算的最佳实践和我的感悟,希望能给正在用或者打算用函数计算的朋友一些参考,少踩坑,少熬夜。
函数计算不是银弹,不是所有的场景都适合,要根据业务场景,选择合适的技术架构,不要为了追热点而盲目使用。但是,用对了场景,函数计算确实能大大提高效率,降低成本,是一个很好的技术。
最后,用一句话结尾:
"新技术有新机会,也有新坑。了解它的优势,也了解它的坑,才能用好它,发挥它的最大价值。愿我们都能跟上技术的发展,用好新技术,为业务创造更大的价值。"
祝大家都能少踩坑,少熬夜,写出稳定、高效、低成本的系统!
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录