无服务器模式
在构建新的解决方案时,我倾向于反复使用一些架构模式。在本文中,我将概述我使用这些模式的原因,以及如何在 AWS 上以无服务器(通常是事件驱动)的方式实现它们。
存储优先
存储优先是我最常用的模式之一。我可以说,我设计的所有架构都充分利用了这一点。顾名思义,该模式的核心是使用 AWS 托管服务存储/捕获传入的请求/数据。如果 API 不需要返回任何数据,那么这是一个非常强大的模式。
通过存储数据,我们可以在处理失败时获得精确的副本。失败的例子可能包括但不限于:
- 无法处理数据
- 将数据存储到持久存储失败
- 调用下游第三方服务或 API 失败。
这是使用 Amazon API Gateway 处理请求的标准方法,我们喜欢处理传入的请求并将数据存储在 Amazon S3 中,它看起来像这样。
如果 AWS Lambda 函数现在无法处理数据、缺少调用 Amazon S3 的权限或出现任何其他故障,数据就会丢失。客户端会收到 4XX 错误,需要重试调用 API。这可能会在客户端中产生非常复杂的逻辑,令人非常沮丧。我们的 API 实现可以使用存储优先方法来解决这个问题,如下所示。
请求和数据将使用 API Gateway 中的服务集成存储在 Amazon SQS 中,即使我们的 Lambda 函数失败,请求在 SQS 中也是安全的,并且可以稍后再次处理。
现在,无需 API 网关和 SQS 队列即可以无服务器方式实现此模式。我们可以将 API 网关与 DynamoDB 结合使用,将 IoT Core 与 SQS 结合使用,还可以使用许多其他不同的方法。重要的是,请求和数据在处理之前就已存储。
断路器
我在很多分布式系统中都使用过断路器模式。它能够在下游服务不可用时快速失败。下游服务可以是同一应用程序中的服务,也可以是第三方服务。通过不调用系统已知不可用的服务,断路器模式可以避免性能问题,并且在分布式回显系统中也能很好地发挥作用,避免向不可用的服务发送过多的请求。
我使用断路器时,通常使用 AWS StepFunction 来实现,因此,如果只集成一个 Lambda 函数,我会选择与 StepFunction 集成,并在 StepFunction 内部完成工作。由于 StepFunction 集成了如此多的服务,因此实际上可以将所有工作都封装到断路器内部。图中的 Lambda Invoke 步骤将被替换为执行实际工作的服务,至于实际工作是其他 StepFunction、ECS Task 还是 API,则无关紧要。
断路器的实现看起来像这样。
我们首先通过 getItem 调用从 DynamoDB 获取电路状态。然后可以检查电路是断开还是闭合。如果 DynamoDB 表中没有记录或值为 CLOSED,则认为电路闭合。如果电路闭合,则执行工作。如果电路断开,则检查电路状态的上次更新是否超过 X 秒。如果时间已过,则执行工作以查看服务是否已恢复。否则,不执行任何工作,我们将快速失败。
执行工作时,Lambda 函数将使用内置重试机制重试三次。如果重试后工作仍然失败,则电路状态将更新为 OPEN,这会导致后续调用快速失败。如果工作成功,则电路状态将更新为 CLOSED。
如果我们现在将其与健康检查实现结合起来,几乎可以实现一个可以自行打开和关闭的全自动电路。它看起来可能像这样。
在此示例中,我们的下游服务通过 Lambda 函数实现,这些函数以 API 网关为前端。Lambda 函数将日志和指标写入 CloudWatch。我们可以使用日志事件筛选器从应用程序日志中创建其他指标,结合 Lambda 函数指标,我们可以创建智能 Cloudwatch 警报。当警报状态发生变化时,会发送到 Amazon EventBridge,后者可以调用 StepFunction,根据警报情况打开或关闭电路。
如果我们将其与浅层健康检查(从预定的 Lambda 函数调用)结合使用,该检查会将健康检查结果发送到 EventBridge。再次调用 StepFunction,并根据健康检查结果打开或关闭电路。
这样我们就得到了自动断路器。
存储优先和断路器
在研究了存储优先和熔断器模式之后,我们可以创建两者的组合。这种模式组合几乎可以称之为“超级模式”。通过确保优先存储请求,我们可以在熔断器再次闭合时处理它们。当熔断器仍然处于打开状态时,我们可以节省处理能力,并且不会调用下游服务。我们只需让请求停留在存储中,而不会有任何丢失数据的风险。
功能标志
“始终对所有功能进行功能标记”肯定是我听过同事说过的最好的话之一。这话千真万确,通过确保所有新特性和功能都可以通过功能标记控制,一旦出现问题,就可以将其关闭。别误会,我并不是说应该永远保留功能标记,这确实很糟糕。但是,我坚信,如果所有新功能从一开始就进行标记,开发起来会容易得多。新版本的发布也将受益于这种方法。当该功能发布到生产环境并稳定后,就可以移除这些标记了。
我已经写过关于如何在 AWS StepFunctions 中使用 AppConfig 作为功能标志的文章,请查看这篇文章以了解更多信息并获取更多详细信息。
Step Functions CRUD
正如本文开头所述,以上提到的所有模式均非我所发明。最后一个模式也不例外,但我认为它非常出色,而且我已经开始研究它了。所以我必须把它纳入其中。我第一次看到这个模式是在 James Beswick 在 re:Invent 大会上演讲构建无服务器 Espresso 时。这是一次精彩的演讲,您可以点击此处观看。
你们中的许多人可能都熟悉以下模式:集成 Lambda 的 API 网关。每个方法都有一个单独的集成。这是我多年来一直在使用的方法,也是我一直倡导的。每个方法一个 Lambda 函数,保持 Lambda 函数简洁并只执行一项任务。然而,正如 James 在 re:Invent 演讲中提到的,这种设计可能会很难一目了然。如果路径和方法众多,就会有大量的 Lambda 函数,快速概览、监控和调试可能会是一项非常艰巨的任务。
我绝不建议在 Lambda 函数中实现任何形式的方法路由。然而,StepFunctions 与 API Gateway 集成后,情况就完全不同了。利用 StepFunctions 强大的选择状态,我们可以将不同的方法路由到不同的路径。这将使方法处理过程更加易于概览,并且在发生故障时也更容易调试。
那么它是如何工作的呢?让我们先来看一个示例状态机。
我们从 API 网关获取请求,首先使用选择状态来确定方法,然后根据方法将请求发送到不同的路径。在每条路径中,我们都可以使用 stepFunction 内置的错误处理和重试机制。这创建了一个非常强大且简单的 API 集成,并具有出色的调试和错误处理功能。在上面的示例中,我用一个 Lambda 函数来说明这一点。在实际应用中,流程当然会更长。
另一种选择是,除了将每个流程都放在状态机中之外,还可以为每个流程创建单独的状态机,并使用 StepFunctions 中的支持来启动单独的状态机。这样就可以拆分并隔离每个流程,从而实现“一个 StepFunction 对应一个作业”的方法。
我仍会在实际项目中使用这种模式,但在我看来,这只是时间问题。我认为这是一个很好的模式,也是创建 API 集成的方法。我唯一能想到的风险是,如果路径数量激增,而且不仅仅用于几种不同的方法,那么就会出现问题。但所有事情都是这样,如果使用不当,就会变得难以处理。
大声呼喊并感谢 James 和 Serverless Espresso 团队的其他成员创造了这个!!
最后的话
本文概述了我使用的一些不同架构模式,以及如何使用无服务器技术在 AWS 中实现它们。敬请期待未来更多深入的文章。
不要忘记在LinkedIn和Twitter上关注我以获取更多内容,并阅读我的其他博客
鏂囩珷鏉ユ簮锛�https://dev.to/aws-builders/serverless-patterns-4439