引言
2014 年我们发布了 Lambda 服务,掀起了 Serverless 革命。现在越来越多的人谈论 Serverless 的未来。事实上,我们自己构建的应用程序中有一半以上是基于 Lambda 的,Serverless 能够最大限度地利用云计算的价值。现在,越来越多的客户正在决定采用 Serverless。这里,我们不只是在谈论 Lambda、API Gateway、Step Functions 或 EventBridge 等 Serverless 服务,而是如何使用 Serverless 实现快速原型设计、成本可控、高可用、自动扩展以及高效运维,这些都是用户在选择初始应用架构时需要考虑的关键设计因素。
Serverless 是试验、学习和超越竞争对手的伟大推动力。
在应用设计领域,设计模式是架构的基石,每种设计模式都来自一个反复出现的常见架构问题,通过总结该问题的解决方案,最终形成可复用的模式。这样,来自五湖四海的架构师们,就能根据这些设计模式,站在前人的经验之上,针对现实问题,明智地选择满足要求的架构设计。本文,我们将尝试总结一些有关 Serverless 常见的应用设计模式。
反模式示例
在逐个分析 Serverless 应用设计模式之前,我们可以先聊聊那些“反模式”,“不是什么”比“是什么”更容易掌握。
1、Lambda 函数成单体
这种使用方式在用户中相当常见,talk is cheap, show me the code,写一个臃肿的 Lambda 函数,里面包含了各种事件触发所需的处理逻辑,从零开始的效率很高,随着复杂性的增加,这会导致 Lambda 函数的代码包变大,冷启动时间变长,运行速度变慢,函数的 IAM 角色必须授予所有资源的权限,违反了最小权限原则,对该 Lambda 函数所需依赖的升级更具风险,不同的开发者需协作维护,测试覆盖率难以提升,团队扩展也受到影响。单任务的 Lambda 函数逻辑是定义拆解边界的起点,未来我们会来探讨将事件风暴的思路应用到 Serverless 设计中。
2、Lambda 函数成编排器
复杂的工作流逻辑是现实应用的真实反映,在 Lambda 函数中实现整个工作流,会导致代码难以阅读、理解和维护,而且必须细心处理错误和重试逻辑,这使得复杂性成倍提升,质量保障难度增加。使用 Step Functions 服务,利用版本化的 JSON 定义状态机,对所需的工作流程进行编排才是合理的解决之道。在状态机中可以处理嵌套的工作流逻辑、错误和重试。不同版本的工作流,可以很方便对生产系统进行升级或回滚,此外还可以减少自定义代码,使应用程序更易于测试和维护。虽然 Step Functions 最适合界限上下文的工作流,但为了协调多服务之间的状态更改,请改为使用 EventBridge,利用事件总线,根据路由规则简化编排。
3、Lambda 调用 Lambda
大多数编程语言都支持在代码中同步调用函数的方法。在这种情况下,调用者会一直等待,直到函数返回响应。这是一种反模式。首先成本考虑,Lambda 服务是按调用时间进行付费,这种模式不符合成本可控原则。其次,在嵌套调用中,错误处理会变得更加复杂,水桶效应,即最慢的功能影响了整个工作流的效率。再次,调用者与被调函数的并发性有共生关系,而并发性在繁忙的系统中容易造成性能瓶颈。
有两种方法可以避免这种模式。一种是在 Lambda 函数之间使用 SQS 队列,解耦这两个功能。第二种是使用 Step Functions,可以帮助减少编排工作流所需的自定义代码,着重在错误和重试处理,而 Lambda 函数仅包含业务逻辑即可。
4、事件死循环
Lambda 函数是事件驱动的,Lambda 函数本身也可以产生新的事件,所以这中间处理不善可能引起事件死循环。虽然大多数编程语言都存在无限循环的可能性,但这种反模式在 Serverless 中会消耗更多资源,主要的原因就在于支持针对流量的自动扩展,事件循环会导致 Lambda 的并发扩展,Lambda 的并发扩展会生成更多事件。在这种情况下,可以手动在 Lambda 控制台中使用“Throttle”按钮,将函数并发缩减为零以打破死循环。建议使用正向触发器,保留并发,利用 CloudWatch 监控和警报。
更多详细内容,可参考 James Beswick 的文章《Operating Lambda: Anti-patterns in event-driven architectures – Part 3》。
常见的设计模式
当前,我们正在构建越来越复杂的平台,同时也努力解决不断变化的业务需求,并按时交付给越来越多的用户。持续快速交付优质软件是用户的核心业务优势。使用现代架构、框架和实践加速开发过程具有战略意义。Serverless 非常适合实现快速、持续的软件交付,无需考虑管理基础架构、配置或规划需求和规模,将代码构建为更小、更简单的单元,这些单元易于理解、更改和部署到生产环境,使我们能够交付业务价值并快速迭代。设计模式是推广最佳实践和共享解决方案的有力武器,预见可行经过验证的 Serverless 设计模式来解决现代云架构中的常见需要。Peter Sbarski 在他的《Serverless Architectures on AWS》一书中列出了 Serverless 架构中常见的五种设计模式,当然这些并不是一个详尽的集合。
Serverless Land 网站,推出了更多的模式集合,并提供了 Serverless 模版示例代码。可以选择合适的服务,生成 SAM 模板复制粘贴到您的代码中最难过。我们将继续添加新的模式,并接受社区的贡献来持续完善这个模式集合,详细可参考这里:
http://serverlessland.com/patterns/
1、命令模式
在软件工程中,命令模式是一种行为设计模式,将请求封装为包含该请求所有信息的独立对象,允许将请求作为方法参数传递、延迟或排队请求的执行,并支持可撤消的操作。命令模式允许将操作的调用者与执行所需处理的实体分离。
在实践中,这种模式可以简化 API 网关的实现,因为不希望或不需要为每种类型的请求创建一个 REST API,还可以使版本控制变得更加简单。Lambda 函数(命令)可以与不同版本的客户端一起使用,并调用客户端所需的不同服务。该模式可解耦调用者和接收者,将参数作为对象传递,并允许客户端使用不同的请求进行参数化,以减少组件之间的耦合,有助于系统的可扩展性。
下图就是一个很好的例子,该服务集中了客户端的请求,以减少通信开销的影响,并向下游服务发出分解的请求,在响应到达时收集、存储和聚合响应,作为一个响应,返回给调用者。
2、消息传递模式
异步消息传递是大多数服务集成的基础,已被证明是企业架构的最佳策略,允许构建松耦合的架构,以克服远程服务通信的限制,如延迟和不可靠性。
下图所示的消息传递模式在分布式系统中很流行,允许开发者从彼此的直接依赖中解耦出来,并允许将事件/记录/请求存储在队列中,构建可扩展且健壮的系统。如果消费者下线,消息将保留在队列中,仍然可以等消费者恢复后继续处理。
一个消息队列的例子,其中包含,一个发送者可以发布到队列,一个接收者可以从队列中检索消息。实施方面,可以使用 SQS 构建此模式。
消息队列包含多个发送方/接收方的时候,而每个 SQS 队列通常只有一个接收器。如果需要有多个消费者,一个直接的方法是在系统中引入多个队列,可以将 SQS 与 SNS 结合使用。SQS 队列可以订阅一个 SNS 主题,将消息推送到 SNS 主题,SQS 会自动将消息推送到所有订阅的队列。
Kinesis Streams 是 SQS 的替代品,尽管它没有某些功能,例如消息的死信。Kinesis Streams 与 Lambda 集成,提供有序的记录序列,并支持多个使用者。
这是一种用于处理工作负载和数据处理的流行模式。队列用作缓冲区,因此如果消费者崩溃,数据不会丢失,仍将保留在队列中,直到消费者恢复并再次开始处理。消息队列也可以使未来的更改更容易,因为函数之间的耦合更少。在具有大量数据处理、消息和请求的环境中,尽量减少直接依赖于其他函数,可改用消息传递模式。
3、优先队列模式
使用 Serverless 架构的一大好处是容量规划和可扩展性,但在某些情况下,希望控制系统处理消息的方式和时间,例如将不同的队列、主题或流来将消息提供给函数。
这也就意味着,对于不同优先级的消息拥有完全不同的工作流。优先级高的消息,会通过使用更昂贵的服务和容量更大的 API 来加快工作流,而不需要尽快处理的消息则使用不同的工作流。
此模式涉及创建和使用完全不同的 SNS 主题、Kinesis Streams、SQS 队列、Lambda 函数,甚至第三方服务。当需要处理具有不同优先级的消息时,此模式适用,可以通过不同工作流的实现,构建不同的服务和 API,满足多种类型的用户需求。
4、扇出模式
扇出是许多用户熟悉的一种消息传递模式。通常,扇出模式用于将消息推送到特定队列或消息管道订阅的所有客户端。
此模式通常使用 SNS 主题实现,当向主题添加新消息时,允许调用多个订阅者。以 S3 为例。将新文件添加到存储桶时,S3 可以使用文件的消息,调用单个 Lambda 函数。
但如果需要同时调用两个、三个或更多 Lambda 函数怎么办?并行执行更多的 Lambda 函数,答案是使用 SNS 的扇出模式。
SNS 主题是可以有多个发布者和订阅者(包括 Lambda 函数)的消息传递渠道。当新消息添加到主题时,会强制并行调用所有订阅者,从而导致事件扇出。
回到前面讨论的 S3 示例,可以将 S3 配置为将消息推送到 SNS 主题,同时调用所有订阅的函数,而不是调用单个 Lambda 函数。这是创建事件驱动架构和并行执行操作的有效方法。
同时调用多个 Lambda 函数,此模式很适用。如果 SNS 主题无法传递消息或函数无法执行,将尝试并重试调用 Lambda 函数。
此外,扇出模式不仅可以用于调用多个 Lambda 函数。SNS 主题支持其他订阅者,例如电子邮件和 SQS 队列。向主题添加新消息可以同时调用 Lambda 函数、发送电子邮件或将消息推送到 SQS 队列。
5、管道和过滤器模式
管道和过滤器模式的目的是将复杂的处理任务分解为一系列在管道中可管理、分散的服务。用于转换数据的组件,传统上称为过滤器,而将数据从一个组件传递到下一个组件的连接器,称为管道。Serverless 架构非常适合这种模式,特别是对于需要多个步骤才有结果的任务类型,非常有用。
建议将每个 Lambda 函数编写为细粒度的任务,并牢记单一任务原则。输入和输出应该明确定义。
每当有一项复杂的任务时,请尝试将其分解为一系列管道,并应用以下规则:
- 确保 Lambda 函数的功能遵循单一任务原则
- 使用函数幂等,也就是说,函数应该始终为给定的输入产生相同的输出
- 明确定义函数的接口,确保清楚地说明输入和输出
- 函数的使用者不必知道如何工作,但必须知道如何使用以及每次期望的输出是什么
总结
本文重点介绍了 Serverless 的反模式和常见的设计模式,在用户开始构建初始架构之前,了解和考虑这些至关重要。我们讨论的内容包括以下反模式:
- Lambda 函数成单体
- Lambda 函数成编排器
- Lambda 调用 Lambda
- 事件死循环
同时,我们也介绍了以下这些 Serverless 常见的设计模式:
- 命令模式
- 消息传递模式
- 优先队列模式
- 扇出模式
- 管道和过滤器模式
出处:https://xie.infoq.cn/article/7e34571c7ca4f7a6a5f5b4a77