DDD福音:Zeebe是一個類似Kafka的可擴充套件的分散式事件溯源工作流引擎

banq發表於2019-07-19

許多人認為工作流自動化僅用於人工任務管理等慢速和低頻用例,這體現了當前工作流技術在可擴充套件性方面的侷限性,傳統工作流引擎基於關聯式資料庫,因此它們自然會受到資料庫處理的限制,即使這對大多數公司來說已經足夠了,但是肯定有一些有趣的用例需要更高的效能和可擴充套件性,例如處理需要在非常高的負載下進行軟實時保證的金融交易。

Zeebe是一個沒有任何中心元件的真正的分散式系統,利用Raft Consensus Algorithm等概念實現可擴充套件性和彈性。Zeebe使用事件溯源和事件流概念以及複製的僅附加日誌,分割槽允許擴充套件。根據反應性宣言,它被設計為反應Reactive系統。

Zeebe與Apache Kafka屬於同一類系統,每秒同樣能夠處理相當巨大的事件數量,快於 Camunda 7.8幾百倍。

那麼怎麼能實現這個呢?一個重要的想法是構建一個事件溯源系統。

事件源工作流引擎

傳統工作流引擎捕獲資料庫表中工作流例項的當前狀態。如果狀態更改,則更新資料庫表。使用這種方法,工作流引擎可以利用關聯式資料庫(RDMS)的許多保證,例如ACID事務

Zeebe的工作方式非常不同,並利用事件溯源。這意味著對工作流狀態的所有更改都將作為事件捕獲,並且這些事件與命令一起儲存在事件日誌中。兩者都被認為是記錄在日誌中。

DDD愛好者的快速提示:這些事件是在Zeebe內部,與工作流狀態有關。如果您在領域中執行自己的事件溯源系統,則通常會為儲存自己的域事件

記錄是不可變的,因此日誌僅附加。一旦寫完,就不會有任何改變,就像會計雜誌一樣。可以非常有效地處理和擴充套件僅附加日誌。

工作流的當前狀態始終可以從這些事件中派生。這被稱為Project投影或投射。Zeebe中的投影在內部利用RocksDB儲存為快照,RocksDB是一個非常快速的鍵值儲存。RocksDB允許Zeebe在內部通過鍵查詢某些物件,因為純日誌甚至不允許簡單的查詢。

Zeebe 將日誌儲存在磁碟上。目前,這是唯一受支援的儲存選項(其他選項,例如Apache Cassandra會定期討論,但目前尚未在路線圖上討論)。RocksDB還將快照狀態重新整理到磁碟,這不僅建立了更快的啟動時間,而且還允許Zeebe從日誌中刪除已處理的記錄,使其保持相當緊湊。

為了實現效能,彈性和可伸縮性,我們應用了以下分散式計算概念:

Zeebe架構和用法示例

Zeebe在Java虛擬機器(JVM)上作為自己的程式執行。關於執行工作流引擎的體系結構選項,這是遠端引擎方法,因為使用Zeebe的應用程式與它遠端對話。但是,當我們利用流式傳輸到客戶端並使用二進位制通訊協議時,這非常有效且高效。它的巨大優勢在於代理具有已定義的設定和環境,並且不受應用程式程式碼的影響。因此,這個設計決策提供了適當的隔離,我們在支援工作流引擎的多年經驗中瞭解到它的重要性。

視覺化工作流程

Zeebe使用ISO標準BPMN中的視覺化工作流定義,可以使用免費的Zeebe Modeler以圖形方式對其進行建模

如果您願意,也可以使用YAML來描述工作流程,例如:

name: order

tasks:
    - id: retrieve-payment
      type: retrieve-payment-service

    - id: fetch-goods
      type: fetch-goods-service

    - id: ship-goods
      type: ship-goods-service

支援反應式程式設計,流媒體和背壓的本地語言客戶端

工作流程可以包括所謂的服務任務。當例項到達這些任務時,您需要執行一些程式碼。這是通過建立JobWorkers在您的應用程式中提取的作業來完成的。Zeebe提供本地語言客戶端,例如Java

ZeebeClient client = ZeebeClient.newClientBuilder().brokerContactPoint("127.0.0.1:26500").build();

JobWorker workerRegistration = client
  .newWorker()
  .jobType("my-service-task")
  .handler(new JobHandler() {
    public void handle(JobClient client, ActivatedJob job) {
      // here: business logic that is executed with every job
      System.out.println(job);
      // and let the workflow engine know we are done.
      // The API can be used blocking or non-blocking
      client.newCompleteCommand(job.getKey()).send().join();
    }  
  )
  .timeout(Duration.ofSeconds(10))
  .open();

正如您可能在程式碼中發現的那樣,您可以在應用程式中使用反應式程式設計模型。

您可以根據需要將盡可能多的客戶端連線到Zeebe,並且將分發作業(目前採用迴圈方式),從而實現工作人員的靈活可擴充套件性(上下)。Zeebe很快將支援背壓,因此確保僅以客戶可以處理的速率提供工作。沒有客戶可以被工作所淹沒。如果有疑問,在新客戶連線之前,作業將儲存在Zeebe中。

客戶端是競爭消費者,這意味著一個作業將只由一個客戶執行。這是使用鎖定事件實現的,需要在執行作業之前將其寫入Zeebe。只有一個客戶端可以編寫該鎖定事件,其他嘗試這樣做的客戶端會收到錯誤訊息。鎖定在保持一段時間之後被自動刪除,因為Zeebe認定客戶端在這種情況下已經意外死亡。

事務和至少一次語義

值得注意的是,Zebee客戶端不實現任何形式的ACID事務協議。這意味著在發生故障的情況下,不會回滾任何事務。通過此設定,您有兩種設計選擇:

  1. 您將事務提交到您的域,然後通知Zeebe完成該作業。現在你的應用程式可能會在提交和完成之間崩潰。因此Zeebe不會知道作業已完成並在鎖定超時後將其交給另一個客戶端。該工作將再次執行。語義是“ 至少一次 ”。
  2. 您首先完成工作,然後提交您的交易。如果應用程式崩潰,您可能已完成作業但未提交事務。工作流程將繼續進行。語義是“ 至多一次 ”。

DDD福音:Zeebe是一個類似Kafka的可擴充套件的分散式事件溯源工作流引擎

大多數時候你會決定使用“最多一次”,因為它在大多數用例中最有意義。

由於您的程式碼可能被多次呼叫,因此您必須使應用程式邏輯具有冪等性。這在您的域中可能很自然,或者您可能會想到其他策略並建立一個冪等接收器(請參閱例如Spring Integration)。我在微服務整合的三個常見陷阱中簡要地解決了冪等性- 以及如何避免它們並計劃一篇關於它的擴充套件文章。

通過CQRS查詢

Zeebe代理負責執行正在執行的工作流程。它被優化為將新命令應用於當前狀態,以達到開頭提到的效能和可伸縮性目標。但是,Zeebe代理無法提供任何查詢,例如“今天早上在8到9之間啟動了哪些工作流例項但尚未完成?”。由於我們不再使用關聯式資料庫,因此無法執行簡單的SELECT語句。在這種情況下,我們確實需要一種不同的方式來處理所謂的查詢模型。

這種分離命令和查詢模型的方式稱為命令查詢責任分離(CQRS),具有很大的優勢:

CQRS允許您將負載與讀取和寫入分開,允許您獨立地進行擴充套件。[...]您可以向雙方應用不同的優化策略。一個例子是使用不同的資料庫訪問技術進行讀取和更新。

這正是我們對Zeebe所做的。Broker利用事件流和優化高吞吐量和低延遲。但它不提供查詢功能。這就是為什麼Zeebe提供所謂的Exporters,它可以訪問整個事件流。一個開箱即用的Exporters是Elasticsearch。通過使用它,所有事件都寫入Elastic並儲存在那裡,隨時可以供您查詢。

Zeebe現在提供了一個操作工具,您可以使用它來檢視工作流引擎:Operate.。您可以看到正在發生的事情,識別問題(所謂的事件)以及根源和修復事件

Operate也可以擴充套件並在Elasticsearch上使用自己的優化索引:

原始碼可用的許可證

您可以下載,修改和重新分發Zeebe程式碼。您可以將Zeebe包含在商業產品和服務中。只要您不提供通用工作流程服務,類似雲提供商那樣。

總結

Zeebe被設計為一個真正可擴充套件且具有彈性的系統,沒有中央資料庫。它非常高效。它可以與幾乎任何程式語言一起使用。它使用BPMN中的視覺化工作流,允許真正的BizDevOps。這種組合使它與我所知道的任何編排或工作流引擎區別開來。

 

相關文章