简介:围绕Serverless技术开发实战,这份PPT以分布式Puppeteer网页截图服务为贯穿案例,系统讲解函数计算的核心原理、实时弹性伸缩机制以及“无需管理基础设施”的免运维优势,定位为面向云端开发者与架构师的解决方案型学习材料。内容依次涵盖函数计算概述、Web应用迁移体验、部署Puppeteer截图服务,并延伸至Rendertron搭建Headless Chrome渲染方案,其中穿插yarn dev、fun deploy等常用命令,清晰呈现从传统部署到Serverless平台的迁移路径与要点,尤其适合需要处理JavaScript渲染依赖、SEO优化或移动端截图预览场景的团队。包体为单个PPTX文件,大小约3.42MB,文件数量精简但知识密度较高,既可用于个人快速学习,也适合团队内部技术分享。已有152人学习,可借助PPT了解如何在函数计算上按需运行浏览器截图任务,理解按实际调用计费的成本模型,并结合实时弹性伸缩能力实现资源利用与高可用的平衡,从而掌握低成本、高弹性的服务部署思路,为后续实践Serverless架构提供扎实参考。
1. 为什么都在聊Serverless:运维焦虑和成本焦虑的另一种解法
三年前我为公司部署一个定时报表任务,申请了一台4核8G的服务器。任务每天跑20分钟,剩下23小时40分钟机器都在闲置,但账单一分不少。后来换成Serverless部署,同样的任务改成事件触发,月底成本降到原来的零头,运维侧几乎不再为它熬夜。这件事让我真正意识到,Serverless并不是什么云端黑科技,而是把「为闲置付费」变成「为使用付费」的算力分配方式。
这个标题指向的开发实战,核心就是解决两件事:第一,怎么把业务从传统服务器迁移到Serverless架构;第二,迁移之后怎么保证线上稳定、账单可控、排查不抓瞎。适合正在被服务器运维缠住、或者想在新项目里直接采用Serverless部署的团队。本文不讲空泛的架构理念,直接拆解选型依据、部署路径、关键参数和线上踩坑记录,让新手能照做,让熟手能避开我走过的弯路。
2. Serverless选型前的四个问题:搞懂它再动手,比急着部署更重要
很多团队一听到Serverless就急着把业务往函数计算里塞,结果遇到冷启动超时、连接池耗尽、账单翻倍,又骂着迁回去。选型阶段花两个小时想清楚边界,比上线后花两天回滚划算。
2.1 无服务器不等于没有服务器:架构边界要画清楚
Serverless的命名很容易让人误以为物理机消失了。实际上服务器还在,只是它的生命周期被平台接管,开发者不再关心机器在哪、配置多少、什么时候扩容。你交付的是一段函数代码,平台负责拉起运行环境、执行代码、回收资源。
这个特性决定了架构的边界:有状态的东西不适合进来。Session、本地文件缓存、内存队列、长连接,这些依赖「机器记忆」的业务放到Serverless里,会反复撞墙。常见做法是把状态外置到Redis或数据库,把函数设计成无状态执行单元。函数只做三件事——接收事件、处理数据、返回结果,其他一概不碰。
这里要区分两个概念:函数计算和Serverless容器。函数计算粒度更细,按调用次数计费,适合API后端、消息处理、定时任务;Serverless容器则承载体量更大的服务,适合微服务迁移。标题里的开发实战,我默认以函数计算为主线,因为它是入门成本和返工风险最低的切入点。
2.2 冷启动、计费粒度、并发上限:三个决定成本的底层概念
冷启动是函数实例从零到可用的过程。平台需要下载代码包、拉起运行环境、初始化运行时,这个耗时通常在几百毫秒到几秒之间。用户的第一个请求往往要承受这个延迟,这是Serverless最被诟病的地方,也是选型时必须想清楚的硬约束。
计费粒度决定了你的账单长什么样。传统服务器按小时计费,Serverless按调用次数加资源使用时长计费,资源时长精确到毫秒级别。这意味着一个每天被调用10万次、平均执行50毫秒的函数,和一个每天被调用100次、平均执行5秒的函数,账单逻辑完全不同。前者适合Serverless,后者要考虑常驻实例。
并发上限是压垮系统的暗坑。每个函数实例能同时处理的请求数是有限的,超出部分要么排队要么报错。很多团队在测试环境跑通就上线,结果大促流量一来,平台疯狂扩容,数据库连接池被瞬间打满,整个链路雪崩。选型阶段就要确定:你的峰值QPS是多少,单实例并发是多少,允许的最大实例数是多少。
2.3 什么业务适合Serverless,什么业务该绕开
适合的业务有一个共性:流量呈脉冲状,且对冷启动容忍度较高。我整理过一张清单,真实项目里直接照着对照即可:
- API网关后端:请求天然无状态,弹性扩缩容是刚需
- 定时任务与批处理:短时执行完就释放,不存在闲置成本
- 消息队列消费者:事件驱动模型与Serverless天然契合
- 图片处理、视频转码:单次任务CPU密集,并发不高但突发性强
- 运维自动化脚本:执行频率低、逻辑独立,完全没必要养一台常驻机器
不适合的也列清楚,别硬上。WebSocket长连接服务、实时游戏对战、需要GPU训练的AI任务、强状态业务(购物车、在线编辑),这些在Serverless上要么实现成本极高,要么性能不达标。另外一个特殊情况:当你的业务已经是全天候均匀流量时,Serverless省下的运维成本可能被账单差异抵消,这时候用容器服务反而更划算。
2.4 一份可以照着勾的选型对照表
表格不是用来收藏的,是动手前逐条打勾的。能打勾超过六条,就可以推进Serverless部署;少于四条,建议重新评估。
| 评估维度 | 关键问题 | 适合Serverless的答案 |
|---|---|---|
| 流量模式 | 流量是否集中在特定时段? | 是,有明显的波峰波谷 |
| 请求时长 | 单次请求平均执行时间多少? | 小于5秒,最好小于1秒 |
| 状态依赖 | 业务是否依赖本地内存/磁盘? | 不依赖,状态都在外部存储 |
| 冷启动容忍 | 用户能接受偶尔几百毫秒的首次延迟吗? | 能,或通过预置实例解决 |
| 团队运维力 | 是否有人力维护服务器和K8s集群? | 没有,希望平台托管 |
| 成本模型 | 现有服务器利用率是否很低? | 是,日常CPU使用率低于10% |
| 事件源类型 | 业务是否由API、消息、定时器触发? | 是,符合事件驱动模型 |
| 迁移复杂度 | 现有代码依赖系统本地环境吗? | 无特殊依赖,或能用容器镜像打包 |
3. 第一个Serverless服务怎么落地:三条部署路径与校验清单
选型通过之后就是动手。这一章给出三条可执行的部署路径,按团队情况任选其一。三条路径不是互斥关系,很多团队第一阶段用控制台,成熟后切到命令行或容器镜像。
3.1 控制台手动创建:适合第一次跑通全流程
第一次接触Serverless,别急着写脚本。打开云厂商的函数计算控制台,选择空白模板或HTTP函数模板,手动创建一个Hello World函数。这个过程花五分钟,但能让你直观理解函数的三要素:触发方式、运行时、入口方法。
创建页面通常会让你配置四个东西:函数名称、运行时环境(Python/Node.js/Java/Go等)、内存大小、超时时间。第一次演练就选最小的内存配置,超时时间设3秒。创建完点击测试按钮,平台会模拟一次事件调用并返回执行结果。这里注意看两个输出:执行日志和返回内容。日志里能看到实例启动耗时和实际执行耗时,这两个数据就是后续调优的依据。
控制台路径的意义不在于生产可用,而是建立心智模型。很多教程直接教写代码,反而让新手忽略了一个基本认知:Serverless函数的运行环境是平台管理的,你看到的控制台页面就是整个运行时的全部,后续所有自动化操作都是在模拟你在页面上做的这些动作。
3.2 命令行CLI部署:把环境写进代码,环境差异交给平台
当函数数量超过三个,控制台的操作效率就跟不上了。这时候迁到命令行工具。主流云厂商都提供各自的CLI,核心流程大同小异:初始化项目、编写函数代码、配置部署参数、执行部署命令。
以常见的Serverless CLI工具为例,部署一个Python函数的典型命令如下:
# 初始化项目目录,--template指定运行时模板 serverless init --template python3 --name order-handler # 进入项目目录 cd order-handler # 部署函数,--memory指定内存MB,--timeout指定超时秒数 serverless deploy --memory 512 --timeout 10 --region cn-north-1命令背后的逻辑要清楚。init命令拉取的是一个标准项目骨架,里面包含函数入口文件、依赖声明文件和资源配置文件。deploy命令做的事情是:把本地代码打包上传到平台、根据配置创建或更新函数、绑定触发器。--memory和--timeout是部署时写入函数配置的,之后可以在控制台修改,但推荐把配置固化在文件里,这样每次部署都是可预期的。
参数说明:内存配置直接决定CPU分配和计费单价,512M是通用起步值,适合大部分IO密集型业务;--timeout是对单次请求执行时长的硬上限,超过就强制终止,10秒对HTTP请求偏宽松,对消息处理差不多;--region要按业务所在区域选择,跨区域调用会增加固定延迟,这点常被忽略。
部署完成后,CLI会输出一个函数URL。拿着这个URL用curl测一下:
curl -X POST https://your-function-url/execute \ -H "Content-Type: application/json" \ -d '{"orderId": "10001"}'正常返回JSON结果就说明链路通了。这里要强调一个习惯:每次部署前把本地依赖安装好,很多函数部署失败都源自依赖没打进去。
3.3 容器镜像托管:解决依赖难缠时的最后一公里
Python项目里native依赖、Java项目的JAR包冲突、Node.js项目的node_modules体积,经常导致函数代码包超过平台限制或部署后启动失败。这时候容器镜像托管是后悔药——把你熟悉的Dockerfile写出来,构建成镜像,平台直接拉取运行。Serverless容器与自建K8s的区别在于:你不用管节点池、不用管负载均衡,平台根据请求自动拉起和销毁实例。
具体做法是,在项目根目录写Dockerfile:
# 基于官方运行时镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 复制依赖声明文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple # 复制业务代码 COPY . . # 声明服务端口 EXPOSE 8080 # 启动入口 CMD ["python", "app.py"]构建镜像并推送到镜像仓库,然后在Serverless控制台创建函数时选择「容器镜像」方式,填上镜像地址和启动命令。平台对镜像是按需拉取的,但首次拉取耗时取决于镜像大小,所以镜像要精简。常见做法是用slim基础镜像、分阶段构建把构建依赖和运行依赖分开、清理pip缓存,把镜像压在200M以内。
这条路径适合两类场景:一类是函数依赖了特定版本的底层库,比如OpenCV、FFmpeg;另一类是团队已有成熟的容器化交付流程,不想为Serverless单独改造。代价是镜像冷启动比代码包方式更慢,需要通过预置实例或最小化镜像来缓解。
3.4 部署之后先别急着写业务:五步校验清单
很多项目上线后出问题,不是因为代码bug,而是部署配置与预期不符。我养成一个习惯,任何函数部署完先跑这套校验清单:
- 第一步,看日志:控制台或CLI查看最近一次调用的日志,确认实例启动耗时和执行耗时,冷启动是否在容忍范围内
- 第二步,看返回头:用curl -I访问函数URL,检查HTTP状态码和响应头里的Server字段,确认请求确实打到了函数计算而不是其他网关
- 第三步,改配置验证:把内存调大一档再调用一次,观察执行时间的变化,验证资源配置和性能的关系
- 第四步,测超时行为:制造一个必然超时的请求,确认平台返回超时错误、并查看日志中是否记录强制终止
- 第五步,看监控图:进入函数监控页面,确认调用次数、错误次数、平均耗时三个指标都在正常范围
这套清单跑完,函数才算真正「活」了。很多人跳过前三步直接写业务逻辑,结果线上环境变量与本地不一致,排查到半夜,最后发现是平台默认的环境变量覆盖了业务配置。
4. 内存、并发与超时:线上表现靠这三个参数说话
Serverless部署最核心的策略都集中在三个参数上:内存、并发度、超时时间。这三个参数直接决定成本、性能和稳定性,也是线上调优的主战场。
4.1 内存与CPU绑定:选配置其实在选账单
平台的内存和CPU是线性绑定的,内存配得越高,CPU份额越强。例如一个函数配128M内存和配512M内存,后者的CPU算力往往翻倍甚至更高。这意味着很多执行的瓶颈可以用内存配置来调节,而不是只盯着超时时间。
这里有一个常见的账单误区:以为把内存配小就省钱。账要这样算——假设函数每次执行需要100ms,配128M时因为CPU弱要跑200ms,而配512M时跑100ms。后者的单位资源单价贵约4倍,但执行时间减半,总价可能持平甚至更便宜。所以内存选择要基于函数类型:IO密集型的配小内存足够,CPU密集型的配大内存反而省钱。
实际调参的方法是压测,而不是拍脑袋。先用128M部署,用压测工具发100个并发请求,看平均耗时和错误率。如果失败率高且CPU资源已经跑满,说明内存给少了,翻倍配置再测。如果单次执行耗时大于300ms,也要考虑升配。这个过程的产出是一张「内存-耗时-成本」对照表,后续每次业务量变化都能快速决定要不要调。
4.2 并发度与实例数上限:成本黑洞最容易藏在这里
函数计算的并发模型是:每个实例同时处理一个或几个请求(取决于运行时),并发请求数超过单实例能力时,平台会创建新实例,直到达到你设置的实例数上限。这个上限如果没设置,默认值可能很高,流量突增时账单会失控。
成本黑洞的典型场景是这样的:业务峰值QPS只有200,但因为一段循环逻辑没优化,单请求耗时2秒,单实例并发为1,平台就需要拉起200个实例。按每个实例512M计价,这一小时的费用是账面数字的好几倍。解决办法有两个方向,一个是优化代码降低耗时,另一个是显式设置实例数上限,比如最多20个实例,多余请求排队等待。
设置排队要谨慎。有些平台对超时请求直接丢弃,不会帮你排队,用户侧表现为请求失败。所以要配合消息队列使用:先把请求写入队列,函数从队列拉取消费,削峰填谷。这也是Serverless部署里最推荐的生产模式——异步化改造。同步调用虽然开发简单,但扛不住流量尖峰。
4.3 超时时间与重试策略:Serverless下的失败处理逻辑
传统服务器里,接口跑太久只是慢,不会被杀掉。函数计算里超时是硬约束,时间一到平台立刻终止执行,而且可能已经释放资源。这就带来一个诡异现象:函数日志里看不到业务错误,执行记录却显示失败,因为进程是被外部杀掉的。
超时时间设置没有标准答案,要区分同步链路和异步链路。同步API网关触发的函数,建议15秒以内,尽量把耗时压在500毫秒内,超时的请求让客户端重试。异步消息触发的函数,可以放宽到5分钟,因为消息还在队列里,函数被杀死后可以重新投递。定时任务类函数最宽松,常见做法是设10到15分钟,给足跑批时间。
重试策略是另一个容易翻车的点。平台默认会对失败调用进行重试,但重试是叠加的——你的函数处理消息时调用了下游API,下游超时了,平台重试你的函数,函数又调下游API,下游还没恢复,又来一次。这是典型的重试风暴。解决思路是:函数内部不设置无限重试,下游调用设置快速失败;同时开启平台的重试退避策略,让多次重试之间有时间间隔。
4.4 中小团队起步参数速查表
这是一份经过真实项目验证的起步配置,适合中小团队在低并发场景下快速上线,后续按压测结果调整。
| 函数类型 | 内存配置 | 超时时间 | 实例数上限 | 触发方式 |
|---|---|---|---|---|
| HTTP API函数 | 512M | 10秒 | 20 | API网关 |
| 消息队列消费函数 | 256M | 3分钟 | 10 | 队列触发器 |
| 定时任务函数 | 1024M | 15分钟 | 5 | 定时触发器 |
| 图片处理函数 | 1024M | 30秒 | 10 | 对象存储触发器 |
| 轻量工具函数 | 128M | 5秒 | 2 | 手动/CLI调用 |
这个表格的价值是消除「第一次配多少」的选择恐惧。后续调优的原则是:先保证功能正确,再调性能,最后优化成本。很多人一上来就按最大配置跑,账单出来才后悔没看参数表。
5. Serverless部署避坑指南:五条线上事故换来的经验
以下每一条都是实际踩过的坑。写成「现象-原因-解决」的格式,方便出了类似问题时对号入座。这不是什么玄学,都是可以用日志和监控确认的。
5.1 现象:函数返回成功,数据却丢了
线上API返回200,前端也提示操作成功,但后台数据库查不到这条记录。排查发现日志里函数执行正常,没有异常堆栈,返回结果也是一段符合预期的JSON。最后定位到问题出在异步调用:代码里调用了另一个函数,采用的是异步方式,主函数不等子函数执行完就返回了。子函数执行失败时,主函数感知不到,所以对外表现是成功的。
原因是对事件驱动的理解有偏差。异步调用不比同步调用可靠,它只负责把事件投递给目标函数,不保证目标函数执行成功。解决方式是在异步调用代码里补充回调逻辑,或者把主函数的返回值改为依赖子函数的执行结果。最稳妥的方案是把这种依赖关系从代码层面改造成消息队列:主函数把任务写入队列,子函数消费完成后再写一条结果记录,主函数通过查询结果记录判断成功与否。
5.2 现象:日志全丢了,排查无从下手
某次线上故障处理,打开日志平台查函数日志,发现只看到最近几条,更早的全是空白。反复确认不是权限问题,日志确实没有采集到。最后发现是日志平台的采集配置只覆盖了默认日志流,函数代码里往标准输出打印的内容没有被收集,因为运行时环境配置的日志项目ID和采集器不一致。
原因是在创建函数时选了默认日志配置,但函数代码里通过第三方日志库写入的日志文件不在标准输出里。解决方式是调整日志输出策略,统一让所有日志走标准输出,让平台日志系统接管采集,而不是自己写文件。如果框架强制写文件,需要在函数配置里加上日志挂载路径,把日志目录指向平台可采集的位置。另一个常见做法是在代码入口处加一个logging配置,强制所有logger输出到stdout。
5.3 现象:数据库连接被函数实例耗光
这是Serverless最典型的翻车现场。函数每次调用都新建数据库连接,没有使用连接池,或者连接池设在了全局变量里但实例频繁被销毁重建,导致连接无法复用。线上表现为数据库实例的连接数达到上限,新的函数调用全部报too many connections。
原因在于传统服务器的连接池是长生命周期的,函数计算的实例生命周期不确定,空闲时会被平台回收。如果连接池只存在实例内,每次冷启动都会重建。解决方式是使用外部连接池服务,或选择云数据库的Serverless版本支持自动扩缩连接数。代码层面的缓解方案是设置合理的连接空闲回收时间,确保实例销毁前释放连接;但这治标不治本,流量大了还是会把数据库打满。根本解法是控制函数实例并发数,让数据库连接数等于实例数上限乘以每实例连接数。
5.4 现象:定时任务同一时刻触发了多次
监控发现定时任务在某个整点被触发了六次,业务数据被重复处理。排查发现函数绑定了多个触发器:一个是控制台配置的定时触发,一个是代码里通过API动态注册的定时触发,还有一个是项目初始化脚本里重复执行注册操作导致的重复规则。
原因是触发器注册操作没有被设计成幂等。同一份部署脚本跑了多次,平台上的定时规则就存在多个,都在同一个时间点触发同一个函数。解决方式是部署前先检查是否已存在同名的定时触发器,存在则先删除再创建。另一个思路是把定时任务的幂等性放在业务代码里:用Redis setnx加分布式锁,锁的key携带任务周期,只有抢到锁的实例才执行任务,锁过期时间要大于任务最长执行时间。
5.5 现象:代码本地正常,上云就超时
本地跑函数一切正常,输出秒回;部署到Serverless平台后,常常等到超时时间耗尽才返回。查日志发现耗时全花在了一个下游HTTP调用上,本地访问这个下游服务只要50毫秒,线上访问需要3秒以上。
原因是函数所在的环境网络路由与本地不同,可能访问公网需要经过NAT网关,或者访问同区域内部服务时走了公网出网链路。解决方式是在函数的VPC配置里,把目标服务的内网地址加入VPC内访问,函数直接通过内网IP调用,绕开公网路由。另一个隐蔽原因是DNS解析慢:函数实例里访问外部服务时,DNS解析耗时远高于本地,因为平台默认的DNS配置不适合高频调用,需要在代码里使用连接复用和IP直连策略。
6. 把排查效率提上去:我的调试验证三板斧
Serverless部署最痛苦的不是写代码,而是出了问题不知道从哪里查。传统服务器的ssh登录进去看,函数计算只能靠日志和链路追踪。我用了很长时间才形成一套适合自己的排查方法论,分享出来供参考。
第一板斧:结构化日志替代print。早期调试函数习惯写print,日志平台采集到的是一堆没有上下文的字符串。后来统一改为JSON格式输出,每次请求把所有关键信息打包:requestId、函数名、业务参数、耗时、下游调用结果。这样日志平台检索时可以直接按requestId过滤整条链路。配合日志平台的键值索引,查询效率提升几个量级。这项改造需要写一个日志工具类,在函数入口处统一初始化,业务代码全部通过工具类打日志,禁止裸用print。
第二板斧:本地模拟网关事件,把线上问题搬回笔记本。每次线上出问题,我第一件事不是去改代码,而是把线上触发的原始事件下载下来,在本地用同样的数据复现一遍。事件源不同,函数的入参格式差异很大,API网关、消息队列、定时器触发的事件结构各有各的格式。本地写一个测试脚本,读取线上事件,调用函数本地入口,对比输出和线上日志,基本能定位八成的逻辑问题。剩下两成是环境问题,比如依赖缺失、环境变量不一致、网络不通,这些靠本地模拟不出来,但对结论有参考价值。
第三板斧:压测前三个必查项,避开「测试全绿、上线全红」。第一查函数超时时间是否匹配压测请求的耗时分布;第二查实例数上限是否和压测并发量匹配,并发超过上限将直接失败;第三查下游服务的连接池配置,函数压测瞬间流量可能打满下游。这三项全过,压测结果才有参考价值,否则压测数据只能证明你的压测工具没问题。
这套习惯帮我省下了大量排查时间。每接手一个新Serverless项目,先把日志规范立起来,再跑通本地模拟链路,最后做压测前置检查,项目的可维护性就有了保障。Serverless把服务器运维的负担卸给了平台,但不意味着可以把工程素养一并卸掉。希望这篇实战笔记能帮你在Serverless部署上少踩几个坑。
本文还有配套的精品资源,点击获取