搶先一步
VMware 提供培訓和認證,以加速您的進展。
了解更多此部落格系列的其他部分
第 1 部分:Spring Cloud Stream Kafka 應用程式中的交易簡介
第 2 部分:Spring Cloud Stream Kafka 應用程式中由生產者啟動的交易
第 3 部分:在 Spring Cloud Stream Kafka 應用程式中與外部交易管理器同步
第 4 部分:使用 Spring Cloud Stream 和 Apache Kafka 的交易回滾策略
第 5 部分:Apache Kafka 在 Spring Cloud Stream Kafka 應用程式中的精確一次語意
在本部落格系列的最後一部分中,我們深入探討一個相對較新的設計模式,該模式最初由 Chris Richardson 提出,但我們將從 Spring Cloud Stream 的角度來看待它。我們將了解 outbox 模式是什麼、它是如何運作的,以及在使用 Spring Cloud Stream 和 Apache Kafka 時可以採用的一些策略。請參閱此處的描述,以了解 Outbox 模式如何運作的簡介。
簡而言之,outbox 模式透過嚴格避免兩階段提交 (2PC),確保在單一原子單元內交付資料庫或外部系統並發佈到訊息系統。
在 outbox 模式中,開發人員需要遵循以下步驟
以下是此流程的圖示視圖
結果是事件的端對端流程在語意上以交易方式完成。我們寫了「語意上」,因為更新訊息系統的程序(在本例中)在資料庫交易之外,但實現了交易系統保證的資料完整性保證。如果資料庫寫入成功,下游程序會看到這一點,並將 outbox 表中的記錄發佈到 Kafka 主題。如果資料庫交易不成功,則不會寫入任何內容到 Kafka。重要的是要注意,我們仍然需要在 Kafka 發佈部分以及移除 outbox 記錄期間使用同步。
使用 outbox 模式的一個重要好處是,它可以避免複雜的交易策略,例如分散式兩階段提交 (2-PC) 或使用單一共享交易資源協調各種提交等。但仍然透過引入一些額外的程序,例如將事件持久化到 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 提供的交易支援來避免這種複雜性。
想像一下,為與上述相同的 order-service 編寫的服務,但重寫為交易式 Spring Cloud Stream 應用程式。與原始 outbox 模式避免 2PC 的前提一樣,我們在這個模型中也不必使用具有分散式交易管理器的兩階段提交。同時,我們也可以避免需要額外的 outbox 表和外部程式碼來查詢它並將其發佈到 Kafka 主題。當使用 Spring Cloud Stream Kafka 生態系統中的交易支援時,所有這些都可以在單一原子單元的範圍內完成。正如我們在第 3 部分的詳細分析中看到的那樣,Kafka 交易與資料庫交易同步。
當將此視為 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
使用交易式生產者 factory(假設我們設定了 transaction-id-prefix
)。交易資源不是啟動新的 Kafka 交易,而是與 JPA 交易同步。當方法退出時,JPA 首先提交,然後是同步的 Kafka 交易。正如您所看到的,它透過使用不同的策略,達成了 outbox 模式提出的相同結果。
以下是此流程的可視化表示
從此圖中可以看出,端對端流程作為單一交易上下文的一部分執行,並且此解決方案不需要任何額外的 outbox 表和外部程序來查詢它,然後才發佈到 Kafka 等等。但有一個重要的注意事項。 如果應用程式在資料庫操作後崩潰,則不會將任何資料傳送到 Kafka,這會使應用程式處於不一致的狀態。如果您的應用程式無法承受這種不一致性,最好的解決方案是依賴 outbox 模式(或使用適當的 2-PC 策略)。
對於消費-處理-生產類型的應用程式,情況更為複雜,因為 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 交易。
以下是圖示說明
這樣,我們可以保持實作非常精簡,而無需額外的資料庫設定和外部程序來查詢表並將其同步發佈到 Kafka,刪除 outbox 記錄以及其他複雜性。
與由生產者啟動的情況一樣,這裡有一些需要記住的事情。如果應用程式在中間崩潰,例如在資料庫操作之後,此解決方案不提供任何容錯能力。在這種情況下,不會有任何記錄發佈到 Kafka,這會使應用程式處於不一致的狀態。您需要編寫應用程式級別的防護措施,例如等冪消費者和其他類似策略,以確保應用程式在此不一致期間正常工作,這可能容易出錯且不太實用。因此,在這種情況下,您最好的選擇是考慮使用適當的 outbox 模式或實作一些 2-PC 策略。
基於我們在本系列中學到的交易基礎,我們在本文中看到了一些策略,當應用程式需要使用 outbox 模式時,我們可以在 Spring 中使用這些策略。這些策略透過建立在 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 Gopinathan 和 Oleg Zhurakousky 給予的所有指導和支援。
再次,以下是本部落格系列中所有其他部分的連結。
第 1 部分:Spring Cloud Stream Kafka 應用程式中的交易簡介
第 2 部分:Spring Cloud Stream Kafka 應用程式中由生產者啟動的交易
第 3 部分:在 Spring Cloud Stream Kafka 應用程式中與外部交易管理器同步
第 4 部分:使用 Spring Cloud Stream 和 Apache Kafka 的交易回滾策略
第 5 部分:Apache Kafka 在 Spring Cloud Stream Kafka 應用程式中的精確一次語意