Airbyte如何使用Temporal擴充套件工作流程編排?

banq發表於2022-04-15

Airbyte 的作用是提供一種在源和目標之間執行資料同步的簡單方法。工作流程編排很重要,因為它確保資料按照客戶指定的頻率同步。
在本文中,我們將討論能夠支援 Airbyte 資料同步的編排器所必需的特性以及我們如何利用 Temporal。然後,我們將詳細介紹如何構建長期執行的 Temporal 工作流,以促進靈活的內部同步排程。我們詳細介紹了使我們能夠實現這些工作流的特定於時間的功能。 

對於 Airbyte 的同步工作流程,我們的編排系統需要能夠:
  • 將工作分配給 Docker Compose 和 Kubernetes 上的節點。在一個或另一個環境上執行的配置之間應該有最小的差異。
  • 使用時間間隔和 crontab 靈活地安排工作流程,同時可以透過可預測的行為動態啟用、禁用或更改計劃。 
  • 跨多個節點同時橫向擴充套件至數千個工作流。

採用多種方法的工作流編排器有許多選項。大多數工作流編排器處理長時間執行的非同步工作流,這些工作流可以處理狀態更新和重試,同時保持執行歷史記錄。

然而,Temporal也使我們能夠以程式設計方式配置工作流程。雖然我們的大多數工作流程代表了資料同步,但有些執行的動作,如查詢資料庫的模式。這些動作會帶來延遲,Airbyte的使用者在使用者介面上會感覺到這些延遲。Temporal允許我們以最小的開銷配置和執行工作,這在Airflow和其他幾個常見的協調器中是不可能的。

我們正在尋找的一個特點是能夠輕鬆地將該工具嵌入到我們的Airbyte的OSS版本中。Airbyte在Docker Compose和Kubernetes上都得到了支援,Airbyte的操作者在維護分散式系統方面有廣泛的經驗。我們避免了像Argo這樣偉大的Kubernetes專用工具,以防止Docker Compose和Kubernetes系統之間的Airbyte內部同步編排出現分歧。Temporal提供各種部署選項,在我們所有的部署平臺上發揮作用。

Airbyte的API也使用Postgres作為資料庫。由於Temporal可以使用Postgres作為備份儲存,所以我們的OSS使用者群不需要額外的容器依賴,即使我們在邏輯上將Airbyte的配置資料庫和Temporal的內部資料庫分開。對於生產環境,我們建議分別使用不同的資料庫。

Airbyte平臺的絕大部分程式碼也是用Java編寫的。雖然不是嚴格的要求,但我們更傾向於使用高質量的Java SDK與協調器進行互動。Temporal提供了一個富有表現力的Java SDK,使用程式碼來指定工作流和活動的行為。你只需編寫程式碼就能得到一個工作流

在可擴充套件性方面,我們希望能夠透過我們的排程和工人執行基礎設施處理同時執行的成千上萬的同步。Temporal能夠解決這個問題,它允許在不同的伺服器元件之間進行獨立的擴充套件,並提供了一個簡單的模型,將工作者節點連線到獨立的任務佇列,這使得在像Kuberentes這樣的多節點系統上可以輕鬆擴充套件。

在更深入地瞭解我們的解決方案的架構之前,我們將解釋Temporal工作流模型的一些基本構建塊。

Temporal概述
Temporal是一個工作流引擎,它處理被分割成步驟或 "活動 "的工作流的狀態管理。我們使用Java SDK連線到Temporal服務,該服務提供了一個API,用於註冊可以執行Temporal活動的工作者,啟動非同步工作流,向正在執行的工作流傳送訊號,並查詢工作流的狀態。這項服務很容易被配置為在Docker或Kubernetes上執行,並使用Postgres或Cassandra等支援資料儲存。

如果你來自一個面向批處理的工作流引擎,如Airflow,許多核心概念類似DAGs、子DAGs和sensors。

Java SDK允許表達工作流的概念(Airflow中的DAG),它以定義的順序執行一些活動。這是任何Temporal執行的入口。工作流可以接收訊號和執行查詢,這將在下面解釋。工作者節點與Temporal伺服器通訊,以確定哪些工作流應該在單個節點上執行。這是透過在建立時將某些工作流分配給命名的任務佇列來協調的,這些任務佇列可以在單個節點上註冊。

活動(Airflow中的任務)是Temporal工作流中的步驟,代表一些操作或操作。這些操作可以包含任何邏輯,但由於它們代表了可重試的工作單位,它們通常被用來分離行為,如對外部資料庫或API的呼叫。一個活動的輸出被序列化,並由Temporal的支援資料儲存以及活動和工作流的任何相關狀態資訊一起儲存。

也可以有子工作流(Airflow 中的 subDAGs),它是由其他工作流啟動的工作流。它可以配置子工作流程的行為,根據父工作流程失敗或被取消的情況,失敗或繼續。

有兩種方法允許與工作流程進行互動:訊號和查詢。訊號允許修改一個工作流的記憶體狀態。這些方法是非同步的,不能保證立即執行。如果一個Temporal工作流依賴於一個被訊號修改的條件,它將智慧地重新評估該條件,以決定工作流是否應該繼續。查詢只是允許讀取一個工作流的狀態。我們通常使用這個功能來檢查長期執行的工作流的狀態。

關於Temporal是什麼以及它的功能的更多資訊,我們建議查閱Temporal網站的介紹和其他指南。

如何組織Temporal工作流
Airbyte的主要Temporal工作流用於將源(API、DB、檔案等...)之間的資料同步到目的地(DB、倉庫、雲端儲存等...)。為了正確執行同步,我們需要按照特定的順序執行以下操作。

  • 獲取排程配置
  • 等待適當的時間
  • 獲取作業配置
  • 更新作業狀態,報告它正在執行
  • 執行同步
  • 更新作業狀態,報告失敗或成功的執行情況

所有這些不同的步驟都被隔離在一個專門的Temporal動作中。一個動作是以下內容之一:一個Temporal活動、一個子工作流或一個Temporal工作流內部方法。在下面的章節中,我們將提供所有三種型別的行動的例子。

我們決定使用一個永久執行的Temporal工作流來表示每個資料同步。為了限制永久執行的工作流的事件歷史的大小,我們使用了一個叫做ContinueAsNew的時態Temporal概念,在執行執行後從第一步重新開始。

這種設計有利於完全定製我們支援的排程型別,因為我們在執行前等待的時間是在一個活動中確定的。這將使Airbyte支援基於cron的工作流排程、停工期和基於時間間隔的排程。此外,Temporal透過在冷庫中快取狀態,並在需要時才載入到記憶體中,使長時間執行的 "等待 "操作變得高效。

設計一個同步工作流程
Airbyte中的同步分為3個活動。

  • 資料複製
  • 資料歸一化(可選)
  • dbt轉換(可選)


所有這些活動都是長期執行的操作,根據資料量和聯結器的吞吐量,可能需要幾分鐘到幾個小時。所有這些活動都與在幾毫秒內執行的獲取時間表或用資料庫I/O報告狀態的非常短的活動有很大不同。為了在一個單獨擴充套件的工人池中隔離長執行,我們在子工作流中執行這些活動。這個子工作流並不獨立於父工作流;如果父工作流被終止或取消了,子工作流也會被終止。這使得我們可以使用父工作流級別的唯一性約束來確保每個連線只能執行一個同步。

實現一個Temporal 工作流
我們正在使用Temporal Java SDK來定義一個工作流。它提供了一組註釋,用於裝飾一個介面,讓Temporal知道如何執行我們正在編寫的不同方法。在介面層面上有一個主要的註解。@WorkflowInterface。有3個方法註解,我們正在使用。

  • @WorkflowMethod。每個工作流只能有一個,這個方法包含了對活動呼叫的實現排序。這是執行工作流的入口。
  • @SignalMethod。裝飾了一個方法,該方法將作為訊號被臨時性呼叫。它不執行I/O,只更新Temporal工作流的記憶體狀態。
  • @QueryMethod: 裝飾了一個將被temporal呼叫作為查詢的方法。它是一個同步方法,將返回記憶體中Temporal狀態的某些部分。


@WorkflowInterface
public interface ConnectionManagerWorkflow {

 @WorkflowMethod
 void run(ConnectionUpdaterInput connectionUpdaterInput);

 @SignalMethod
 void submitManualSync();

 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 class JobInformation {

   private long jobId;
   private int attemptId;

 }

 @QueryMethod
 JobInformation getJobInformation();
}


實現一個Temporal的活動
這與工作流的實現非常相似。Temporal提供了一組註解,允許你裝飾一個介面。然後,它將把這些方法識別為一個活動,並能夠確保它們在每個工作流執行中只執行一次。比如說:

@ActivityInterface
public interface ConfigFetchActivity {

 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 class ScheduleRetrieverInput {

   private UUID connectionId;

 }

 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 class ScheduleRetrieverOutput {

   private Duration timeToWait;

 }

 @ActivityMethod
 ScheduleRetrieverOutput getTimeToWait(ScheduleRetrieverInput input);

}



然後,它需要在工作流程中註冊。這樣做將使你能夠把它與最理想的工人庫聯絡起來。

private final ConfigFetchActivity configFetchActivity =
   Workflow.newActivityStub(ConfigFetchActivity.class,
ActivityConfiguration.SHORT_ACTIVITY_OPTIONS);


觸發同步執行
有時Airbyte使用者想手動觸發同步。我們使用Temporal的訊號方法來傳輸這個請求。訊號方法是一種與Temporal通訊並修改其內部狀態的非同步方式。修改狀態後,Temporal將有效地重新評估等待條件,並允許執行子工作流。

取消同步執行
取消是一個很好的例子,說明我們可以結合不同的Temporal原語來安全地處理各種協調器的狀態。具體來說,我們將討論如何使用以下時態基元:子工作流、訊號方法、取消範圍和繼續為新。

取消是由外部使用者觸發的,噹噹前執行需要被取消時,該使用者透過Temporal服務向工作流傳送訊號。這可以透過Temporal API或Temporal CLI完成。為了能夠處理訊號,我們首先需要在我們的工作流實現中宣告並實現訊號方法。在Java SDK中,Temporal為此使用了一個註解,你可以在這裡看到:

@WorkflowInterface
public interface ConnectionManagerWorkflow {

 @WorkflowMethod
 void run(ConnectionUpdaterInput connectionUpdaterInput);

 @SignalMethod
 void cancelJob();
}


訊號方法signal method 的實現非常簡單,它只是將作業的狀態設定為取消的狀態,然後取消cancellation Scope(我們將在下面解釋)。然後,該狀態將被用於報告我們內部資料庫對同步的表示中的正確狀態。

@Override
public void cancelJob() {
 workflowState.setCancelled(true);
 cancellableSyncWorkflow.cancel();
}



cancellation Scope允許您使用將在工作流取消時執行的回撥方法。如果cancellation Scope取消作用域的取消方法被呼叫,它將停止回撥方法和其餘活動的執行。然後,取消會被傳播到子工作流中。這使我們能夠確保在父工作流被取消時,同步被正確地停止。這就是如何建立一個取消作用域。

Workflow.newCancellationScope(() -> {
  // Implementation here
}

這就是如何取消作用域:
cancellableSyncWorkflow.cancel();

如前所述,這將取消父工作流並將取消傳播到子工作流中。我們已經配置了ParentClosePolicy來傳播取消,而不是突然終止工作流。我們使用的是PARENT_CLOSE_POLICY_REQUEST_CANCEL,它允許我們優雅地終止子工作流。

總結
在這篇文章中,我們高屋建瓴地介紹了Airbyte在協調資料同步工作流時面臨的挑戰,以及我們選擇使用Temporal建立一個可定製的解決方案的原因。我們還更深入地展示了一些Temporal的功能,我們使用這些功能是為了處理Airbyte公司協調資料同步的廣泛需求。如果你有興趣閱讀我們排程的整個實現過程,原始碼是開源的,可以在 here 獲得。
 

相關文章