最近换工作,面试了几家公司,发现Serverless成了面试的高频考点,尤其是云原生和后端开发岗位。前前后后被问到了很多Serverless相关的问题,有基础概念题,有架构设计题,有实战经验题,也有踩坑经历题。
Serverless这几年发展很快,从一个概念变成了很多公司实际在用的技术。尤其是AWS Lambda、阿里云函数计算、腾讯云云函数等平台的成熟,让Serverless的应用越来越广泛。面试中考察Serverless,也是很正常的事情。
本文整理我面试中被问到的Serverless框架面试题,附上我的回答思路和参考答案,给正在准备面试的朋友一些参考。这些问题,有的是我被问到的,有的是我和朋友交流后整理的,覆盖了基础概念、核心原理、架构设计、实战经验、常见问题等多个方面。
一、基础概念题
问题1:什么是Serverless?它和传统的服务器架构有什么区别?
这是最基础的问题,几乎每场面试都会问。
回答思路: Serverless(无服务器架构)是一种云计算架构,开发者不需要管理服务器,只需要编写函数代码,部署到云平台,由云平台负责服务器的管理、扩缩容、运维等。开发者只需要关注业务逻辑,不需要关心底层基础设施。
Serverless通常包含两个部分:
- FaaS(Function as a Service,函数即服务):把代码拆分成一个个函数,每个函数独立部署、独立运行、独立扩缩容,按调用次数和执行时间付费。比如AWS Lambda、阿里云函数计算。
- BaaS(Backend as a Service,后端即服务):把后端的通用能力(比如数据库、消息队列、存储、认证等)封装成服务,开发者直接调用,不需要自己搭建和维护。比如Firebase、AWS Amplify。
和传统服务器架构的区别:
- 服务器管理: 传统架构需要开发者自己管理服务器(购买、配置、部署、运维、扩缩容);Serverless不需要管理服务器,由云平台负责。
- 扩缩容: 传统架构需要手动配置扩缩容策略,或者预估流量提前扩容;Serverless自动扩缩容,根据请求量自动调整实例数量,请求来了就启动实例,没有请求就缩容到零。
- 计费方式: 传统架构按服务器资源(CPU、内存、带宽)付费,不管有没有请求都要付费;Serverless按调用次数和执行时间付费,没有调用就不付费。
- 状态管理: 传统架构的应用是长驻进程,可以在内存中保存状态;Serverless的函数是无状态的,执行完就销毁,状态需要保存在外部存储(数据库、缓存等)中。
- 冷启动: 传统架构的应用一直运行,没有冷启动问题;Serverless的函数在没有请求时会被销毁,下次请求来时需要重新启动,有冷启动延迟。
- 开发模式: 传统架构是单体应用或微服务,一个应用包含很多功能;Serverless是函数式,每个函数负责一个功能,粒度更细。
问题2:Serverless有哪些优缺点?
回答思路:
优点:
- 降低运维成本: 不需要管理服务器,不需要关心操作系统、安全补丁、扩缩容等运维工作,开发者可以专注于业务逻辑。
- 弹性扩缩容: 自动根据请求量扩缩容,从0到无限(受平台限制),不需要手动配置,能应对突发流量。
- 按需付费: 按调用次数和执行时间付费,没有调用就不付费,成本低,尤其适合流量波动大、请求量小的场景。
- 高可用: 云平台通常会在多个可用区部署,自动故障转移,可用性高,不需要开发者自己做高可用。
- 开发效率高: 只需要写函数代码,不需要搭建框架、配置服务器,部署简单,迭代快,能快速上线。
- 技术栈灵活: 支持多种编程语言(Node.js、Python、Java、Go等),可以根据需求选择合适的语言。
缺点:
- 冷启动问题: 函数长时间没有调用会被销毁,下次调用需要重新启动,有冷启动延迟(从几百毫秒到几秒不等),对延迟敏感的场景不友好。
- 无状态限制: 函数是无状态的,不能在内存中保存状态,状态需要保存在外部存储,增加了架构复杂度和延迟。
- 执行时间限制: 函数有最大执行时间限制(比如AWS Lambda最多15分钟),不适合长时间运行的任务。
- 调试和测试困难: 因为函数运行在云平台上,本地调试和测试比较困难,虽然有一些工具(比如Serverless Framework、SAM),但还是不如传统应用方便。
- 厂商锁定: 不同云平台的Serverless实现不一样,API、触发器、运行时都有差异,迁移成本高,容易被厂商锁定。
- 监控和可观测性不足: 函数粒度细,调用链路复杂,监控和排查问题比较困难,虽然云平台提供了一些监控工具,但还是不如传统应用方便。
- 不适合长连接和WebSocket: 函数是请求-响应模式,不适合长连接、WebSocket等场景,虽然有一些解决方案(比如API Gateway的WebSocket支持),但还是比较复杂。
- 冷启动导致的资源浪费和性能波动: 冷启动不仅影响延迟,还可能导致性能波动,因为冷启动时的性能和热启动时不一样。
问题3:Serverless适合什么场景?不适合什么场景?
回答思路:
适合的场景:
- 事件驱动的应用: 比如文件上传后触发处理(图片压缩、视频转码)、消息队列触发处理、定时任务(Cron)、Webhook处理等,这些场景天然适合函数式的事件驱动模式。
- API后端: 把API的每个接口拆分成一个函数,由API Gateway转发请求,适合RESTful API、GraphQL API等。
- 流量波动大的应用: 比如电商大促、活动页面、营销活动等,流量波动大,平时请求量小,高峰期请求量大,Serverless自动扩缩容,按需付费,成本低。
- 请求量小的应用: 比如个人项目、内部工具、小程序后端等,请求量小,用传统服务器不划算(一直付费但利用率低),用Serverless按需付费,成本低。
- 数据处理和ETL: 比如数据清洗、数据转换、日志处理、实时数据分析等,这些场景通常是事件驱动的,处理完就结束,适合Serverless。
- IoT后端: 物联网设备的消息处理、设备管理、数据采集等,设备数量多,消息量波动大,适合Serverless。
- 聊天机器人和AI应用: 比如聊天机器人、图像识别、语音处理等,调用量波动大,处理时间短,适合Serverless。
不适合的场景:
- 长时间运行的任务: 函数有最大执行时间限制,不适合需要长时间运行的任务(比如大数据批处理、长时间计算等)。
- 长连接和WebSocket应用: 函数是请求-响应模式,不适合长连接、WebSocket、实时通信等场景。
- 对延迟极度敏感的应用: 冷启动有延迟,对延迟极度敏感的场景(比如高频交易、实时游戏等)不适合。
- 有状态的应用: 函数是无状态的,如果应用需要大量的状态保存在内存中(比如游戏服务器、会话保持等),不适合Serverless。
- 高并发、持续高负载的应用: 如果应用持续高负载,请求量一直很大,Serverless的成本可能比传统服务器高,而且冷启动频繁,性能不稳定。
- 需要底层控制和定制化的应用: Serverless屏蔽了底层基础设施,如果应用需要对操作系统、运行时、网络等进行深度定制和控制,不适合Serverless。
- 复杂的单体应用: 如果应用很复杂,功能之间耦合度高,拆分成函数很困难,或者函数之间调用频繁,不适合Serverless。
- 需要快速本地开发和调试的应用: Serverless的本地开发和调试比较困难,如果需要频繁的本地开发和调试,传统应用更方便。
二、核心原理题
问题4:Serverless的冷启动是什么?为什么会有冷启动?怎么优化冷启动?
这是高频考点,几乎每场面试都会问。
回答思路:
什么是冷启动: 冷启动是指函数在长时间没有调用后,云平台会销毁函数实例以节省资源;当新的请求到来时,云平台需要重新启动函数实例(下载代码、启动运行时、初始化函数、执行代码),这个过程需要时间,会导致请求延迟增加,这个过程就是冷启动。
和冷启动相对的是热启动:函数实例已经在运行,新的请求到来时,直接复用已有的实例执行,不需要重新启动,延迟低。
为什么会有冷启动:
- 资源节约: 云平台为了节约资源,会把长时间没有调用的函数实例销毁,避免占用资源。
- 按需扩缩容: Serverless的核心是按需扩缩容,没有请求时缩容到零,有请求时再启动实例,这就必然会有冷启动。
- 无状态设计: 函数是无状态的,实例不需要保持状态,所以可以随时销毁和重建,这也导致了冷启动。
冷启动的过程:
- 下载函数代码到新的实例
- 启动运行时(Node.js、Python、JVM等)
- 加载函数代码和依赖
- 执行函数的初始化代码(比如连接数据库、初始化客户端等)
- 处理请求
其中,启动运行时和初始化代码是最耗时的部分,尤其是Java等重运行时,冷启动时间可能达到几秒。
怎么优化冷启动:
- 选择轻量级运行时: 不同语言的冷启动时间差别很大,Node.js、Python等轻量级运行时冷启动快(几百毫秒),Java、C#等重量级运行时冷启动慢(几秒)。对延迟敏感的场景,选择轻量级语言。
- 减少函数代码和依赖的大小: 代码和依赖越大,下载和加载的时间越长,冷启动越慢。优化方法:
- 只打包必要的依赖,移除不需要的依赖 - 使用轻量级的依赖库,替代重量级的库 - 对代码进行压缩和混淆(注意:压缩可能影响调试) - 使用分层依赖,把不常变化的依赖放在底层,利用缓存
- 优化初始化代码: 把不需要在初始化时执行的代码移到处理函数中,减少初始化时间。比如:
- 延迟初始化:把数据库连接、客户端初始化等移到第一次调用时再执行(注意:这会增加第一次请求的时间,但可以减少冷启动的初始化时间) - 全局复用:把数据库连接、客户端等放在全局变量中,复用到多个请求,避免每次请求都重新初始化 - 减少初始化时的同步操作:初始化时不要做耗时的同步操作,比如同步读取大文件、同步请求外部服务等
- 使用预热(Provisioned Concurrency): 很多云平台提供了预置并发(Provisioned Concurrency)功能,可以预先启动一定数量的函数实例,保持热状态,避免冷启动。适合对延迟敏感的场景,但需要额外付费。
- 使用保持活跃的策略: 可以通过定时任务(比如每分钟调用一次函数)来保持函数实例的活跃,避免被销毁。但这种方法不是很可靠,因为云平台可能还是会销毁实例,而且会增加调用次数和成本。
- 优化函数粒度: 不要把函数拆得太细,函数之间调用频繁会增加冷启动的概率和延迟。合理设计函数粒度,把相关的功能放在一个函数中,减少函数之间的调用。
- 使用容器镜像: 一些云平台支持使用容器镜像部署函数,可以把运行时、依赖、代码都打包在镜像中,利用镜像缓存,减少下载和启动时间。
- 选择合适的内存配置: 云平台的函数性能和内存配置相关,内存越大,CPU越强,冷启动越快。适当增加内存配置,可以减少冷启动时间,虽然成本会增加,但对延迟敏感的场景可能值得。
问题5:Serverless函数是无状态的,那状态怎么管理?
回答思路:
Serverless函数是无状态的,函数执行完就销毁,内存中的状态不会保留。所以状态需要保存在外部存储中,根据状态的类型和访问模式,选择合适的存储方案。
常见的状态管理方案:
- 数据库: 适合持久化的结构化数据,比如用户信息、订单数据、业务数据等。可以用关系型数据库(MySQL、PostgreSQL)或者NoSQL数据库(MongoDB、DynamoDB)。
- 注意:函数是无状态的,数据库连接不能保存在函数内存中,需要每次请求都建立连接,或者使用连接池(放在全局变量中,复用到多个请求)。 - 注意:高并发下,函数实例数量多,数据库连接数可能会爆炸,需要使用连接池、限制并发数,或者使用Serverless专用的数据库代理(比如AWS RDS Proxy)。
- 缓存: 适合高频访问、对延迟敏感的临时数据,比如会话信息、热点数据、计算结果等。可以用Redis、Memcached等缓存服务。
- 注意:和数据库一样,缓存连接也需要复用,放在全局变量中。 - 注意:缓存是临时存储,数据可能会丢失,不要把重要数据只存在缓存中。
- 对象存储: 适合大文件、非结构化数据,比如图片、视频、文档、日志等。可以用AWS S3、阿里云OSS等对象存储服务。
- 注意:对象存储的访问延迟比数据库和缓存高,不适合高频访问的小数据。 - 注意:对象存储通常是最终一致性,写入后可能不会立即可读,需要注意。
- 消息队列: 适合异步处理、解耦、削峰填谷的场景,比如任务队列、事件通知、日志收集等。可以用AWS SQS、阿里云MQ等消息队列服务。
- 注意:消息队列是异步的,不适合需要同步返回结果的场景。 - 注意:消息可能会重复投递,需要保证函数的幂等性。
- 密钥管理服务: 适合保存敏感信息,比如数据库密码、API密钥、证书等。可以用AWS KMS、阿里云KMS等密钥管理服务,或者用环境变量(注意:环境变量不够安全,敏感信息不要明文放在环境变量中)。
- 函数的临时存储: 一些云平台提供了函数的临时存储(比如AWS Lambda的/tmp目录),可以在函数执行期间保存临时文件,但函数执行完就会被销毁,不适合持久化数据。适合保存函数执行期间的临时文件,比如下载的文件、处理中的中间结果等。
- 客户端存储: 对于用户会话、用户偏好等状态,可以保存在客户端(比如Cookie、LocalStorage、IndexedDB),函数每次请求时从客户端读取,不需要在服务端保存状态。适合无状态的API设计。
状态管理的最佳实践:
- 尽量设计无状态的函数,把状态保存在外部存储中。
- 根据数据的类型和访问模式,选择合适的存储方案,不要一种存储用到底。
- 数据库和缓存连接要复用,放在全局变量中,避免每次请求都重新建立连接。
- 注意高并发下的数据库连接数问题,使用连接池、限制并发数,或者使用数据库代理。
- 保证函数的幂等性,因为函数可能会被重复调用(比如消息队列重复投递、重试机制等)。
- 敏感信息不要明文保存在代码或环境变量中,使用密钥管理服务。
- 临时数据保存在函数的临时存储中,不要占用外部存储,减少成本和延迟。
问题6:Serverless的计费方式是怎样的?成本怎么优化?
回答思路:
计费方式: Serverless通常按以下几个维度计费:
- 调用次数: 按函数被调用的次数计费,每次调用收费固定的费用(很便宜,通常每百万次调用几美元到几十美元)。
- 执行时间: 按函数的执行时间计费,通常以100毫秒为单位,执行时间越长,费用越高。执行时间和内存配置相关,内存越大,单位时间的费用越高。
- 内存配置: 函数的内存配置决定了CPU的性能和单位时间的费用,内存越大,CPU越强,单位时间费用越高。
- 网络流量: 函数产生的出网流量(比如访问外部服务、返回数据给客户端)按流量计费,入网流量通常免费。
- 其他服务费用: 函数调用的其他云服务(比如数据库、缓存、对象存储、API Gateway等)按各自的计费方式收费,这些费用可能比函数本身的费用还高。
- 预置并发费用: 如果使用了预置并发(Provisioned Concurrency)来避免冷启动,需要为预置的实例付费,不管有没有调用都要付费。
成本优化方法:
- 优化函数执行时间: 执行时间是Serverless成本的重要组成部分,优化执行时间能显著降低成本。
- 优化代码逻辑,减少不必要的计算和等待 - 使用更高效的算法和数据结构 - 减少外部服务调用,或者使用缓存减少重复调用 - 异步处理不需要同步返回的任务,缩短主函数的执行时间
- 选择合适的内存配置: 内存配置决定了CPU性能和单位时间费用,需要在性能和成本之间找到平衡。
- 测试不同内存配置下的执行时间和成本,找到性价比最高的配置 - 有时候增加内存配置,CPU性能提升,执行时间缩短,总成本反而更低 - 对执行时间短、调用频率低的函数,使用小内存配置,降低成本 - 对执行时间长、对性能敏感的函数,使用大内存配置,缩短执行时间
- 减少调用次数: 调用次数也是成本的一部分,减少不必要的调用能降低成本。
- 合并函数,把相关的功能放在一个函数中,减少函数之间的调用 - 使用批处理,把多个请求合并成一次调用 - 使用缓存,减少重复计算和重复调用 - 优化API Gateway的配置,避免不必要的函数调用(比如静态资源直接由CDN或对象存储提供,不经过函数)
- 优化网络流量: 出网流量是收费的,减少不必要的出网流量能降低成本。
- 减少函数返回的数据量,只返回必要的数据 - 使用CDN缓存静态资源,减少函数的出网流量 - 函数访问的外部服务尽量和函数在同一个区域,减少跨区域流量 - 压缩传输的数据,减少流量
- 合理使用其他云服务: 其他云服务的费用可能比函数本身还高,需要合理选择和使用。
- 选择合适的数据库和缓存规格,不要过度配置 - 使用Serverless专用的数据库(比如AWS Aurora Serverless、DynamoDB),按需付费,降低成本 - 优化数据库查询,减少数据库的读取次数和数据量 - 使用缓存减少数据库压力,降低数据库成本
- 避免冷启动导致的额外成本: 冷启动不仅影响性能,还可能增加成本(因为冷启动时的执行时间更长)。
- 优化冷启动,减少冷启动时间 - 对调用频率低、对延迟不敏感的函数,不需要使用预置并发,避免额外费用 - 对调用频率高、对延迟敏感的函数,使用预置并发,虽然增加了费用,但提升了性能,可能值得
- 监控和分析成本: 定期监控和分析函数的成本,找到成本高的函数,针对性优化。
- 使用云平台的成本监控工具,查看每个函数的调用次数、执行时间、费用 - 设置成本告警,当成本超过阈值时及时通知 - 定期分析成本报告,找到成本异常的函数,优化或调整配置
- 选择合适的部署模式: 根据应用的特点,选择合适的部署模式,不要所有功能都用Serverless。
- 对持续高负载、长连接、对延迟极度敏感的功能,使用传统服务器或容器,成本可能更低 - 对事件驱动、流量波动大、请求量小的功能,使用Serverless,成本更低 - 混合使用Serverless和传统架构,各取所长,整体成本最优
三、架构设计题
问题7:怎么设计一个基于Serverless的微服务架构?
回答思路:
设计基于Serverless的微服务架构,需要考虑服务拆分、API设计、数据管理、服务间通信、部署运维、监控可观测性等方面。
1. 服务拆分:
- 按照业务领域拆分服务,每个服务负责一个业务领域(比如用户服务、订单服务、商品服务等),遵循单一职责原则和领域驱动设计(DDD)。
- 每个服务可以包含多个函数,每个函数负责一个具体的功能(比如用户服务包含注册、登录、获取用户信息、更新用户信息等函数)。
- 不要把函数拆得太细,避免函数之间调用频繁,增加延迟和成本。合理的粒度是:一个函数负责一个API接口或者一个事件处理逻辑。
- 服务之间松耦合,通过API或事件通信,不直接共享数据库。
2. API设计:
- 使用API Gateway作为统一入口,负责请求路由、认证授权、限流熔断、请求转换等。
- 每个API接口对应一个函数,由API Gateway转发请求到对应的函数。
- 设计RESTful API或者GraphQL API,遵循统一的接口规范,便于调用和维护。
- API Gateway可以做请求校验、参数转换、响应格式化等,减少函数的重复代码。
- 对公开API,使用API Gateway的认证授权功能(比如API Key、JWT、OAuth2等),保证API安全。
3. 数据管理:
- 每个服务有自己的数据库,服务之间不共享数据库,保证服务的独立性和解耦。
- 根据数据的特点选择合适的数据库:结构化数据用关系型数据库,非结构化数据用NoSQL数据库,缓存用Redis,大文件用对象存储。
- 函数是无状态的,数据库连接放在全局变量中复用,注意高并发下的连接数问题,可以使用数据库代理(比如AWS RDS Proxy)或者Serverless数据库(比如Aurora Serverless、DynamoDB)。
- 服务之间的数据一致性通过事件驱动或者分布式事务保证(比如Saga模式),避免分布式事务的复杂性。
- 数据备份和恢复由云平台负责,但需要配置备份策略,保证数据安全。
4. 服务间通信:
- 同步通信:服务之间需要同步返回结果的场景,通过API调用(HTTP/REST),由API Gateway转发,或者直接调用函数(比如AWS Lambda的直接调用)。
- 异步通信:服务之间不需要同步返回结果的场景,通过消息队列或事件总线通信(比如AWS SQS、SNS、EventBridge),实现服务解耦和削峰填谷。
- 事件驱动架构:使用事件驱动的方式,服务之间通过事件通信,一个服务发布事件,其他服务订阅事件并处理,实现服务的松耦合和可扩展性。
- 注意:服务间调用会增加延迟和成本,尽量减少服务间的同步调用,能用异步的就用异步。
5. 部署和运维:
- 使用基础设施即代码(IaC)工具管理基础设施,比如AWS CloudFormation、Terraform、Serverless Framework等,把基础设施配置代码化,便于版本管理、自动化部署和环境复制。
- 每个服务独立部署、独立扩缩容、独立版本管理,一个服务的变更不影响其他服务。
- 使用CI/CD流水线自动化部署,代码提交后自动测试、构建、部署,提高开发效率和部署质量。
- 蓝绿部署或金丝雀发布,降低部署风险,新版本先发布少量实例,验证没问题后再全量发布。
- 配置管理使用环境变量或配置服务,不同环境(开发、测试、生产)使用不同的配置,不要把配置硬编码在代码中。
6. 监控和可观测性:
- 函数粒度细,调用链路复杂,监控和可观测性非常重要。
- 使用云平台的监控工具(比如AWS CloudWatch、阿里云ARMS)监控函数的调用次数、执行时间、错误率、内存使用等指标。
- 分布式链路追踪:使用分布式追踪工具(比如AWS X-Ray、Jaeger、Zipkin)追踪请求在多个函数和服务之间的调用链路,便于排查问题和性能优化。
- 日志管理:函数的日志统一收集到日志服务(比如AWS CloudWatch Logs、阿里云SLS),便于查询和分析。结构化日志,包含请求ID、用户ID、函数名、执行时间等信息,便于排查问题。
- 告警配置:对关键指标(错误率、执行时间、调用次数等)设置告警,出现异常时及时通知,快速响应。
- 自定义指标:除了云平台提供的默认指标,还可以根据业务需求上报自定义指标,监控业务层面的状态。
7. 安全设计:
- 认证授权:API Gateway统一做认证授权,函数不需要重复处理认证逻辑。使用JWT、OAuth2、API Key等认证方式。
- 最小权限原则:每个函数只授予它需要的最小权限,不要授予过大的权限,减少安全风险。
- 敏感信息管理:数据库密码、API密钥等敏感信息使用密钥管理服务(KMS)加密保存,不要明文放在代码或环境变量中。
- 网络安全:函数部署在VPC中,通过安全组和网络ACL控制网络访问,数据库等服务不暴露在公网,只允许函数访问。
- 输入校验:函数对输入参数进行严格校验,防止注入攻击、越权访问等安全问题。
- 依赖安全:定期检查函数依赖的第三方库,及时更新有安全漏洞的依赖。
问题8:Serverless和微服务、容器(K8s)的关系和区别是什么?怎么选择?
回答思路:
关系和区别:
Serverless、微服务、容器(K8s)是三个不同层面的概念,它们之间既有区别又有联系:
- 微服务是一种架构风格: 微服务是把单体应用拆分成多个小的、独立的服务,每个服务负责一个业务领域,独立部署、独立扩缩容、独立技术栈。微服务关注的是应用的架构和拆分方式,不关心底层的部署和运行环境。微服务可以部署在传统服务器上,也可以部署在容器(K8s)上,也可以用Serverless实现。
- 容器(K8s)是一种部署和运行环境: 容器是一种轻量级的虚拟化技术,把应用和依赖打包在容器镜像中,运行在容器引擎上。K8s是容器编排平台,负责容器的部署、扩缩容、运维、服务发现等。容器(K8s)关注的是应用的部署和运行环境,不关心应用的架构(可以是单体,也可以是微服务)。容器(K8s)提供了比传统服务器更灵活的部署和运维方式,但还是需要开发者管理服务器和容器。
- Serverless是一种云计算架构和运行模式: Serverless是开发者不需要管理服务器,只需要编写函数代码,由云平台负责服务器的管理、扩缩容、运维等。Serverless关注的是应用的运行模式和运维方式,把服务器管理抽象掉,让开发者只关注业务逻辑。Serverless通常和微服务结合使用,每个微服务拆分成多个函数,但Serverless也可以用来实现单体应用(虽然不太适合)。
三者的区别:
| 维度 | 传统服务器 | 容器(K8s) | Serverless |
|---|---|---|---|
| 服务器管理 | 开发者管理 | 开发者管理(K8s降低了管理成本) | 云平台管理 |
| 部署单位 | 应用 | 容器镜像 | 函数 |
| 扩缩容 | 手动或半自动 | 自动(需要配置HPA) | 自动(从0到N) |
| 计费方式 | 按资源付费(一直付费) | 按资源付费(一直付费) | 按调用次数和执行时间付费(无调用不付费) |
| 状态 | 有状态(长驻进程) | 有状态(长驻进程) | 无状态(执行完销毁) |
| 冷启动 | 无 | 有(容器启动) | 有(函数启动,更明显) |
| 执行时间限制 | 无 | 无 | 有(通常最多15分钟) |
| 运维成本 | 高 | 中 | 低 |
| 灵活性 | 高(完全控制) | 高(完全控制) | 低(受平台限制) |
| 适合场景 | 所有场景 | 复杂应用、持续高负载 | 事件驱动、流量波动大、请求量小 |
怎么选择:
选择哪种架构和部署方式,需要根据应用的特点、团队能力、成本预算等因素综合考虑:
- 选择Serverless的场景:
- 事件驱动的应用(文件处理、消息处理、定时任务、Webhook等) - API后端,尤其是流量波动大、请求量小的API - 流量波动大的应用(电商大促、活动页面、营销活动等) - 请求量小的应用(个人项目、内部工具、小程序后端等) - 数据处理和ETL(数据清洗、日志处理、实时分析等) - 初创公司和小团队,没有专门的运维人员,想降低运维成本 - 快速原型验证,想快速上线,验证想法
- 选择容器(K8s)的场景:
- 复杂的微服务架构,服务之间耦合度高,调用频繁 - 持续高负载的应用,请求量一直很大,Serverless成本可能更高 - 长连接、WebSocket、实时通信等应用 - 对延迟极度敏感的应用(高频交易、实时游戏等) - 需要长时间运行的任务(大数据批处理、长时间计算等) - 需要对底层操作系统、运行时、网络进行深度定制和控制的应用 - 有专门的运维团队,有能力管理K8s集群 - 需要避免厂商锁定,希望可以在不同云平台之间迁移
- 选择传统服务器的场景:
- 简单的单体应用,不需要微服务和弹性扩缩容 - 预算有限,小服务器成本最低 - 团队技术能力有限,不会用K8s和Serverless - 对安全性和可控性要求极高,需要完全控制服务器 - 遗留系统,迁移成本高,暂时不适合改造
- 混合使用:
- 很多时候,不是非此即彼,可以混合使用多种架构和部署方式。 - 比如:核心业务用容器(K8s)部署,事件处理和流量波动大的功能用Serverless,静态资源用对象存储+CDN。 - 比如:微服务架构,有的服务部署在K8s上,有的服务用Serverless实现,根据每个服务的特点选择合适的部署方式。 - 混合使用可以各取所长,在性能、成本、运维效率之间找到最优平衡。
四、实战经验题
问题9:你在实际项目中用过Serverless吗?遇到过什么坑?怎么解决的?
这是考察实战经验的问题,需要结合实际项目回答。
回答思路(结合我的实际经验):
我在几个项目中用过Serverless,主要是阿里云函数计算和AWS Lambda,遇到过不少坑,分享几个典型的:
坑1:冷启动导致接口延迟高,用户体验差
问题描述: 我们有一个API服务,用Serverless实现,部署在阿里云函数计算上。平时请求量不大,接口响应很快。但每天早上第一次调用,或者隔了一段时间没有调用后,接口响应特别慢,要3-5秒才能返回,用户体验很差,经常收到用户投诉。
原因分析: 这是典型的冷启动问题。函数长时间没有调用,实例被销毁,新的请求到来时需要重新启动实例。我们用的是Java运行时,JVM启动慢,加上初始化代码(连接数据库、加载Spring上下文)耗时,冷启动时间达到了3-5秒。
解决方案:
- 把Java运行时换成了Node.js,冷启动时间从3-5秒降到了500毫秒以内。
- 优化了初始化代码,把不需要在初始化时执行的代码移到了处理函数中,数据库连接延迟初始化。
- 对核心接口使用了预置并发(Provisioned Concurrency),预先启动5个实例保持热状态,虽然增加了一些成本,但核心接口的延迟稳定在了200毫秒以内。
- 对非核心接口,使用了定时预热,每5分钟调用一次,保持实例活跃。
效果: 优化后,接口平均延迟从1.5秒降到了300毫秒,冷启动导致的延迟问题基本解决,用户投诉也没有了。
坑2:高并发下数据库连接数爆炸,数据库崩溃
问题描述: 我们做了一个营销活动,预计QPS在500左右。活动开始后,流量瞬间上来,函数实例自动扩缩容到了几百个,每个实例都建立了数据库连接,数据库的连接数瞬间达到了上限,数据库响应变慢,甚至崩溃,导致整个服务不可用。
原因分析: Serverless函数是无状态的,每个实例都独立建立数据库连接。高并发下,函数实例数量暴增,数据库连接数也跟着暴增,超过了数据库的最大连接数,导致数据库崩溃。传统应用中,我们用连接池控制连接数,但Serverless的每个实例都有自己的连接池,实例数量多了,总连接数还是会爆炸。
解决方案:
- 使用了数据库代理(阿里云的RDS Proxy),由代理统一管理数据库连接,函数实例连接到代理,代理复用数据库连接,大大减少了数据库的实际连接数。
- 优化了函数的数据库连接配置,每个实例的连接池大小从10降到了2,因为函数是单线程处理请求,不需要太多连接。
- 给函数设置了最大并发数限制,避免实例数量无限增长,超过数据库的承受能力。
- 对热点数据使用了Redis缓存,减少数据库查询,降低数据库压力。
- 数据库做了读写分离,读请求走从库,写请求走主库,分散数据库压力。
效果: 优化后,数据库连接数稳定在200以内,数据库CPU使用率从100%降到了50%左右,活动期间服务稳定运行,没有再出现数据库崩溃的问题。
坑3:函数执行时间超时,任务被强制终止
问题描述: 我们有一个数据处理函数,需要处理大量数据,正常执行时间在10分钟左右。但函数计算的最大执行时间限制是15分钟,有时候数据量大一点,处理时间超过15分钟,函数就被强制终止了,数据处理到一半,结果不完整。
原因分析: Serverless函数有最大执行时间限制(AWS Lambda最多15分钟,阿里云函数计算最多也是15分钟),不适合长时间运行的任务。我们的数据处理任务有时候会超过这个限制,导致任务失败。
解决方案:
- 把大数据处理任务拆分成多个小任务,每个小任务处理一部分数据,执行时间控制在5分钟以内。用消息队列分发任务,多个函数并行处理,最后汇总结果。
- 对处理逻辑进行了优化,使用更高效的算法,减少不必要的计算,把单任务的执行时间从10分钟降到了3-5分钟。
- 增加了任务断点续传功能,把处理进度保存在数据库中,如果任务超时失败,下次可以从断点继续处理,不需要从头开始。
- 对特别大的数据处理任务,改用了容器(K8s)部署,没有执行时间限制,更适合长时间运行的任务。
效果: 优化后,数据处理任务的执行时间稳定在5分钟以内,不再出现超时问题,处理效率也因为并行处理提升了很多。
坑4:本地开发和调试困难,开发效率低
问题描述: 刚开始用Serverless的时候,本地开发和调试很困难。函数运行在云平台上,本地没有完整的运行环境,每次修改代码都要部署到云上才能测试,部署一次要几分钟,开发效率很低。而且云上的日志查看也不方便,排查问题很费劲。
原因分析: Serverless屏蔽了底层运行环境,本地很难模拟完整的云平台环境(包括API Gateway、触发器、各种云服务等),导致本地开发和调试困难。
解决方案:
- 使用了Serverless Framework工具,它提供了本地模拟运行的功能,可以在本地运行函数,模拟API Gateway和触发器,大部分功能可以在本地测试,不需要每次都部署到云上。
- 使用了Docker容器模拟云服务(比如本地MySQL、Redis、MinIO模拟对象存储),本地环境和云上环境尽量保持一致。
- 编写了完善的单元测试,核心逻辑通过单元测试验证,不需要依赖云平台环境。
- 部署时使用了版本和别名功能,先部署到开发环境测试,没问题后再发布到生产环境,减少对生产环境的影响。
- 使用了分布式链路追踪工具(阿里云ARMS),请求的调用链路可以清晰地看到,每个函数的执行时间、输入输出、日志都能查到,排查问题方便了很多。
效果: 优化后,本地开发效率提升了很多,大部分功能可以在本地测试,部署频率从每天几次降到了每天一次,排查问题的时间也从几小时降到了几十分钟。
坑5:厂商锁定,迁移成本高
问题描述: 我们最开始用的是阿里云函数计算,后来因为业务需要,想迁移到AWS Lambda,发现迁移成本很高。两个平台的API、触发器、运行时、配置方式都不一样,代码需要大量修改,基础设施配置也需要重新做,迁移花了一个多月的时间。
原因分析: 不同云平台的Serverless实现不一样,没有统一的标准,API、触发器、运行时、配置方式都有差异,导致厂商锁定,迁移成本高。
解决方案:
- 抽象了一层函数运行时的接口,业务逻辑和平台相关的代码分离,业务逻辑不直接调用平台API,而是通过抽象接口调用,迁移到新平台时只需要实现新平台的接口,业务逻辑不需要修改。
- 使用了Serverless Framework作为部署工具,它支持多个云平台,基础设施配置用统一的语法,迁移时只需要修改平台相关的配置,不需要重写整个基础设施配置。
- 云服务也做了抽象,比如数据库用标准的SQL,对象存储用S3兼容的API,消息队列用标准的协议,尽量减少对特定云平台的依赖。
- 重要的业务逻辑都有完善的单元测试和集成测试,迁移后可以快速验证功能是否正常。
效果: 做了这些优化后,后来我们又迁移过一次平台,只花了一周时间就完成了,迁移成本大大降低。
问题10:怎么保证Serverless函数的幂等性?
回答思路:
为什么需要幂等性: Serverless函数可能会被重复调用,原因包括:
- 消息队列的重复投递(很多消息队列保证至少投递一次,不保证只投递一次)
- 函数执行失败后的重试机制(云平台会自动重试失败的函数)
- 客户端的重试(用户重复点击、网络超时后重试等)
- 事件源的重复事件(比如S3的事件通知可能会重复发送)
如果函数不是幂等的,重复调用可能会导致数据重复、状态错误、资金损失等严重问题。所以Serverless函数必须保证幂等性。
什么是幂等性: 幂等性是指同一个操作执行一次和执行多次的效果是一样的,不会因为重复执行而产生副作用。比如查询操作天然是幂等的,因为查询不会改变数据;但写入操作不是天然幂等的,重复写入会导致数据重复。
怎么保证幂等性:
- 使用唯一标识符(Idempotency Key):
- 每个请求或事件携带一个唯一标识符(比如订单ID、消息ID、请求ID),函数执行前先检查这个标识符是否已经处理过,如果已经处理过,直接返回之前的结果,不重复处理。 - 可以用数据库或缓存保存已经处理过的标识符,设置合理的过期时间(比如24小时),避免存储无限增长。 - 这是最通用、最可靠的幂等性方案,适用于大多数场景。
- 数据库层面的唯一约束:
- 在数据库表中设置唯一约束(比如订单号、用户ID+操作类型),重复写入时数据库会报错,函数捕获错误并处理,避免数据重复。 - 比如创建订单时,订单号设置唯一约束,重复创建相同订单号的订单会失败,函数捕获后返回"订单已存在"。 - 这种方法简单可靠,适合有唯一标识的写入操作。
- 状态机控制:
- 对有状态的操作,使用状态机控制状态流转,每个状态只能流转到特定的下一个状态,重复执行时如果状态已经流转,直接返回,不重复处理。 - 比如订单状态:待支付 → 已支付 → 已发货 → 已完成。支付操作只能把"待支付"状态改为"已支付",如果订单已经是"已支付"状态,重复调用支付接口直接返回成功,不重复处理。 - 这种方法适合有明确状态流转的业务场景。
- 先查询再写入(Check-then-Act):
- 写入前先查询数据是否已经存在,如果已经存在,直接返回,不重复写入;如果不存在,再执行写入。 - 注意:这种方法在并发场景下可能有竞态条件(两个请求同时查询都发现不存在,然后都写入),需要配合数据库唯一约束或分布式锁使用。 - 适合并发不高的场景,或者作为其他幂等方案的补充。
- 使用分布式锁:
- 对关键操作,使用分布式锁(比如Redis的SETNX、Zookeeper)保证同一时间只有一个请求在处理,重复请求等待锁释放后,发现数据已经处理过,直接返回。 - 分布式锁会增加延迟和复杂度,适合并发高、对一致性要求高的场景。 - 注意:分布式锁需要设置合理的超时时间,避免锁无法释放导致死锁。
- 操作本身设计成幂等的:
- 在设计接口和操作时,尽量把操作设计成幂等的,比如用PUT代替POST(PUT是幂等的,重复提交相同数据效果一样),用增量更新代替全量更新(增量更新重复执行可能有问题,需要注意)。 - 删除操作天然是幂等的(删除一个不存在的数据和删除一个存在的数据,最终结果都是数据不存在)。 - 在API设计阶段就考虑幂等性,比事后补救更有效。
幂等性的最佳实践:
- 所有写入操作都要考虑幂等性,假设函数会被重复调用,设计时就保证重复调用不会产生副作用。
- 优先使用唯一标识符(Idempotency Key)的方案,这是最通用、最可靠的。
- 数据库层面设置唯一约束,作为最后一道防线,即使应用层有漏洞,数据库也能保证数据不重复。
- 对有状态的操作,使用状态机控制状态流转,保证状态只能向前流转,不能重复。
- 幂等性的存储(比如已处理的标识符)要设置合理的过期时间,避免存储无限增长,同时保证在可能重复的时间范围内有效。
- 重复调用时,返回和第一次调用相同的结果,不要返回错误,让调用方知道操作已经成功完成。
- 对幂等性进行测试,模拟重复调用的场景,验证函数是否真的幂等。
五、写在最后
Serverless是这几年云计算领域最热门的技术之一,它改变了应用的开发和部署方式,让开发者可以更专注于业务逻辑,降低了运维成本,提高了开发效率。但Serverless也不是银弹,它有自己的适用场景和局限性,冷启动、无状态、调试困难、厂商锁定等问题,都需要在实际应用中注意和解决。
面试中考察Serverless,不仅是考察你对这个技术的了解程度,更是考察你对云计算架构的理解,对技术选型的判断,以及解决实际问题的能力。所以准备Serverless面试,不要只背概念,还要理解原理,结合实际项目经验,思考怎么用Serverless解决实际问题,怎么避免常见的坑。
本文整理的这些面试题,覆盖了基础概念、核心原理、架构设计、实战经验等多个方面,希望能给正在准备面试的朋友一些参考。当然,面试题是背不完的,关键是理解原理,多实践,多思考,这样才能在面试中游刃有余。
最后,用一句话结束本文:"技术是为业务服务的,没有最好的技术,只有最合适的技术。"Serverless也是一样,它不是万能的,但在合适的场景下,它能发挥巨大的价值。希望我们都能根据业务需求,选择最合适的技术,做出最好的产品。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录