交货处理可能不需要顺序,因为消息顺序处理和伸缩性是很难平衡的 - particular

19-04-23 banq
                   

尝试应用严格的有序处理将对我们的系统施加人为限制。这是因为保证消息排序在技术上非常困难,即使成功,也总是需要权衡诸如较低的消息吞吐量和较低的可扩展性,这会妨碍系统成功的能力。

比如比萨店交付披萨时,不一定要根据订单前后顺序准备披萨,而是根据比萨的制作顺序,有些披萨需要烧烤时间长一些,有一些需要短一点。

让我们来看看为什么从技术角度来保证有顺序的交货是多么困难。

异常

在消息处理代码中编程异常时会发生什么?即使使用最强大的代码,异常仍然可能发生。作为开发人员,这对我们来说并不陌生。毕竟,这就是我们编写单元测试的原因:防范意外。但并非一切都在我们的控制之下,而且很多都可能出错。

我们可以使用因瞬态错误而抛出异常的消息。虽然并非所有错误都是严重的,但我们确实需要处理这个问题。很多时候我们可以简单地重新排列失败的消息来解决问题。但是我们也应该能够处理“毒药”消息,这些消息会一直失败,应暂时放在一边,以便稍后重试。

我们是在几秒钟内还是几天后重试一次有毒信息是无关紧要的。重要的一点是,毒药消息已被处理,并且被重试后会从队列中取走。结果是我们预期按顺序到达的消息现在正在按顺序处理。

可伸缩性

当系统以消息传递作为其基础之一构建时,向外扩展伸缩的能力是无法按照事前预测计划进行的,不幸的是,这使得有序交付几乎无法支持。

向外扩展伸缩的能力是一个非常强大的功能。您可以通过让更多服务器处理消息,无需购买功能更强大且更昂贵的硬件。每个服务器基本上都在竞争消息,尽可能多地处理消息。回到我们的披萨店,这类似于购买更多的烤箱,一次烘焙更多的比萨饼,而不是升级现有的烤箱,以更快地处理相同数量的比萨饼。

虽然服务器可以扩展,但它们彼此不了解。这通常不是问题,除了有序地交货。需要按顺序到达的消息可能会在不同的计算机上处​​理。其中一台机器可以更快地完成工作或完成工作,从而导致消息无序处理。即使您没有伸缩并且只有一台服务器,您的服务器也必须使用单个线程处理消息才能保证有顺序(因此速度较慢),两者类似。

回到现实世界

当物质世界中的事情发生故障时,事情通常会通过各种检查和平衡来解决。我可能先在其他人前面点了我的披萨,但如果他们的披萨先做​​好,他们会先拿到他们的披萨。

有时我会在回家的路上订一块比萨饼。当我下订单时,商店将告诉我大约需要多长时间。如果我早点到达那里,我会支付它并等到它完成。但也许我的交通延迟或等到我正在第十次观看的权力的游戏节目结束。当我到达那里时,我的披萨正等着我,所以我付钱给它并随身携带。

这种情况的重要部分是两个事件 : “披萨准备好了”与”披萨付款” ,可能发生故障。但两者都需要在比萨饼交付之前完成。在系统建模术语中,我们可能有一个DeliveryService,它依赖于来自PaymentService的OrderPaid消息和来自KitchenService的PizzaPrepared消息。如果首先获得OrderPaid消息,则它无法交付,因为它不知道它的订单中的披萨是否已经准备好。在这种情况下,您可以想象客户会定期询问DeliveryService(即收银员),看看他的披萨是否已经完成。

在软件中建模无序消息

相反,我们可以使用名为sagas的NServiceBus功能。这些是消息驱动的状态机,允许我们协调业务流程。Sagas自动存储状态,处理并发,并可以帮助我们协调长期运行的业务流程。

让我们来看看NServiceBus中的一个Saga如何处理无序到达的消息。通过一个小状态,它可以记住已经发生的事情并根据这些约束采取行动。为了简化代码,我们将使用两个标志。

class DeliveryPolicy : Saga
{
  public Handle(OrderPaid message)
  {
    Data.OrderPaid = true;
    VerifyIfPizzaCanBeDelivered();
  }

  public Handle(PizzaPrepared message)
  {
    Data.PizzaPrepared = true;
    VerifyIfPizzaCanBeDelivered();
  }

  private VerifyIfPizzaCanBeDelivered()
  {
    if (Data.OrderPaid && Data.PizzaPrepared)
    {
      // ... send message that pizza can be delivered
    }
  }
}

在此示例中,当任一消息到达时,Saga的状态会发生变化。然后检查此状态以查看是否应继续交付。我们假设PizzaPrepared消息首先到达。它会将订单标记为已准备好,然后检查是否已满足所有条件;如果还没有准备好,Saga回到等待holding模式,直到OrderPaid消息到来。此时,VerifyIfPizzaCanBeDelivered确定已满足所有条件,我们可以继续完成订单。

但如果OrderPaid消息首先到达怎么办?也许KitchenService复制了订单但没有及时完成。在这种情况下,Saga也几乎做完全相同事情,它将订单标记为已付款,然后检查其内部状态以查看是否已满足所有条件以继续。如果还没有,订单一直等待holding到PizzaPrepared事件到来并完成提供披萨的要求。

Sagas提供了一个解决所有这些排序问题的工具,并提供了横向扩展和乐观并发等所有技术考虑因素,使您可以专注于业务需求。

总结

从技术角度来看,有顺序交货、处理错误以及拥有可扩展的系统三者具备几乎是不可能的。同时,业务流程实际上不太可能需要有序交付。从业务和技术角度来看,我们都需要能够适应不同的场景。我们实际需要的是一种处理这些替代方案的方法。

但是消息将无序到达,应该允许它们无序到达。我们可以接受它,而不是试图“修复”这个问题,而是提出问题并提供替代流程。我们不应该接受一种技术限制,迫使客户等待,而他们的披萨变冷,只是因为其他客户先订购。

                   

3