Spring Cloud Stream Kafka Binder 中的事務:Outbox 模式策略的用例

工程 | Soby Chacko | 2023 年 10 月 24 日 | ...

本系列部落格的其他部分

第一部分:Spring Cloud Stream Kafka 應用程式中的事務簡介

第二部分:Spring Cloud Stream Kafka 應用程式中的生產者啟動事務

第三部分:Spring Cloud Stream 中與外部事務管理器同步

第四部分:Spring Cloud Stream 和 Apache Kafka 的事務回滾策略

第五部分:Spring Cloud Stream Kafka 應用程式中的 Apache Kafka 的精確一次語義

在本系列部落格的最後一部分,我們將深入探討一個由Chris Richardson首先提出但從 Spring Cloud Stream 的角度來看相對較新的設計模式。我們將瞭解 Outbox 模式是什麼,它是如何工作的,以及在使用 Spring Cloud Stream 和 Apache Kafka 時採用該模式的幾種策略。有關 Outbox 模式工作原理的介紹,請參閱此處的說明。

Outbox 模式快速概述

簡而言之,Outbox 模式透過嚴格避免兩階段提交 (2PC),在一個原子單元內確保資料庫或外部系統接收訊息併發布到訊息系統。

在 Outbox 模式中,開發人員需要遵循以下步驟

  1. 處理器方法接收訊息。
  2. 在其邏輯中,它首先以事務方式與資料庫互動,然後在此事務中建立一個名為 outbox 的特定表的新記錄。
  3. 外部程序查詢此 outbox 表並將訊息釋出到 Kafka。
  4. 一旦 Kafka 釋出成功,該記錄將從 outbox 表中刪除。

下圖直觀展示了這一流程

outbox-pattern-txn-blog-part-6

結果是,事件的**端到端**流在語義上是事務性的。我們寫“語義上”,因為更新訊息系統的過程(在本例中)在資料庫事務之外,但實現了事務系統所保證的資料完整性。如果資料庫寫入成功,下游程序將看到這一點,並將記錄從 outbox 表釋出到 Kafka 主題。如果資料庫事務不成功,則不會寫入任何內容到 Kafka。需要注意的是,在 Kafka 釋出和刪除 outbox 記錄時,我們仍然需要使用同步機制。

使用 Outbox 模式的一個重要優點是它避免了複雜的事務策略,例如分散式兩階段提交 (2-PC) 或使用單一共享事務資源協調各種提交等。但透過引入一些額外的程序,例如將事件持久化到 outbox 表,然後由另一個程序基於此將事件釋出到訊息代理,它仍然能提供分散式事務的語義好處。

在 Spring Cloud Stream 中適配 Outbox 模式

Outbox 模式在許多涉及訊息代理的不同用例中都有效。如果您的用例特別需要使用此模式,您可以按照規定來實現此模式。然而,在本部落格中,我們向使用 Spring 和 Apache Kafka 的使用者展示了一些替代策略,如果可以放寬遵循 Outbox 模式的嚴格規則。

儘管從概念上講,Outbox 設計模式是訊息系統的良好抽象,尤其是在應用程式想要避免 2PC 時,正如我們在本系列第三部分中所討論的,但對於 Apache Kafka 和 Spring Cloud Stream,如果您不需要 Outbox 模式的完整支援,也有一些選擇。首先,實現存在複雜性,例如應用程式需要維護一個額外的資料庫表用於 outbox,需要額外的程式碼來消費它然後釋出到 Kafka,訊息釋出後需要更多的程式碼來顯式刪除它,等等。

在編寫 Spring Cloud Stream Kafka 應用程式時,我們可以透過利用 Spring Cloud Stream 透過 Spring for Apache Kafka 提供的事務支援來避免這種複雜性。

想象一個與上面相同的 order-service 編寫的服務,但重寫為一個事務性的 Spring Cloud Stream 應用程式。與原始 Outbox 模式避免 2PC 的前提一樣,在此模型中我們也不必使用分散式事務管理器的 2PC。同時,我們還可以避免需要額外的 outbox 表以及用於查詢它並將其釋出到 Kafka 主題的外部程式碼。在使用 Spring Cloud Stream Kafka 生態系統中的事務支援時,所有這些都可以在單個原子單元的範圍內完成。正如我們在第三部分的詳細分析中所見,Kafka 事務與資料庫事務同步。

將此作為 Outbox 模式的替代策略時,有幾點需要注意。此處提出的想法**不**完全等同於 Outbox 模式提供的語義。如果您的用例需要這種級別的保證,建議直接使用 Outbox 模式。在下面的部分中,我們將指出解決方案在哪些方面缺乏 Outbox 模式的完整保證。

生產者啟動應用程式中的 Outbox 模式語義

在本系列第二部分中,我們瞭解了生產者啟動事務

@Autowired
Sender sender;

@PostMapping("/send-data")
public void sendData() throws InterruptedException {
   sender.send(streamBridge, repository);
}

@Component
static class Sender {

   @Transactional
   public void send(StreamBridge streamBridge, OrderRepository repository){
       Order order = new Order();
       order.setId("order-id");

       Order savedOrder = repository.save(order);

       OrderEvent event = new OrderEvent();
       event.setId(savedOrder.getId());
       event.setType("OrderType");
       streamBridge.send("process-out-0", event);
   }
}

工作流的主要觸發器是 REST 端點,它呼叫一個用 @Transactional 標註的方法。事務攔截器啟動 JPA 事務,併發生資料庫操作,但由於方法處於事務中間,因此不作為事務的一部分進行提交。之後,我們透過 StreamBridge 的 send 方法釋出到 Kafka。StreamBridge 使用的 KafkaTemplate 使用事務性生產者工廠(假設我們設定了 transaction-id-prefix)。事務資源不會啟動新的 Kafka 事務,而是與 JPA 事務同步。當方法退出時,JPA 首先提交,然後是同步的 Kafka 提交。正如您所見,它透過使用不同的策略實現了 Outbox 模式所提出的相同結果。

這是該流程的直觀表示

producer-init-txn-blog-part-6

從圖中可以看出,端到端流程執行在一個事務上下文中,並且此解決方案不需要額外的 outbox 表以及用於查詢它然後僅釋出到 Kafka 等的外部程序。**但有一個重要的注意事項。** 如果應用程式在資料庫操作後崩潰,則不會將任何資料傳送到 Kafka,這會導致應用程式處於不一致狀態。如果您的應用程式無法承受這種不一致性,最佳解決方案是依賴 Outbox 模式(或使用適當的 2PC 策略)。

消費-處理-生產應用程式中的 Outbox 模式語義

對於**消費-處理-生產**型別的應用程式,情況更為複雜,因為 Spring for Apache Kafka 中的訊息偵聽器容器在消費記錄後啟動 Kafka 事務。

讓我們回顧一下系列中部落格 3中看到的**消費-處理-生產**模式的程式碼

@Bean
public Consumer<OrderEvent> process(TxCode txCode) {
   return txCode::run;
}

@Component
class TxCode {

   @Transactional
   void run(OrderEvent orderEvent) {
       Order order = new Order();
       order.setId(orderEvent.getId());

       Order savedOrder = repository.save(order);

       OrderEvent event = new OrderEvent();
       event.setId(savedOver.getId());
       event.setType("OrderType");
       streamBridge.send("process-out-0", event);
   }
}

這段程式碼以事務方式釋出到資料庫和 Kafka。

訊息偵聽器容器啟動 Kafka 事務,然後我們使用 @Transactional 來包裝我們的內部 run 方法和 JPA 事務。如果資料庫操作成功,我們釋出到 Kafka 主題,Kafka 釋出操作使用由訊息偵聽器容器在此過程開始時建立的相同事務資源。方法退出後,JPA 提交,當控制權返回到訊息偵聽器容器時,它提交 Kafka 事務。

以下是這張圖示

cons-process-prod-txn-blog-part-6

這樣,我們就可以保持實現非常精簡,而無需額外的資料庫設定和外部程序來查詢表並將其釋出到 Kafka,以及同步、刪除 outbox 記錄和其他複雜性。

特殊注意事項

與生產者啟動場景一樣,這裡也有幾點需要注意。此解決方案在應用程式中途崩潰(例如,在資料庫操作之後)時,不提供任何容錯能力。在這種情況下,不會將任何記錄釋出到 Kafka,這會導致應用程式處於不一致狀態。您需要編寫應用程式級別的保護措施,例如冪等使用者和其他類似策略,以確保應用程式在不一致期間正常工作,這可能容易出錯且不切實際。因此,在這種情況下,您的最佳選擇是考慮使用正確的 Outbox 模式或實現一些 2PC 策略。

結論

基於貫穿本系列學習的事務基礎,我們在本文中看到了當應用程式需要使用 Outbox 模式時,可以在 Spring 中使用的一些策略。這些策略透過利用 Spring 和 Apache Kafka 中的事務支援,採用了輕量級的方法。這些解決方案不能替代 Outbox 模式,但如果您的應用程式不需要 Outbox 模式的完整保證,可以作為一些參考。

在此再次重申,在**消費-處理-生產**模式和**生產者啟動**事務場景中,如果您想嚴格遵循 Outbox 模式實現的原始規則,可以在不經過上述捷徑的情況下做到。Spring Cloud Stream 和 Spring for Apache Kafka 允許您這樣做。只需按照規定遵循模式即可。

致謝

在結束本系列關於 Spring Cloud Stream 和 Apache Kafka 事務的介紹之際,我想特別感謝幾位在整個系列中給予我寶貴反饋和指導的人。我想以一種非常特別的方式感謝 Gary Russell,他是 Spring for Apache Kafka 的專案負責人,他指導我瞭解了 Spring for Apache Kafka 中事務在非常底層的工作原理。Gary 回答了我無數關於 Spring 和事務的問題,尤其是從 Spring for Apache Kafka/Spring Cloud Stream 的角度,我非常感激他。我還要特別感謝 Jay Bryant 仔細校對了所有部落格草稿並進行了必要的更正。還要特別感謝 Ilayaperumal GopinathanOleg Zhurakousky 給予的所有指導和支援。

再次,以下是本系列所有其他部分的連結。

第一部分:Spring Cloud Stream Kafka 應用程式中的事務簡介

第二部分:Spring Cloud Stream Kafka 應用程式中的生產者啟動事務

第三部分:Spring Cloud Stream 中與外部事務管理器同步

第四部分:Spring Cloud Stream 和 Apache Kafka 的事務回滾策略

第五部分:Spring Cloud Stream Kafka 應用程式中的 Apache Kafka 的精確一次語義

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有