2026-01-27 14:32:24
7
by James Beswick on 2024年3月4日 in AWS Lambda Serverless Permalink Share
在设计无服务器微服务时,有多种方法可供选择。本文探讨了三种主要架构设计模式:单一责任的Lambda函数、Lambda大型一体函数以及读写分离。这些方法各有优缺点,了解它们如何满足业务需求是关键。
设计基于AWS Lambda的工作负载时,开发人员会面临许多问题,因为在代码或基础设施层面都可以表达出模块化特性。使用无服务器架构运行代码需要额外的规划,以便将业务逻辑从基础功能中提取出来。这种明确的关注点分离确保了强大的模块化,为演化架构铺平了道路。
本篇文章侧重于同步工作负载,但类似的考量也适用于其他类型的工作负载。在确定API的边界上下文并与消费者达成API合同后,是时候构建您的边界上下文和相关基础设施的架构。
使用Lambda函数构建API的两种最常见方式是单一责任和Lambda大型一体函数。然而,本文将探讨一种替代方案,可以兼顾两者的优点。
单一责任Lambda函数旨在执行特定任务或处理无服务器架构中的特定事件触发操作。
这种方法在业务逻辑与功能之间提供了强大的关注点分离。您可以单独测试特定能力,独立部署Lambda函数,减少引入错误的可能性,并更容易在Amazon CloudWatch中调试问题。
此外,单一用途函数实现了高效的资源分配,因为Lambda会根据需求自动扩展,优化资源消耗并降低成本。这意味着您可以针对每个函数调整内存大小、架构和其他可用配置。此外,通过提交支持票请求并发函数执行更新,也变得更为简便,因为您不必将流量聚集到处理所有请求的单一Lambda函数,而是可以根据单个任务的流量请求特定的增加。
另一个优点是快速执行时间。考虑到设计用于单一任务的单一用途Lambda函数的业务逻辑,您可以更轻松地优化函数的大小,而无需其他方法中所需的额外库。这有助于减少冷启动时间,因为包大小较小。
尽管有这些好处,但仅依赖单一用途Lambda函数也存在一些问题。虽然冷启动时间有所缓解,但您可能会体验到更高数量的冷启动,特别是对于那些偶发或不常调用的函数。例如,删除Amazon DynamoDB表中的用户的函数,很可能不会像读取用户数据的函数那样频繁触发。此外,过多依赖单一用途Lambda函数可能会导致系统复杂性增加,特别是随着函数数量的增长。
良好的关注点分离有助于维护您的代码库,但代价是缺乏内聚性。在执行类似任务的函数例如API的写操作POST、PUT、DELETE中,您可能会在多个函数中重复代码和行为。此外,通过Lambda层或其他依赖管理系统更新共享的公共库时,需要在每个函数中多次更改,而不是在单个文件中进行原子性更改。对于运行时版本的更新等其它更改情况也是如此。
当多个工作负载使用单一用途的Lambda函数时,开发人员最终会在AWS账户中出现大量的Lambda函数。开发人员面临的主要挑战之一是更新公共依赖或函数配置。除非有清晰的治理策略来解决此问题例如使用Dependabot强制更新依赖关系,或在配置时检索参数化参数,否则开发人员可能会选择不同的策略。
因此,许多开发团队采取相反的方向,将与API相关的所有代码聚合到同一个Lambda函数中。
这种方法通常被称为Lambda大型一体函数,因为它将组成API的所有HTTP动词以及某些情况下多个API汇聚在同一函数中。
这种方法使您能够在应用程序的不同方面之间实现更高的代码内聚性和位置集成。在这种情况下,模块化在代码水平上得以表现,开发团队应用的设计模式,如单一责任、依赖注入和外观模式,都是维护大型代码库的关键。
然而,考虑到更少的Lambda函数,更新配置或在多个API之间实现新的标准相比单一责任方法更为简便。
此外,因每个HTTP动词的请求都调用相同的Lambda函数,因此不常用的代码部分更有可能获取到可用的执行环境,从而提升响应时间。
另一个需要考虑的因素是函数大小。当将多个动词与API的所有依赖和业务逻辑聚合在同一函数时,这可能会增加您的冷启动时间,特别是对于突发工作负载。客户应评估此方法的好处,尤其是在有严格SLA的应用程序中,冷启动将影响其正常运作。开发人员可以通过关注所使用的依赖关系以及实施诸如树摇、缩小和死代码消除等技术来缓解此问题。
这种粗粒度的方法并未允许您单独调整函数配置。您必须找到一个符合所有代码能力的配置,可能会增加内存大小并降低安全权限,而这可能与安全团队的要求存在冲突。
这两种方法都有各自的权衡,但还有第三种选项可以结合它们的好处。
通常,API流量更倾向于读取或写入,这迫使开发人员在某一侧优化代码和配置。
例如,考虑构建一个用户API,允许消费者创建、更新和删除用户,同时查找单个用户或用户列表。在这种情况下,您可以一次更改一个用户,而没有可用的批量操作,但可以在API请求中获取一个或多个用户。将API的设计划分为读写操作会形成如下架构:
速云梯登录对于写操作创建、更新和删除,代码的内聚性对许多原因是有益的。例如,您可能需要验证请求体,确保其中包含所有必需的参数。如果工作负载在写操作上很重,不常用的操作如删除可以从温暖的执行环境中受益。代码的聚合提高了reusability,相似操作之间的代码可减少组织项目时的认知负担。例如,通过共享库或Lambda层实现的功能。
在查看读取操作时,您可以减少与此函数捆绑的代码,从而提高冷启动速度,相较于写操作大幅优化性能。此外,您还可以在执行环境的内存中存储部分或完整的查询结果,以提高Lambda函数的执行时间。
这种方法的演变特性也有助于您。想象一下,如果这个平台变得更加流行,您将必须进一步优化API,通过引入缓存模式和ElastiCache和Redis来改进读取性能。此外,您决定在缓存失效时使用第二个优化读取能力的数据库来优化读取查询。
在写操作方面,您已与API消费者达成共识,认为只需接收和确认用户的创建或删除就足够,因为他们已完全接受分布式系统的最终一致性特性。
现在,您可以通过在Lambda函数之前添加SQS队列来改善写操作的响应时间。您可以批量更新写入数据库,从而减少处理引写操作所需的调用次数,而不是逐一处理每个请求。
命令查询责任分离CQRS是一种成熟的模式,它将数据变更或命令部分与查询部分分开。如果更新和查询对吞吐量、延迟或一致性有不同的要求,您可以使用CQRS模式将其分开。
虽然不必从一开始就实施完整的CQRS模式,但您可以在初始的读写实现中更轻松地演变,而无须对API进行大规模重构。
以下是三种方法的比较:
单一责任Lambda大型一体读写分离优点 强大的关注点分离 较少的冷启动调用 细粒度配置 更高的代码内聚性 更好的调试 更简单的维护问题 代码重复 粗粒度配置 复杂的维护 冷启动时间较长开发人员通常在单一责任函数与Lambda大型一体函数之间迁移,随着架构的发展,两者各自存在相对的权衡。本文展示了如何通过将工作负载划分为读写操作,结合这两种方法的最佳特征。

这三种方法对于设计无服务器API都是可行的,了解您优化的重点是做出最佳决策的关键。请记住,理解您的上下文和业务需求并在应用程序中表达,将引导您朝着可接受的妥协方向发展,从而满足特定工作负载的要求。保持开放的心态,寻找能够解决问题的解决方案,平衡安全性、开发人员体验、成本与可维护性。
欲获取更多无服务器学习资源,请访问Serverless Land。