海外某流媒体公司的 API 构建在一个动态脚本平台之上。该平台每天需要处理数千次代码变更,允许客户端开发者通过执行服务端适配器代码来响应 HTTP 请求,从而为上千种设备类型定制 API 体验。开发者只需关注自己编写的适配器代码,无需操心服务器管理、容量规划和日常运维等基础设施问题。
从开发者体验来看,这一脚本平台类似于 Serverless 或函数即服务(FaaS)平台。需要说明的是,这种相似性仅限于开发者体验(DevEx)层面;其运行时是自研实现,并不是为了支持通用 Serverless 场景而设计的。

多年来,我们一直在为不同类型的开发者建设和运营这一平台,也因此积累了大量关于 Serverless 平台开发者体验的实践经验。以下是其中最重要的几点收获。
在 Serverless 架构中,更小的部署单元与更高层次的抽象相结合,可以带来许多显著优势,例如更快的部署速度、更强的可扩展性、更低的成本,以及更重要的是,让团队能够更加专注于产品功能本身。然而,运维问题并不会消失;它们只是换了一种形式出现,有时甚至会被进一步放大。因此,运维工具也必须随之演进,以适应新的需求。与此同时,开发者对整体体验的期待也会被进一步提高。
本文是系列文章的第一篇。我们将结合自身经验,概述下一代 Serverless 平台正在重点解决的几个方向。我们相信,这些经验同样适用于更通用的 Serverless 解决方案。本文将重点讨论应用开发、交付以及代码组合;后续文章将继续深入探讨部署、可观测性、性能、可扩展性以及其他运维相关问题。
开发:打造低延迟的本地开发体验
本地开发体验是否足够顺畅?
我们的脚本平台允许开发者编写承载应用逻辑的函数。开发者将代码,也就是脚本,上传到脚本平台,由平台提供运行时环境,并负责 API 路由、弹性扩展等基础设施能力。脚本可以通过开发者定义的 HTTP 路由,也就是端点,对外访问,并在远程虚拟机上执行。
从机制上看,远程运行时模型天然会限制用户脚本在本地直接执行,这给开发和测试迭代带来了不少不便。即便本地改动能够被顺畅地部署到远端,哪怕只是几十秒的等待时间,对开发者来说也依然显得十分漫长。

地方发展工作流程
为缓解这一问题,我们曾提供一个基于云端的交互式解释器(REPL),用于交互式地探索和执行脚本。然而,我们发现,脚本很少会长期保持为简单的函数单元。随着时间推移,它们往往会演变为类似“纳米服务”的复杂系统,业务逻辑分散在多个模块和源文件中。REPL 很难支撑真实生产脚本的复杂度,也无法满足开发者使用自己偏好的集成开发环境(IDE)、通过断点进行调试等需求。
我们还注意到,一些反模式开始悄然出现。为了避免反复上传代码进行迭代,开发者会倾向于使用冗长的调试日志,或编写其他防御性代码。这不仅影响开发效率,也会带来风险,例如敏感数据可能被意外写入调试日志。
基于这些经验,我们决定在下一代平台中优先建设一流、低延迟的本地开发体验,并支持实时重载、断点调试,以及对云端执行环境的模拟。
打包与版本控制:让部署工件可迁移、可追溯
部署工件是否便于迁移、版本控制和管理?
在当前平台中,我们主要关注部署工件的三个方面:
- 提供“一次构建,到处部署”的模式;
- 支持简单、直观的寻址方式;
- 促进可追溯性和生命周期管理。
为了确保代码在不同环境中行为一致,并能够被放心地部署到生产环境,可移植且不可变的组件至关重要。由于我们的平台运行在 JVM 之上,因此 JAR 文件自然成为实现这一目标的理想选择。
构建完成后,工件需要能够在交付流程中从测试环境一路流转到生产环境,并可在任何环境中被访问。我们发现,最简单的方案是使用名称和一个可选版本号来定位工件。有了名称和版本号,任何环境或区域都可以访问该工件并执行相应脚本。这个方案听起来很简单,但这种易于理解的命名模型让用户不必面对晦涩难懂、由系统生成的资源标识符。
我们还会为工件附加丰富的元数据,包括运行时信息,例如 JDK 版本、存活时间(TTL)、源码管理(SCM)提交记录等。这些元数据能够支持多种使用场景。例如,提交记录指针可以实现跨版本、跨环境的源码追溯,并支持自动化升级流程。主动纳入这些元数据,也有助于解决 Serverless 场景中特有的问题,例如资源生命周期管理。
总体来看,这套方法对用户而言效果不错。但任何技术方案都有局限,实际使用方式也可能与最初设想有所不同。例如,由于我们将版本号设计为可选项,许多团队开始在资源名称中自行引入版本控制方案,从而造成不必要的重复劳动。
在架构重构过程中,我们从以下几个方面改进了这一问题:
首先,基于对语义化版本控制概念的充分理解,我们将版本控制提升为平台中的一等概念。
其次,采用容器化打包方式,有助于通过捆绑系统依赖来保证代码的不可变性和可移植性。结合前文提到的本地开发体验,容器化打包也使开发者能够在本地以更接近生产环境的方式运行生产脚本。
测试与持续集成:应对更快的开发节奏
开发速度提升会带来哪些影响?
在我们的平台上,部署代码变更非常便捷,开发者也充分利用了这一能力。与此同时,这也凸显出预生产环境与生产环境之间的一些显著差异。
例如,频繁的开发者提交以及持续集成/持续交付(CI/CD)流程,会让预生产环境中的部署速度比生产环境快近 10 倍。每 10 到 20 次测试部署中,可能只有一次最终会进入生产环境。这些测试部署通常生命周期很短,却会给开发者带来额外的维护负担,因为大量不再使用的部署会遗留下来,需要被清理以节省资源。
另一个差异在于,测试环境的流量远低于生产环境。再加上部署周期较短,脚本的冷启动问题会变得更加明显。因此,开发和测试过程中经常会出现难以预测的执行延迟,客户端超时时间也很难以一致的方式进行调整。这会造成一种不理想的局面:开发者开始依赖生产环境完成测试,而这又违背了环境隔离的目标,并导致测试占用生产环境配额。
最后,远程执行模型还意味着,预生产阶段需要一些不同于生产环境的能力,例如运行集成测试、生成代码覆盖率、分析执行性能等。这些能力在生产环境中要么存在风险,要么并不可行。
综合以上因素,从一开始就建立定义清晰、相互隔离且易于使用的预生产环境至关重要。在下一代 Serverless 平台中,我们的目标是让预生产体验能够与生产环境相匹配,同时拥有自身独立的能力演进和创新空间。对于企业自身的研发管理实践而言,如果能借助 PingCode 这类智能化研发管理工具,将团队目标、客户反馈、需求清理、评审排期、开发、测试、发布以及 Wiki 知识沉淀串联起来,也更容易让研发流程实现自动化、数据化和智能化。
代码模块化与组合:提升函数复用与依赖管理能力
能否快速、准确地编写细粒度函数?
随着脚本平台的普及,对代码复用的需求也不断增长,尤其是用于实现共享功能的代码复用。然而,复制粘贴式的复用能力非常有限;一旦公共组件需要修改,还会额外增加运维成本。
我们需要一种解决方案,既能轻松地动态组合松耦合组件,又能提供关于代码复用情况的洞察。为实现这一目标,我们优先建设了动态共享库和依赖管理能力,并支持在运行时解析和链接直接依赖与传递依赖。
这里有一个重要的早期经验:共享模块的提供者和使用者在更新方面有着不同诉求。使用者希望严格控制自己何时获取更新;而提供者则希望在快速推送更新时保持高度灵活,并尽可能与使用者解耦。
使用语义化版本控制来支持依赖声明和依赖解析,有助于同时满足双方需求。提供者和使用者可以根据各自偏好,通过严格版本号或语义化版本范围来达成约定。

在这种松耦合模型下,要在大规模场景中持续保持变更速度,就必须深入了解双向依赖链。我们的平台使消费者和提供者都能够跟踪他们所依赖或交付的共享模块更新情况。
这些洞察能够帮助用户完成以下工作:
共享模块提供方可以跟踪错误修复或功能更新的采用情况。
消费者可以快速识别自己正在使用哪些依赖版本,以及是否需要升级。
这些信息也有助于更好地进行生命周期管理。关于这一点,我们将在后续文章中展开讨论。共享代码只要仍有用户使用,就需要持续维护;如果某个版本范围,例如某个主版本或次版本,已经不再被使用,就可以将其停用,从而释放系统资源用于其他用途。
在实际落地过程中,平台能力建设往往还会牵涉多角色、多团队协同。如果团队需要在更通用的项目协作层面统一管理任务、项目、文档、日历、工时和审批,也可以通过 Worktile 这类通用项目协作系统降低沟通成本,让平台研发、运营治理和跨团队协作更加顺畅。
文章包含AI辅助创作:Serverless 平台开发者体验:应用开发、交付与代码组合经验,发布者:cai,转载请注明出处:https://worktile.com/kb/p/3980862
微信扫一扫
支付宝扫一扫