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

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

本部落格系列的其他部分

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

第二部分:Spring Cloud Stream Kafka 應用中的生產者發起事務

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

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

第五部分:Apache Kafka 在 Spring Cloud Stream Kafka 應用中的 Exactly-Once 語義

作為本系列部落格的最後一部分,我們將深入探討一個由 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 模式的嚴格規則,那麼在本部落格中,我們將向您展示針對這些用例的一些替代策略。

儘管從概念上講,當應用程式想要避免 2PC 時,outbox 設計模式通常是訊息系統的一個很好的抽象(正如我們在本系列的第 3 部分中討論的那樣),但在使用 Apache Kafka 和 Spring Cloud Stream 時,如果您不需要 outbox 模式的全部支援,則有一些選擇。首先,實現起來存在複雜性,例如應用程式需要維護一個額外的 outbox 資料庫表,需要額外的程式碼來消費它然後釋出到 Kafka,以及在訊息釋出後需要更多程式碼顯式地從中刪除它等等。

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

想象一個為上述相同訂單服務編寫的服務,但被重寫為一個事務性的 Spring Cloud Stream 應用程式。與原始 outbox 模式避免 2PC 的前提一樣,在這種模型中,我們也不必使用帶有分散式事務管理器的兩階段提交。同時,我們還可以避免額外的 outbox 表以及用於查詢併發布到 Kafka 主題的外部程式碼。在使用 Spring Cloud Stream Kafka 生態系統中的事務支援時,所有這些都可以在單個原子單元的範圍內完成。正如我們在第 3 部分的詳細分析中所見,Kafka 事務與資料庫事務同步。

在將此視為 outbox 模式的替代策略時,需要記住一些注意事項。這裡提出的想法並完全等同於 outbox 模式提供的語義。如果您的用例需要該級別的保證,建議直接使用 outbox 模式。在下面的章節中,我們將指出這些解決方案缺乏 outbox 模式完整保證的情況。

生產者發起應用中的 Outbox 模式語義

在本系列的第 2 部分中,我們討論了生產者發起事務

@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 模式(或使用適當的 2-PC 策略)。

消費-處理-生產應用中的 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 模式或實現某些 2-PC 策略。

結論

基於我們在本系列中學習到的事務基礎知識,我們在本文中看到了一些在 Spring 中使用的策略,這些策略適用於應用程式需要使用 outbox 模式的情況。這些策略透過利用 Spring 和 Apache Kafka 中的事務支援,採用了一種輕量級的方法。這些解決方案不是 outbox 模式的替代品,而是提供了一些指導,供您的應用程式不需要 outbox 模式的完整保證時考慮。

這裡值得重申的是,無論是在消費-處理-生產模式還是生產者發起事務場景中,如果您想嚴格遵循 outbox 模式實現的原始規則,您都可以做到,而無需採用上述捷徑。Spring Cloud Stream 和 Spring for Apache Kafka 允許您這樣做。只需按規定遵循該模式即可。

致謝

在我們結束關於 Spring Cloud Stream 和 Apache Kafka 事務的系列文章之際,我要感謝幾位在本系列中給予我寶貴反饋和指導的人。我要特別感謝 Spring for Apache Kafka 專案負責人 Gary Russell,他非常耐心地指導我瞭解了 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 Kafka 應用中與外部事務管理器同步

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

第五部分:Apache Kafka 在 Spring Cloud Stream Kafka 應用中的 Exactly-Once 語義

獲取 Spring 新聞通訊

訂閱 Spring 新聞通訊,保持聯絡

訂閱

領先一步

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

瞭解更多

獲取支援

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

瞭解更多

即將舉行的活動

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

檢視全部