在事件驱动的世界中处理异常

Jam*_*mes 10 event-driven-design event-driven

我试图了解如何使用微服务(使用 apache kafka)在事件驱动的世界中处理异常。例如,如果您采用以下订单场景,其中需要在完成订单之前执行以下操作。

  • 1) 向支付服务提供商授权支付
  • 2)从库存中保留该项目
  • 3.1) 通过支付服务提供商捕获付款
  • 3.2) 订购商品
  • 4) 发送电子邮件通知接受订单并附上收据

在这种情况下的任何阶段,都可能出现故障,例如:

  • 该商品不再有库存
  • 支付信息有误
  • 收款人使用的账户没有可用资金
  • 外部调用(例如对支付服务提供商的调用)失败,例如停机

您如何跟踪每个阶段已被要求和/或完成?

你如何处理出现的问题?你将如何通知前端失败?

Edw*_*rzo 18

您描述的一些事情不是错误或异常,而是您应该在分布式架构中考虑的替代流程。

例如,商品缺货是您业务流程中完全有效的替代流程。一种可能需要人工干预的。您可以将消息移动到单独的队列,并提供一些 UI,操作员可以在其中处理问题、解决问题并使事件流继续。

您描述的付款问题也有类似的说法。如果订单无法成功结算,则需要人工操作员调查并解决问题。就此而言,您的设计必须考虑该替代流程作为其中的一部分,并使其能够在消息最终进入需要人员查看的队列时以某种方式进行干预。

这些情况应该与程序抛出的错误或异常区分开来。根据具体情况,这些情况实际上可能需要将消息移动到死信队列 (DLQ) 以供工程师查看。

这是一个非常广泛的主题,可以写整本书来讨论这个问题。

我相信您可能会受益于对以下概念的更多理解:

补偿交易背后的想法是,每个 ying 都有其 yang:如果您有一个可以下订单的交易,那么您可以通过取消该订单的交易来撤消该交易。后一种交易是补偿交易。因此,如果您执行了许多成功的交易,然后其中一个失败了,您可以追溯您的步骤并补偿您所做的每笔成功交易,从而恢复它们的副作用。

我特别喜欢REST from Research to Practice一书中的一章。它的第 23 章(Towards Distributed Atomic Transactions over RESTful Services)深入解释了Try/Cancel/Confirm 模式

一般而言,这意味着当您进行一组交易时,它们的副作用在交易协调器得到确认它们都成功之前不会生效。例如,如果您在 Expedia 上预订并且您的航班有两条不同航空公司的航段,那么一笔交易将预订美国航空公司的航班,另一笔交易预订美国联合航空公司的航班。如果您的第二次预订失败,那么您希望补偿第一次预订。但不仅如此,您还想避免第一个预订有效,直到您能够确认两者。因此,初始交易进行预订,但保持其副作用等待确认. 第二个保留也会做同样的事情。一旦交易协调器知道一切都被保留了,它可以向所有各方发送确认消息,以便他们确认他们的保留。如果未在合理的时间范围内确认预订,受影响的系统将自动撤销预订。

企业集成模式》一书对如何实现这种事件协调有一些基本的想法(例如,查看流程管理器模式并与路由滑动模式进行比较,这些模式与微服务世界中的编排与编排相似)。

如您所见,根据分布式工作流的复杂程度,能够补偿事务可能会很复杂。流程经理可能需要跟踪每个步骤的状态,并知道何时需要撤消整个过程。这几乎就是微服务世界中Sagas 的想法。

《微服务模式》一书有一整章名为“使用 Sagas 管理事务”,其中详细介绍了如何实现此类解决方案。

我通常还会考虑的其他一些方面如下:

幂等性

我相信在分布式系统中成功实现服务事务的关键在于使它们具有幂等性。一旦您可以保证给定的服务是幂等的,那么您就可以安全地重试它,而不必担心会导致额外的副作用。但是,仅仅重试失败的事务并不能解决您的问题。

瞬态与持久性错误

在重试服务事务时,您不应该仅仅因为失败而重试。您必须首先知道它失败的原因,并根据错误重试与否可能有意义。某些类型的错误是暂时的,例如,如果一个事务由于查询超时而失败,那么重试可能没问题,并且很可能第二次成功;但是如果您遇到数据库约束冲突错误(例如,因为 DBA 向字段添加了检查约束),则重试该事务没有意义:无论您尝试多少次,它都会失败。

接受错误作为替代流程

正如我在回答开头所提到的,并非一切都是错误。有些事情只是替代流程。

在这些服务间通信(计算机对计算机交互)的情况下,当工作流的给定步骤失败时,您不一定需要撤消在先前步骤中所做的一切。您可以将错误作为工作流程的一部分。编目错误的可能原因,并使它们成为仅需要人工干预的替代事件流。这只是完整编排中的另一个步骤,需要一个人进行干预以做出决定、解决与数据的不一致或只是批准走哪条路。

例如,可能在您处理订单时,支付服务失败,因为您没有足够的资金。因此,取消其他所有内容都没有意义。我们所需要的只是将订单置于某个问题解决者可以在系统中解决它的状态,一旦修复,您就可以继续工作流程的其余部分。

事务和数据模型状态是关键

我发现这种类型的事务性工作流需要对模型必须经历的不同状态进行良好的设计。与尝试/取消/确认模式的情况一样,这意味着最初应用副作用而不必使数据模型对用户可用。

例如,当您下订单时,您可能将它以“待处理”状态添加到数据库中,而不会出现在仓库系统的 UI 中。确认付款后,订单将显示在 UI 中,以便用户最终可以处理其发货。

这里的难点在于如何设计事务粒度,即使您的事务工作流程的一个步骤失败,系统仍会保持有效状态,一旦故障原因得到纠正,您就可以从该状态恢复。

设计分布式事务工作流

因此,如您所见,设计一个以这种方式工作的分布式系统比单独调用分布式事务服务要复杂一些。现在,每个服务调用都可能由于多种原因而失败,并使您的分布式工作流处于不一致的状态。并且重试事务不一定总能解决问题。并且您的数据需要像状态机一样建模,以便在整个编排成功之前应用副作用但不会确认。

这就是为什么整个事情可能需要以与您通常在单体客户端 - 服务器应用程序中所做的不同的方式进行设计。在解决冲突时,您的用户现在可能是设计的解决方案的一部分,并考虑到事务编排可能需要数小时甚至数天才能完成,具体取决于他们的冲突是如何解决的。

正如我最初所说,这个话题太广泛了,可能需要一个更具体的问题来详细讨论这些方面中的一两个。

无论如何,我希望这对您的调查有所帮助。