簡體   English   中英

事件溯源聚合中的版本號?

[英]Version number in event sourcing aggregate?

我正在構建微服務。 我的微服務之一是使用 CQRS 和事件源。 集成事件在系統中引發,我將聚合保存在事件存儲中,同時更新我的​​讀取模型。

我的問題是,當我們針對該聚合更新事件流時,為什么需要聚合版本? 我讀到我們需要這個以保持一致性,並且要按順序重播事件,我們需要在保存之前檢查版本( https://blog.leifbattermann.de/2017/04/21/12-things-you-should-know- about-event-sourcing/ )我仍然無法解決這個問題,因為事件是按順序引發和保存的,所以我真的需要具體的例子來了解我們從版本中獲得什么好處以及為什么我們甚至需要它們。

非常感謝,

伊姆蘭

讓我描述一個聚合版本有用的情況:

在我們的reSove 框架中,聚合版本用於樂觀並發控制。

我會通過例子來解釋它。 假設InventoryItem聚合接受命令AddItemsOrderItems AddItems增加庫存商品的數量, OrderItems - 減少。 假設您有一個InventoryItem聚合 #123,其中包含一個事件 - ITEMS_ADDED ,數量為 5。聚合 #123 表示庫存中有 5 件商品。

因此,您的 UI 向用戶顯示有 5 件商品庫存。 用戶 A 決定訂購 3 件商品,用戶 B - 4 件商品。 兩者幾乎同時發出OrderItems命令,假設用戶 A 領先幾毫秒。

現在,如果您在內存中有一個聚合 #123 的單個實例,在單線程中,您沒有問題 - 來自用戶 A 的第一個命令將成功,將應用事件,狀態說數量為 2,所以第二個命令來自用戶 B 將失敗。

在分布式或無服務器系統中,來自 A 和 B 的命令將在單獨的進程中,如果我們不使用某些並發控制,這兩個命令都會成功並使聚合進入錯誤狀態。 有幾種方法可以做到這一點 - 悲觀鎖定、命令隊列、聚合存儲庫或樂觀鎖定。

樂觀鎖似乎是最簡單和最實用的解決方案:

我們說每個聚合都有一個版本 - 其流中的事件數。 所以我們的聚合 #123 有版本 1。

當聚合發出事件時,此事件數據具有聚合版本。 在我們的例子中,來自用戶 A 和 B 的ITEMS_ORDERED事件的事件聚合版本為 2。顯然,聚合事件的版本應該按順序增加。 所以我們需要做的只是放置一個數據庫約束,即元組{aggregateId, aggregateVersion}在寫入事件存儲時應該是唯一的。

讓我們看看我們的示例如何在具有樂觀並發控制的分布式系統中工作:

  • 用戶 A 為聚合 #123 發出命令OrderItem

  • 聚合 #123 從事件{version 1, quantity 5}恢復

  • 用戶 B 為聚合 #123 發出命令OrderItem

  • Aggregate #123 的另一個實例從事件中恢復(版本 1,數量 5)

  • 用戶 A 的聚合實例執行命令,它成功,事件ITEMS_ORDERED {aggregateId 123, version 2}被寫入事件存儲。

  • 用戶 B 的聚合實例執行命令,它成功,事件ITEMS_ORDERED {aggregateId 123, version 2}它嘗試將其寫入事件存儲並因並發異常而失敗。

  • 在用戶 B 的此類異常命令處理程序上,只需重復整個過程 - 然后聚合 #123 將處於{version 2, quantity 2}狀態,並且命令將被正確執行。

我希望這可以清除聚合版本有用的情況。

是的,這是正確的。 您需要版本或序列號以保持一致性。

你想要的兩件事:

  1. 正確排序
    通常事件本質上是冪等的,因為在分布式系統中,冪等的消息或事件更容易處理。 冪等消息是即使多次應用也會產生相同結果的消息。 使用固定值(例如 1)更新寄存器是冪等的,但將計數器加一則不是。 在分布式系統中,當 A 向 B 發送消息時,B 確認 A。再次。 現在 B 再次應用該消息,如果該消息不是冪等的,則最終狀態將出錯。 所以,你想要冪等的消息。 但是如果你沒有按照它們產生的順序應用這些冪等消息,你的狀態將再次出錯。 可以使用版本 ID 或序列來實現這種排序。 如果您的事件存儲是 RDBMS,則您無法在沒有任何類似排序鍵的情況下對事件進行排序。 在 Kafka 中,您也有偏移量 id,客戶端會跟蹤它已經消耗的偏移量

  2. 重復數據刪除
    其次,如果您的消息不是冪等的怎么辦? 或者,如果您的消息是冪等的,但消費者以不確定的方式調用某些外部服務,該怎么辦。 在這種情況下,您需要精確一次語義,因為如果您兩次應用相同的消息,您的狀態將是錯誤的。 在這里,您還需要版本 ID 或序列號。 如果在消費者端,您跟蹤已處理的版本 id,則可以根據 id 進行重復數據刪除。 在 Kafka 中,您可能希望在消費者端存儲偏移 id

基於評論的進一步澄清:

相關文章的作者假設 RDBMS 作為事件存儲。 版本 id 或事件序列應由生產者生成。 因此,在您的示例中,“已交付”事件的順序將高於“運輸中”事件。

當您想要並行處理事件時會出現問題。 如果一個消費者收到“delivered”事件而另一個消費者收到“intransport”事件怎么辦? 顯然,您必須確保特定訂單的所有事件都由同一個消費者處理。 在 Kafka 中,您可以通過選擇訂單 ID 作為分區鍵來解決此問題。 由於一個分區將僅由一個消費者處理,因此您知道在交付之前您總是會收到“在途”。 但是多個訂單將分布在同一消費者組內的不同消費者中,因此您可以進行並行處理。

關於聚合 id,我認為這與 Kafka 中的主題同義。 由於作者假設 RDBMS 存儲,他需要一些標識符來分隔不同類別的消息。 您可以通過在 Kafka 中創建單獨的主題以及每個聚合的消費者組來做到這一點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM