去年公司要做一个新项目,技术选型的时候,有人提出用Serverless架构。理由很充分:不用管服务器、自动扩缩容、按调用量付费、开发效率高。大家讨论了一下,觉得挺有道理,就决定试试。
我作为这个项目的技术负责人,全程参与了Serverless架构的调研、选型、开发、部署和运维。一年下来,项目上线了,也跑起来了,但整个过程可以说是"从入门到放弃"——不是放弃了Serverless,而是放弃了对Serverless的理想化想象,回归到了理性看待。
这篇文章就来聊聊这段经历,说说Serverless到底好不好用,有哪些坑,什么场景适合用,什么场景不该用。
入门:Serverless听起来很美
刚开始调研Serverless的时候,我是很兴奋的。
Serverless的理念太吸引人了。你只需要写业务逻辑代码,部署到云平台上,平台自动帮你管理服务器、自动扩缩容、按实际调用量付费。不用再关心运维,不用再为流量峰值预留服务器,不用再为闲置的资源付费。开发效率高,运维成本低,这简直是开发者的梦想。
我们调研了市面上的主流Serverless平台:AWS Lambda、阿里云函数计算、腾讯云SCF、Vercel、Cloudflare Workers等等。每个平台都有自己的特点,但核心理念是一样的:事件驱动、按需执行、自动扩缩容。
最后我们选择了阿里云函数计算,因为公司其他业务也在阿里云上,网络打通比较方便,而且函数计算的功能比较完善,支持多种语言、自定义运行时、容器镜像等。
选型之后,我们开始学习Serverless的开发模式。和传统的Web开发不同,Serverless应用是由一个个函数组成的,每个函数响应一个事件(HTTP请求、消息队列消息、定时任务、文件上传等)。函数之间是独立的,可以单独部署、单独扩缩容。
我们还学习了Serverless Framework这个工具,它可以帮你管理函数的部署、配置、依赖,简化了开发流程。还有一些框架比如Midway Serverless、NestJS的Serverless适配器,可以让你用熟悉的框架来开发Serverless应用。
学习曲线不算太陡,大概一两周就能上手。那时候我们觉得,Serverless也没那么难嘛,这个技术选型应该是对的。
第一个坑:冷启动
项目开发了大概一个月,第一个版本上线了。上线之后很快就遇到了第一个问题:冷启动。
什么是冷启动?就是当一个函数很长时间没有被调用的时候,云平台会把它的运行实例销毁掉,释放资源。下一次有请求进来的时候,平台需要重新创建实例、加载代码、初始化运行时,这个过程需要时间,从几百毫秒到几秒不等。在这段时间里,用户的请求会被阻塞,体验很差。
我们的项目是一个内部管理系统,使用频率不高,很多函数一天也就被调用几十次。结果就是,每次用户点一个功能,都要等好几秒才能响应,因为函数冷启动了。用户反馈说"这个系统怎么这么卡",我们解释了半天冷启动的原理,用户还是不满意。
为了解决冷启动问题,我们尝试了各种方法。
第一个方法是预留实例。云平台提供了"预留实例"或"预置并发"的功能,你可以指定一直保持一定数量的实例运行,不会被销毁。这样就没有冷启动了,但代价是你要为这些预留的实例付费,不管有没有调用。我们算了一下,如果所有函数都开预留实例,费用比买一台ECS还贵,Serverless"按调用量付费"的优势就没了。
第二个方法是减小函数体积。冷启动时间和函数的代码体积、依赖数量有很大关系。我们把一些不必要的依赖去掉了,把大的依赖拆成按需加载,把代码压缩了一下。冷启动时间确实缩短了一些,但还是有一秒多。
第三个方法是用更轻量的运行时。Node.js的冷启动比Java快,Python也比较快。我们把一些Java写的函数重写成了Node.js,冷启动又快了一些。但重写的成本也不低。
第四个方法是定时预热。写一个定时任务,每隔几分钟调用一次函数,让它保持"热"的状态。这个方法有点hack,但确实有效。缺点是要为这些预热调用付费,而且如果平台在两次预热之间销毁了实例,还是会有冷启动。
最后我们的方案是:核心函数开预留实例(数量不多,保证最低限度的可用性),非核心函数靠定时预热,同时尽量优化函数体积和启动时间。这样勉强把冷启动的影响控制在了可接受的范围内,但也花了不少精力。
冷启动这个问题,在使用频率高的场景下不明显(因为函数一直是热的),但在使用频率低的场景下非常头疼。这是我们之前没有充分预料到的。
第二个坑:本地开发和调试
Serverless的本地开发体验,说实话,不如传统开发好。
传统开发的时候,你在本地跑一个服务,改了代码刷新一下就能看到效果,调试也很方便,打断点、看日志、查变量,都很顺畅。
但Serverless不一样。函数是部署在云上的,本地没有完整的运行环境。虽然有一些工具可以在本地模拟Serverless环境(比如Serverless Framework的offline插件、各个云厂商的本地模拟器),但这些模拟工具和真实的云环境还是有差异的。
我们遇到的问题包括:本地模拟器不支持某些云服务(比如对象存储触发器、消息队列触发器),本地的函数执行环境和云上的不完全一致(比如操作系统、库版本),本地调试的时候看不到云上的日志和监控数据。
最痛苦的是调试。在本地能跑通的代码,部署到云上可能出问题;在云上出了问题,又没法像本地那样方便地打断点调试。只能靠打日志、看日志来排查问题,效率很低。
后来我们摸索出了一套开发流程:先在本地用传统的Web框架(比如Express)开发和调试业务逻辑,确保逻辑正确,然后再把它包装成Serverless函数部署到云上。这样大部分调试工作可以在本地完成,云上只需要做集成测试和验证。
但这种方式也有问题:本地的Express和云上的Serverless函数的请求/响应格式不完全一样,需要做一层适配。而且有些云平台特有的功能(比如上下文对象、触发器事件)在本地模拟不了,只能在云上测试。
另外,团队成员的开发环境一致性也是个问题。每个人本地的Node.js版本、依赖版本、操作系统可能不一样,导致"在我本地是好的"这种情况经常出现。我们后来用了Docker来统一开发环境,才解决了这个问题。
第三个坑:监控和排障
Serverless应用的监控和排障,比传统应用复杂很多。
传统应用是一个长运行的进程,你可以看它的CPU、内存、网络、日志,出了问题可以登录服务器排查。但Serverless应用是由很多短命的函数实例组成的,实例是动态创建和销毁的,你没法登录到某个实例上去排查。
云平台通常会提供一些基础的监控指标,比如调用次数、调用时长、错误率、并发数等。也会提供日志查询功能,可以按函数、时间范围来搜索日志。
但这些基础的监控和日志功能,对于复杂的排障来说是不够的。
第一个问题是分布式追踪。一个用户请求可能会触发多个函数,函数之间可能通过消息队列、HTTP调用等方式交互。出了问题的时候,你需要知道这个请求经过了哪些函数、每个函数花了多长时间、哪里出了错。这就需要分布式追踪。虽然云平台也提供了链路追踪的功能,但配置起来比较麻烦,而且不同函数之间的链路上下文传递需要自己处理。
第二个问题是日志的结构化和可查询性。函数的日志默认是按时间顺序输出的文本,搜索功能比较基础。如果你想按某个字段(比如用户ID、请求ID)来过滤日志,或者做一些聚合分析,就需要把日志采集到专门的日志系统(比如ELK、Loki)里。这又增加了一套基础设施的运维成本。
第三个问题是性能分析。当一个函数执行慢的时候,你需要知道时间花在了哪里——是数据库查询慢,还是外部API调用慢,还是代码本身的逻辑慢。传统应用可以用APM工具(比如New Relic、SkyWalking)来做性能分析,但Serverless函数的生命周期太短,APM工具的agent启动和数据上报都有挑战。我们试过几个支持Serverless的APM工具,但效果都不太理想。
第四个问题是告警。云平台的告警功能比较基础,只能按几个固定的指标来设置。如果你想设置更复杂的告警规则(比如错误率在5分钟内超过阈值且调用量大于某个值),就需要自己搭建监控告警系统。
为了解决这些问题,我们花了不少时间搭建了一套监控体系:把函数日志采集到ELK,用Prometheus+Grafana做指标监控,用Jaeger做分布式追踪,用Alertmanager做告警。这套体系搭起来之后,排障效率提升了很多,但维护这套体系本身也需要精力。
第四个坑:成本核算
Serverless宣传的一大优势是"按调用量付费,成本低"。但实际用下来,我们发现成本核算比想象中复杂。
Serverless的计费通常包括几个部分:函数调用次数、函数执行时长(按毫秒计费)、流量费用、以及使用的其他云服务的费用(数据库、对象存储、消息队列等)。
在使用量小的时候,Serverless确实很便宜,甚至可能在免费额度内。但当使用量增长到一定程度之后,Serverless的成本可能会超过传统的服务器方案。
我们做过一个测算:对于一个持续高负载的服务(比如QPS稳定在100以上),用Serverless的月费用大概是用同等配置ECS的1.5到2倍。因为Serverless的单价(按毫秒计算)其实是包含了平台的管理成本和利润的,比自己买服务器贵。
Serverless成本低的前提是"调用量有波动、有很多空闲时间"。比如一个API,白天QPS 100,晚上QPS 1,用Serverless就很划算,因为晚上几乎不花钱。但如果是一个24小时稳定高负载的服务,用Serverless就不划算了。
还有一个容易被忽略的成本是"隐藏成本"。比如冷启动导致的用户体验下降和用户流失、开发调试效率降低带来的人力成本、监控运维体系的搭建和维护成本、厂商锁定带来的迁移成本。这些成本虽然不是直接的云费用,但对项目的影响很大。
我们项目后来做了一次成本分析,发现Serverless的实际总成本(包括云费用和人力成本)并不比传统方案低。当然,这和我们的项目特点(内部系统、使用频率不高、对响应时间敏感)也有关系。
第五个坑:厂商锁定
Serverless的厂商锁定问题,是我们之前有所预料但没有充分重视的。
每个云厂商的Serverless产品都有自己的API、触发器类型、运行时支持、配置方式。你在阿里云函数计算上写的代码,不能直接部署到AWS Lambda上,需要做不少改造。
我们项目用了一些阿里云特有的功能,比如函数计算的自定义运行时、和阿里云其他服务(OSS、RDS、MNS)的集成触发器。这些功能用起来很方便,但也把我们和阿里云绑定了。如果以后想迁移到其他云,或者想做多云部署,成本会很高。
为了降低厂商锁定的风险,我们做了一些努力:用Serverless Framework来管理部署(它支持多个云厂商),把业务逻辑和云平台特定的代码分开,用抽象层来封装云服务的调用。但即使这样,迁移的成本还是不低,因为不同云厂商的触发器模型、权限模型、配置方式都有差异。
厂商锁定不是不能接受,但在选型的时候应该有清醒的认识。如果你确定会长期用某一家云,厂商锁定不是大问题。但如果有多云或迁移的需求,就要慎重考虑Serverless的选型,或者采用更开放的方案(比如基于Kubernetes的Serverless框架Knative、或者用容器化的方式部署函数)。
我的结论:Serverless适合什么场景
说了这么多坑,你可能觉得我对Serverless很失望。其实不是的。Serverless有它独特的价值,只是它不是银弹,不是所有场景都适合。
根据我们的经验,Serverless特别适合以下场景:
第一,事件驱动的异步任务。比如图片处理、文件转码、数据ETL、消息消费、定时任务。这些任务对响应时间不敏感,调用量有波动,用Serverless很合适。函数被事件触发,执行完就销毁,不需要一直运行,成本低,运维简单。
第二,流量波动大的API。比如营销活动的接口、秒杀接口、突发事件的接口。这些接口平时调用量很小,但高峰期可能暴涨。Serverless的自动扩缩容可以很好地应对这种场景,而且平时成本很低。
第三,小型工具和内部服务。比如Webhook处理、聊天机器人、数据报表生成、内部小工具。这些服务通常调用量不大,对可用性要求不是特别高,用Serverless可以快速开发上线,成本也低。
第四,原型验证和MVP。当你想快速验证一个想法的时候,用Serverless可以在几天内搭出一个可用的原型,不需要关心基础设施。验证通过之后再决定是否用传统架构重构。
Serverless不太适合的场景:
第一,持续高负载的核心服务。如果一个服务24小时都在高负载运行,用Serverless的成本会比传统服务器高,而且冷启动不是问题(因为一直是热的),Serverless的优势不明显。
第二,对响应时间极度敏感的服务。比如支付接口、实时通信、游戏后端。冷启动的延迟可能会影响用户体验,即使开了预留实例,成本也会很高。
第三,长连接和有状态的服务。Serverless函数是无状态的、短命的,不适合做WebSocket、长轮询、需要保持连接的服务。虽然有些平台支持了WebSocket,但体验和成本都不如传统方案。
第四,复杂的单体应用。如果你的应用是一个复杂的单体,拆成很多函数之后管理成本会很高,函数之间的调用和调试都很麻烦。这种场景用传统的容器化部署更合适。
我们项目的最终选择
说了这么多,我们项目最后怎么样了呢?
项目上线运行了半年之后,我们做了一次复盘。Serverless架构确实带来了一些好处:开发速度快、运维简单、在使用量小的时候成本低。但也带来了不少问题:冷启动影响体验、监控排障复杂、成本在增长后不占优势、厂商锁定。
最后我们决定:核心的Web服务迁移到容器化部署(用阿里云的ACK,Kubernetes托管服务),保留一些异步任务和定时任务用Serverless。这样既发挥了Serverless在异步任务场景下的优势,又避免了它在Web服务场景下的不足。
迁移的过程花了大概一个月,主要是把函数改造成传统的Web服务,调整部署流程和监控体系。迁移之后,用户体验明显改善了(没有冷启动了),运维也更熟悉了(K8s是团队已经掌握的技术),成本也有所下降。
这个"从入门到放弃"的经历,让我对Serverless有了更理性的认识。它不是什么革命性的技术,也不是什么骗局,它就是一种架构模式,有它的适用场景和局限性。作为技术人,我们要做的不是盲目跟风,也不是一概否定,而是根据项目的实际情况,选择最合适的技术方案。
写在最后
Serverless从入门到放弃,我经历了什么?
我经历了最初的兴奋——不用管服务器、自动扩缩容、按调用付费,太美好了;经历了冷启动的痛苦——用户等几秒才响应,各种优化手段都用上了;经历了本地开发的不便——模拟环境和真实环境有差异,调试效率低;经历了监控排障的复杂——搭了一套监控体系才勉强够用;经历了成本核算的意外——用量增长后成本并不低;也经历了厂商锁定的无奈——迁移成本高。
最后我选择了"放弃"——不是放弃Serverless,而是放弃了"Serverless是银弹"的幻想,回归到理性的技术选型。Serverless在异步任务、波动流量、小型工具、原型验证这些场景下依然是很好的选择,但在核心Web服务、高负载、低延迟的场景下,传统架构可能更合适。
技术的世界里没有银弹,只有权衡。每一种技术都有它的优势和局限,重要的是理解它们,然后在合适的场景下用合适的技术。
这就是我用Serverless一年多来最大的收获。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录