如何在基于事件、消息驱动的微服务架构中,在故障情况下恢复状态

21

在微服务架构的背景下,消息驱动、异步、基于事件的设计似乎越来越受欢迎(参见此处此处以及反应式宣言-消息驱动特性),而不是同步的(可能是基于REST的)机制。

在这个背景下,假设一个过度简化的订单系统如下所示:

ordering system

以及以下的消息流:

  • 从某个来源(Web / 移动等)下单
  • 订单服务接受订单并发布CreateOrderEvent
  • 库存服务对CreateOrderEvent作出反应,进行一些库存操作,并在完成后发布InventoryUpdatedEvent
  • 然后发票服务对InventoryUpdatedEvent做出反应,发送发票并发布EmailInvoiceEvent

所有服务都正常运行,我们愉快地处理订单...大家都很开心。 然后,库存服务因某种原因停止工作。

假设事件总线上的事件以“非阻塞”的方式流动。也就是说,如果消息发布到中央主题并且没有服务从中读取,则消息不会在队列中堆积(我试图传达的是一个事件总线,即使事件发布到总线上,它也会直接流过去而不会排队 - 在这一点上忽略使用的消息平台/技术)。这意味着,如果库存服务停机5分钟,那么在此期间通过事件总线传递的CreateOrderEvent现在已经“消失”或未被库存服务看到,因为在我们过度简化的系统中,没有其他系统对这些事件感兴趣。
我的问题是:库存服务(以及整个系统)如何恢复状态,以确保不会错过/未处理任何订单?
3个回答

11

好问题!这里基本上有三个因素在起作用:

  1. 如果服务出现故障,则需要重新播放可能被忽略的任何事件,以保持一致性。
  2. 事件按时间发生时,它们之间存在“这个先发生,那个后发生”的顺序。
  3. 可能会(但不一定)有其他方对云中的事件进行监督,以确保实现某种状态。

对于#1和#2,您希望拥有某种事件的持久日志。传统的消息队列/主题可以提供此功能,尽管您必须考虑在交易/异常/故障行为方面可能会处理消息时的乱序情况。类似Apache Bookkeeper、Apache Kafka、AWS Kinesis等更简单的日志可以按顺序存储/保留这些类型的事件,并将其留给使用者按顺序处理/过滤重复内容/分区流等。

对于第3点,我认为是一个状态机。但是您可以自己实现状态机。基本上,该状态机跟踪已经发生的事件,并根据其他系统中的事件转换到允许的状态(并可能参与发射事件/命令)。

例如,真实世界中的一个用例可能看起来像是“代管”,当您试图关闭房屋时。代管公司不仅处理财务交易,而且通常与房地产经纪人合作,协调整理文件,签署文件,转移资金等。在每个事件之后,代管将从“等待买方签名”更改为“等待卖方签名”然后到“等待资金”再到“成功关闭”...他们甚至有这些事件的截止日期等,并且如果资金未被转移就可以转换到另一种状态,比如“交易关闭,未完成”或其他。

在你的例子中,这个状态机会监听发布/订阅频道并捕获这些状态,运行计时器,发出其他事件以推动相关系统的进展。它不一定会"编排"它们本身,但它会跟踪进度,在需要的地方强制执行超时和补偿。这可以实现为流处理器、过程引擎或(我的建议是)一个简单的"托管"服务。
实际上还有更多需要跟踪的事情,例如如果"托管"服务崩溃/失败会发生什么,它如何处理重复的事件,它如何处理意外事件给出了状态,它如何对重复事件做出贡献等等...但希望这已经足够开始了。

感谢您的迅速回复。在Kafka的上下文中,选项1和2非常有趣。鉴于所有服务都是幂等的,那么使用我的示例,一旦库存服务恢复正常,它就可以从最后已知的偏移开始读取并“赶上”进度。选项3似乎暗示了CQRS中的Saga模式,这是一个公平的假设吗? - Donovan Muller

2
我将提供架构师的答案而不是深入细节。希望你不介意。
首先建议解耦所有概念:事件、消息、总线和/或队列以及异步。只要你还没有决定使用哪种软件来实现总线,这会打开更多可能性。
从架构角度来看,如果需要“必须交付”类型的场景,当服务失败时,将持久化消息。是的,系统中可能需要一些清理工作,因为事情总会发生,但首先专注于保证交付问题。我首先看到两个基本选项,可以进行扩展(可能还有更多,但这些已足以开始思考问题)。
1.库存服务处理从队列中拉取消息。在这种方法中,服务重新运行并查找任何消息。 2.“总线”保证交付。当出现故障时,它会等待服务恢复(可以ping来查看是否恢复或服务可以重新注册为订阅者(企业服务总线类型的场景))。
仅因为系统是异步和基于事件的,并不意味着您不能实现某种类型的保证交付。队列是一个选择(您似乎放弃了这个想法?),但在订阅者再次上线后持久存在并重试的总线是另一个选择。您可以在不阻塞的情况下持久化。
另一个问题是消息使用哪些令牌将它们同步回到手头的业务功能,但我假设您已经在系统中以某种方式处理了这个问题。唯一的概念可能是让所有系统都尊重令牌并尊重其他系统,在失败的情况下返回消息。
请注意,从业务角度来看,异步通信并不意味着在接触点处“点火并忘记”。你可以返回消息而不使用每个信息的异步方法。我指的是库存系统启动时可能会处理消息并发送到UI端的应用程序,它可以返回“忘记它,你太慢了”,因此交易被返回到其原始状态(不存在?)。
我没有足够的信息(或时间?)来建议哪种方法最适合您的架构,因为细节还有点高级,但希望这激发一些思考。
希望这有意义,因为我基本上是在我的ADHD状态下进行了大脑到键盘的操作。;-)

感谢您抽出时间回复。我在问题中定义的系统纯属虚构,仅用于设置背景。我有意排除了基于队列的选项,以探索系统本身如何处理故障情况,而不是特别依赖于用于事件总线的消息代理。 - Donovan Muller
我作为一名顾问看待软件的方式是,你有一个可以通过自己编程或产品来解决的概念。无论你是否构建了容错性,或者将其留给总线,这都需要更多的信息来做出决定。但是,我希望我的回答能帮助你思考你的练习,并想出可行的方案。 - Gregory A Beamer

-1
首先,我们构建的系统通常有一个目的,即通过让客户感到满意并不断回流来增加收入和利润。因此,源自客户行动的消息/事件必须被处理(假设所涉及的公司将客户体验作为优先事项......愿意投资其中)。
顺便说一下,与所有其他内部关系不同,我们希望客户-企业之间的关系是紧密耦合的。因此,在这些情况下,这是“权威”而不是自治的例子。我们通过品牌来保证SLA。
但是,消息重要性的范围应该比“必须交付”还要细化,反映优先级。类似于能力变得更细化(微服务)。稍后会更多地谈论这个问题。
因此,确保订阅者处理消息/事件的目标可以通过确保服务永远不会停机(如MS Orleans中的“虚拟演员”概念)或将更多的错误处理逻辑放入传递机制中来实现。
后者似乎更加集中式/耦合而不是自治/解耦。但是,如果您假设服务并不总是可用(正如我们应该做的那样),则需要考虑删除有关“瞬态”消息的其他假设。

第一种选择将如何保证可用性的决策留给服务和敏捷团队,因此性能是通过输出指标来衡量的。

此外,如果作为封装能力的服务保证高水平的服务水平(“永不停机”),那么通过调整消息优先级以及向系统注入新服务和事件来连续适应整个系统(=企业)的结果控制。

另一个重要方面是同步架构(=基于调用堆栈)提供了三个特性,异步架构(事件驱动)为了减少依赖而没有展示:协调、继续和上下文(参见Hohpe,“无调用堆栈编程”,2006年)。

我们仍然需要这些功能来满足客户在业务层面的需求,因此它们需要在其他地方得到覆盖。 Hohpe建议,松散耦合系统的配置和监视需要一个额外的代码层,该层与核心业务能力一样重要(复杂事件处理以理解事件之间的关系)

这些现代CEP系统必须处理大量数据、不同的速度、结构和正确性水平,可以在现代数据处理和大数据系统(例如Spark)之上实现,用于敏捷团队(改进其服务)和管理团队在其级别上的理解、决策和优化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接