交貨處理可能不需要順序,因為訊息順序處理和伸縮性是很難平衡的 - particular

banq發表於2019-04-23

嘗試應用嚴格的有序處理將對我們的系統施加人為限制。這是因為保證訊息排序在技術上非常困難,即使成功,也總是需要權衡諸如較低的訊息吞吐量和較低的可擴充套件性,這會妨礙系統成功的能力。

比如比薩店交付披薩時,不一定要根據訂單前後順序準備披薩,而是根據比薩的製作順序,有些披薩需要燒烤時間長一些,有一些需要短一點。

讓我們來看看為什麼從技術角度來保證有順序的交貨是多麼困難。

異常

在訊息處理程式碼中程式設計異常時會發生什麼?即使使用最強大的程式碼,異常仍然可能發生。作為開發人員,這對我們來說並不陌生。畢竟,這就是我們編寫單元測試的原因:防範意外。但並非一切都在我們的控制之下,而且很多都可能出錯。

我們可以使用因瞬態錯誤而丟擲異常的訊息。雖然並非所有錯誤都是嚴重的,但我們確實需要處理這個問題。很多時候我們可以簡單地重新排列失敗的訊息來解決問題。但是我們也應該能夠處理“毒藥”訊息,這些訊息會一直失敗,應暫時放在一邊,以便稍後重試。

我們是在幾秒鐘內還是幾天後重試一次有毒資訊是無關緊要的。重要的一點是,毒藥訊息已被處理,並且被重試後會從佇列中取走。結果是我們預期按順序到達的訊息現在正在按順序處理。

可伸縮性

當系統以訊息傳遞作為其基礎之一構建時,向外擴充套件伸縮的能力是無法按照事前預測計劃進行的,不幸的是,這使得有序交付幾乎無法支援。

向外擴充套件伸縮的能力是一個非常強大的功能。您可以通過讓更多伺服器處理訊息,無需購買功能更強大且更昂貴的硬體。每個伺服器基本上都在競爭訊息,儘可能多地處理訊息。回到我們的披薩店,這類似於購買更多的烤箱,一次烘焙更多的比薩餅,而不是升級現有的烤箱,以更快地處理相同數量的比薩餅。

雖然伺服器可以擴充套件,但它們彼此不瞭解。這通常不是問題,除了有序地交貨。需要按順序到達的訊息可能會在不同的計算機上處​​理。其中一臺機器可以更快地完成工作或完成工作,從而導致訊息無序處理。即使您沒有伸縮並且只有一臺伺服器,您的伺服器也必須使用單個執行緒處理訊息才能保證有順序(因此速度較慢),兩者類似。

回到現實世界

當物質世界中的事情發生故障時,事情通常會通過各種檢查和平衡來解決。我可能先在其他人前面點了我的披薩,但如果他們的披薩先做​​好,他們會先拿到他們的披薩。

有時我會在回家的路上訂一塊比薩餅。當我下訂單時,商店將告訴我大約需要多長時間。如果我早點到達那裡,我會支付它並等到它完成。但也許我的交通延遲或等到我正在第十次觀看的權力的遊戲節目結束。當我到達那裡時,我的披薩正等著我,所以我付錢給它並隨身攜帶。

這種情況的重要部分是兩個事件 : “披薩準備好了”與”披薩付款” ,可能發生故障。但兩者都需要在比薩餅交付之前完成。在系統建模術語中,我們可能有一個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提供了一個解決所有這些排序問題的工具,並提供了橫向擴充套件和樂觀併發等所有技術考慮因素,使您可以專注於業務需求。

總結

從技術角度來看,有順序交貨、處理錯誤以及擁有可擴充套件的系統三者具備幾乎是不可能的。同時,業務流程實際上不太可能需要有序交付。從業務和技術角度來看,我們都需要能夠適應不同的場景。我們實際需要的是一種處理這些替代方案的方法。

但是訊息將無序到達,應該允許它們無序到達。我們可以接受它,而不是試圖“修復”這個問題,而是提出問題並提供替代流程。我們不應該接受一種技術限制,迫使客戶等待,而他們的披薩變冷,只是因為其他客戶先訂購。

相關文章