Serverless:复杂度没有消失,只是换了地址

2018年前后,Serverless在国内技术圈火了一阵。各大云厂商轮番布道,"按需付费"、"零运维"、"自动扩缩容"——每个口号都精准戳中了团队的痛点。我当时也投入了不少精力研究,毕竟谁不想不用管服务器?

几年过去了,当初那些All in Serverless的团队,相当一部分已经在往回走。不是Serverless不好,而是很多人慢慢意识到:当初以为消灭的复杂度,其实只是搬了个家。

 

那些承诺里没写的小字

 

Serverless的核心承诺有三条,每一条都有对应的现实版本。

"不用管服务器"——开发者只需写业务逻辑,基础设施平台托管。这对前端团队尤其有吸引力,写完函数丢上去就能跑,不用折腾部署和运维。但现实是,你很快会发现自己需要关注函数的内存配置、超时时间、并发限制、冷启动策略、VPC配置、IAM权限……这些不叫"服务器运维",但本质上还是在管基础设施,只是换了个仪表盘。

"按需付费"——代码不运行就不收钱,空转成本消失。但下一句没人告诉你的是:按需付费的反面是成本不可预测。一个函数死循环触发重试,一个爬虫突然疯爬你的API,一个配置错误导致无限调用——这些异常场景下的账单,会让你深切怀念固定成本的日子。

"自动扩缩容"——流量来时自动扩展,退去自动缩容。理论上没问题,但冷启动的延迟意味着"自动扩容"不是瞬间完成的。对异步任务无所谓,对同步请求,用户等不了你的扩容。

三条承诺都成立,但每条都附带条件。可惜技术传播的机制天然倾向于放大收益、缩小说辞——会议演讲不会讲冷启动导致的数据不一致,云厂商的白皮书更不会提成本失控的案例。

 

冷启动:省下的钱,花在了等待上

 

这是Serverless被诟病最多的问题,但很多人低估了它在生产环境中的影响。

函数一段时间没被调用,平台回收运行环境。下次请求到来时需要重新初始化:加载运行时、加载代码、执行初始化逻辑。Java可能要数秒,Node.js通常几百毫秒,Python也不短。而且这不是偶尔发生的——你的业务有波峰波谷,函数就有频繁的冷启动。

几百毫秒听起来不多?在金融场景里很要命。用户打开理财页面,看到的是实时行情和持仓数据。冷启动导致的延迟意味着用户看到的价格可能已经过期了。波动行情下,500毫秒的延迟足以让报价和实际成交价之间产生滑点。不是每秒都在波动,但你不知道哪一秒会波动,这就是问题。

于是很多团队做了各种优化:预热函数、定时心跳保活、设置最小实例数、预留并发。做着做着突然发现——这些操作是不是有点眼熟?为了保证响应速度预留实例,这不就是变相的包月服务器?用Serverless的初衷是"不用管服务器",结果现在要管函数的预热策略、冷启动超时、实例保活。管的东西没变少,只是从"服务器运维"变成了"函数运维"。

更难受的是,这些优化手段本身也有成本。预留实例要额外付费,心跳保活产生不必要的调用次数,而且每个云厂商的优化方式不同——你在AWS上学的那套保活策略,换到阿里云上可能完全不一样。

 

调试炼狱:一次请求穿越15个函数

 

Serverless架构天然倾向把系统拆成大量小函数。一个用户请求从API Gateway进来,可能经过鉴权函数、路由函数、业务逻辑函数、数据聚合函数、后处理函数,最后到达响应函数。每一步都是独立的函数,独立部署、独立扩缩容、独立出错。

这种架构在PPT上很优雅,在生产环境排查问题的时候就是噩梦。

传统的单体应用,设个断点能从头跟到尾。即便是普通的微服务,一个请求经过三五个服务,用分布式追踪也还能理清。但Serverless架构下,一个业务流程可能涉及十几个函数,每个函数的调用链、超时设置、错误处理都各自为战。出了问题,你需要在日志系统里把十几个函数的调用记录拼接起来,像侦探破案一样还原现场。

我见过一个团队专门用Wiki维护了一张"函数调用关系图",人工标注哪个函数调用哪个函数。每次有新人入职,第一件事就是对着这张图理解系统整体。这张图的真实性完全依赖于某个工程师有没有及时更新——传统的微服务依赖图至少可以从代码分析出来,Serverless的事件驱动调用关系,不跑一遍根本不知道谁调了谁。

分布式追踪当然可以解决这个问题。OpenTelemetry在Serverless场景下也能用,但每个函数加追踪探针本身有性能开销和成本,追踪数据的存储和查询又是一笔额外费用。更关键的是,很多团队在上线初期压根没考虑这个问题——Serverless的按需付费模式天然鼓励"先跑起来再说",可观测性这种基础设施级的事情往往被排到后面。等到线上出了事才发现,日志里根本找不到完整的调用链。

这不是Serverless的bug,而是这种架构风格的本性:扁平和分散天然对抗可观测性。当你把系统拆成十几个独立函数的时候,你也把系统的可见性拆成了十几个碎片。

 

成本陷阱:从省心到焦虑

 

"按需付费"被包装成了省钱方案,但它真正的效果是把固定成本变成了变动成本。这个变化的心理影响远大于经济影响。

传统模式下,每月的服务器费用是固定的,预算可控。业务增长需要扩容,提前申请、审批、采购,流程虽然慢但可预期。Serverless模式下,正常流量时可能很便宜,但一旦出现异常——不管是被攻击、爬虫疯爬、还是某个函数死循环触发大量重试——账单会以你想象不到的速度飙升。

更麻烦的是Serverless的成本结构极其碎片化。函数调用费、API Gateway费、存储费、网络传输费、日志存储费、分布式追踪费、消息队列费……这些费用散落在云厂商账单的不同角落。你要搞清楚一个业务的总成本,得做一番会计级别的统计。很多团队在上线初期觉得"好便宜",等到用量上来之后才发现,加起来的总成本比同等规模的传统架构还高。

这还没算隐性成本。Serverless架构下,开发者需要理解冷启动机制、函数生命周期、各服务的集成方式、云厂商特有的配置和限制。这些知识的获取和维护本身就是人力成本,而且这种知识和云厂商深度绑定——你在AWS上积累的Serverless经验,换到阿里云上要重新学一遍。

有句话说得很精准:Serverless不是帮你省钱,是帮你把固定成本变成变动成本,再把变动成本藏到你看不见的角落。对业务端来说,固定成本是可预期的,变动的不可预期成本才是焦虑的来源。

 

状态管理:无状态世界里的状态难题

 

Serverless函数是无状态的——这是基本设定。但几乎所有的业务都需要状态。用户session、缓存数据、业务中间结果、并发锁、事务上下文……这些在传统服务器上理所当然的东西,在Serverless里变得别扭。

你不得不引入外部状态存储:Redis、DynamoDB、S3、数据库。每引入一个,就多一层网络延迟、多一个故障点、多一份成本。更关键的是,状态管理和业务逻辑被物理分离了——写代码时需要在脑子里跨越函数和存储的边界,查数据一致性时需要同时看函数日志和存储日志,同一个逻辑的事务现在分散在多个地方。

金融业务里这个问题尤为突出。一个交易流程可能涉及多个步骤:校验用户、校验余额、扣款、记账、发送通知。在传统架构里,这些步骤可以在一个数据库事务里完成,要么全部成功要么全部回滚。在Serverless架构里,每个步骤是一个独立的函数调用,事务一致性需要用Saga模式或者补偿事务来实现。复杂度不是减少了一点点,而是提升了一个数量级。

有人说用Step Functions可以编排这些流程。确实可以,但Step Functions本身又是一个需要学习、维护、调试的服务。状态机的定义和业务代码分离在不同的文件里,排查问题时要同时看状态机流转日志和函数执行日志,心智负担不轻。而且状态机的每一步流转都有成本——Step Functions的按状态转换次数计费,一个复杂的业务流程跑下来,光状态编排的费用可能就超过了业务函数本身。

本质上,Serverless的无状态设计把状态问题"推"给了外部服务,而不是解决了它。状态还在那里,只是你管理它的方式变得更间接、更分散、也更难以整体把握。

 

供应商锁定:温水里的青蛙

 

所有Serverless讨论里,供应商锁定是被提及最多也最常被忽视的问题。

常被提及,是因为几乎每篇Serverless文章都会提到vendor lock-in风险。常被忽视,是因为大多数团队觉得"我们不会换云厂商",所以这个风险可以往后排。

但供应商锁定不只是"能不能迁移到另一家云"的问题。更深层的影响是,你的技术选型被云厂商的能力边界框死了。

用AWS Lambda,你会自然而然地用API Gateway、DynamoDB、SQS、SNS、Step Functions——这些服务组合起来确实好用,但它们的设计哲学会影响你的架构决策。你倾向于把架构往AWS擅长的方向设计,而不是往业务最合适的方向设计。用阿里云函数计算也一样,你会自然地依赖OSS、Table Store、消息服务,架构慢慢就长成了阿里云的形状。

更隐蔽的是,云厂商的Serverless服务在不断进化,每次进化都在加深你的依赖。你用了Lambda@Edge,就绑定了CloudFront;你用了EventBridge,就绑定了AWS的事件模型。每多一层依赖,迁移成本就翻一番。这不一定是恶意的——云厂商只是在提供更好的服务——但客观效果就是,两年后你想走的时候发现走不了了。

温水煮蛙这个过程最可怕的地方在于:每一步决策都是合理的。选云原生的服务确实比自己搭建更省事,确实更快上线,确实初期成本更低。每个单独的决策都没问题,但它们累积起来的结果是你对单一供应商的依赖越来越深,直到不再有选择权。

 

什么时候Serverless是对的

 

说了这么多,不是给Serverless判死刑。在特定的场景下,Serverless确实是对的工具。

事件驱动的异步任务是最经典的场景。图片处理、消息通知、定时任务、数据ETL——这些任务不需要低延迟,流量模式不固定,Serverless的按需付费和自动扩缩容完美匹配。没有人会在凌晨3点为一个月跑一次的对账任务维护一台7x24小时的服务器。

Webhook处理也是好场景。来自外部系统的回调请求频率不可控,Serverless天然适合这种偶尔来一下的流量。

还有数据管道里的处理节点——接收事件、做轻量转换、输出到下一个环节。每个节点功能单一、无状态、不需要跨步骤的事务协调,服务起来很舒服。

这些场景有一个共同特点:异步、无状态、低延迟容忍、不需要复杂的事务协调。凡是需要同步响应、强一致性、复杂事务协调的场景,Serverless引入的复杂度几乎一定超过它消除的复杂度。

 

复杂度守恒

 

软件工程里有一条不成文的定律:复杂度守恒。你可以把复杂度从一个地方转移到另一个地方,但无法消灭它。

Serverless把基础设施的复杂度转移给了平台,但产生了新的复杂度:冷调试、分布式调试、状态外部化、成本碎片化、供应商依赖。这些复杂度不比管理服务器更少,只是形态不同,而且更隐蔽。服务器挂了你能看到,函数冷启动导致的数据不一致你未必能看到。Nginx配置错了502一眼就知道,十几个函数的事件链路出了问题可能要查半天。

我这些年观察到一个规律:当一项技术声称"消灭"了某个问题的时候,通常它只是把问题推迟或隐藏了。而推迟出现的问题往往比原始问题更难定位、更难修复,因为你对它完全没有心理准备。

Serverless不是银弹,也不是毒药。它是一个有清晰使用边界的工具。问题出在边界被无视的时候——当你拿它处理它不擅长的事情,复杂度的转移不但没减轻负担,反而创造了一类全新的、更难搞定的问题。

下次评估Serverless的时候,不妨问一个问题:我消除的是什么复杂度?我又引入了什么新的复杂度?两相比较,我真的变简单了吗?

如果答案是"我消除了管理服务器的复杂度,但引入了冷启动焦虑、调试炼狱、状态外化、成本不可控和云厂商依赖",那这个选择需要重新审视。

复杂度不会凭空消失。它只是换了地址,等你搬进去之后才收到账单。

You voted 5. Total votes: 10

添加新评论