金融領域微服務架構中如何實現分散式事務?如何記錄更多事件,儲存在哪裡?事件順序如何保證? - Revolut
Revolut需要記錄每個與金錢有關的事件,它們都很重要的;這是一個水晶球,我們必須小心接住並處理。此類事件包括匯款,更改使用者資料,任何卡操作等。與處理財務操作相關的所有事情都需要100%的一致性,金錢是非常敏感的事情。同時,我們每天必須跟上成千上萬新使用者的步伐。指數級放大還影響功能交付。我們一直在開發新的和現有的產品,但是新的複雜性正在出現。所有這些使我們處於需要專用解決方案的特殊位置。
為什麼不使用現成的解決方案?
像Kafka這樣的現成的解決方案可能已經解決了我們的一些問題,但同時也會帶來維護,配置和保持可用性的複雜性。因此,我們選擇投資自定義解決方案,該解決方案將隨著我們要解決的問題而增長。
有了我們自己的解決方案,我們就可以控制事件的整個生命週期 -從事件作為業務交易的一部分建立到到達EventStore並通過EventStream進行流式傳輸。
我們新增了Kafka不支援的以下功能(至少不能以輕鬆滿足我們需求的方式):
- 由於將事件儲存在PostgreSQL中,因此可以使用SQL進行即席查詢和輕鬆進行資料檢查。
- 按事件時間查詢-我們按時間戳劃分事件。
- 狀態更改和持久事件之間有保證的一致性,例如,狀態資料庫中的卡阻塞可保證CardBlocked事件。
- 關係:我們具有事件的巢狀結構(具有零個,一個或多個模型事件的動作事件),並且我們需要流式傳輸模型事件,而不是流事件。我們可以研究這種結構的非規範化/扁平化以適應第三方解決方案,但是作為副作用,我們將不得不面對不必要的資料冗餘。
- 該系統應允許使用者基於任意有效負載過濾器查詢事件,而不是處理所有事件或整個分割槽。
- 輕鬆歸檔舊事件。
- 在多個使用者節點上的並行事件流處理由任意分割槽鍵分割。
我們希望每個模型的事件自然順序得到保證。
我們不希望任何可能導致事故的意外發生。Kafka需要處理角落裡零星的專業知識。例如,設定其預設配置時要考慮吞吐量,而不是考慮高一致性。預設情況下,不執行諸如fsync之類的操作,因此可能會導致生產者/消費者之間的某些資料丟失或不一致。
無論選擇哪種解決方案,我們仍然需要一個資料庫(例如Postgres)和一個應用程式層。儘管現成的解決方案無法替代我們的整個系統,但它可能會接管其某些功能。例如,Kafka可用於接管短期(例如7天)持久事件流的職責。從吞吐量的角度來看,有其他選擇,例如Pulsar(事件流比Kafka具有更好的效能)和TimescaleDB(Postgres的時間序列資料庫擴充套件,用於優化基於時間的儲存和查詢)。
業務特點
當您處理金錢並出了點問題時,您需要知道與該操作相關的每項操作。識別並記錄所有貨幣交易,回滾以及相應的使用者資料。客戶服務可以使用此稽核日誌對問題進行適當的反應。
風險檢測:一些服務插入資料到EventStream,以便後來根據需要使用這些資料。他們執行模型(通常基於機器學習演算法),以評估面臨不同形式的金融犯罪風險的風險。
外匯操作中的實時監控:Revolut最受歡迎的功能之一是貨幣兌換。我們允許客戶比其他銀行更便宜地進行外匯交易。因此,為了促進我們的主要功能,我們需要實時監視進入系統的所有交易。我們實時對外匯風險敞口進行自動對衝。
市場推廣:我們消耗事件,觀察模式和行為,並根據結果觸發個性化的營銷活動和促銷。
我們對解決方案的期望
最初,我們所需要的只是一個交換事件並能夠動態查詢事件的系統。後來,隨著Revolut產品的開發,我們的要求也相應地發展了,並提出了以下建議:
- 業務資料的準確性
- 高服務可用性
- 進行高階查詢的能力-如按事件型別過濾交易事件
- 系統的可擴充套件性
- 資料的不變性
- 低延遲或具有控制延遲的能力(事件的完整處理,包括大約50-200 ms的儲存和流傳輸)
- 可觀察性-事件發生的原因和地點,是否可以預見
我們回顧了早在2016年初當時市場上可用的事件流解決方案,但找不到能同時滿足我們所有需求的事件流解決方案。例如,將缺少進行臨時查詢的能力,或者維護第三方解決方案需要大量專業知識來處理極端情況。
因此,我們在Revolut建立了自己的事件流媒體。
我們的解決方案設計
大多數Revolut服務都使用PostgreSQL 來儲存資料。對於事件,我們還將PostgreSQL與listen / notify機制一起使用,因此我們可以重用來自不同團隊的所有經驗,從而使消費者可以對資料庫進行高階查詢。當我們使用PostgreSQL複製時,我們有兩種資料庫例項-主資料庫和副本資料庫。我們使用一個主機 來儲存事件,並使用一個副本來進行流和獲取。
我們有 EventStore 應用程式,它負責編排事件的儲存和獲取。由於所有事件都位於Postgres中,因此此EventStore實現是無狀態的。它使用JetBrains Ktor框架和協程在Kotlin中編寫。我們使用EventStore應用程式的兩個例項,但根據負載,您可以擁有更多例項-它們的數量是水平縮放的。例項之間的負載由負載均衡器處理。
我們還有 EventStream 叢集,它完全負責向訂閱者流式傳輸事件。EventStream是使用RSocket在Java 11中開發的,用於處理使用者與EventStream之間的通訊彈性。
我們使用Google Cloud Platform,特別是虛擬私有云來隔離系統的不同部分,並促進適當的訪問管理。我們將儲存與應用程式分開,而應用程式實際上是與API閘道器分離的。
最後,我們獲得了一組微服務,這些微服務執行釋出者,使用者或只是獲取事件的服務的角色。我們決定開發用於服務的SDK-將客戶端SDK和伺服器置於一個團隊的控制之下,這使我們可以最大程度地減少向後相容性問題,抽象協議,並使REST API的更改更加容易。
實現準確性和一致性
這裡有兩個選擇;事件溯源和事件流。
您可以在首次生成事件然後對其進行處理時選擇事件源方法。但是,我們希望終端使用者的體驗與使用即時反饋進行同步處理一樣。這就是為什麼我們選擇第二個選項。
我們更改模型,然後基於此更改生成事件。因此,我們有兩個分開的東西:模型和事件,我們需要保持這兩者一致。如果回滾一個,那麼您也必須回滾另一個。如果其中一個成功,您知道另一個也成功。
流程的每個步驟:每個微服務和每個EventStore都有自己的資料庫。當服務產生事件時,我們不傳送事件,而是更改資料庫中的模型,並且為了實現一致性,我們將模型和事件都儲存在同一資料庫中。資料庫事務保證成功或回滾,系統就將回滾模型和事件,或者將兩者儲存。因此,一旦我們成功完成模型更改,事件就會儲存到資料庫中。然後,我們非同步釋出事到到EventStore,(banq注:關鍵是如何知道事件已經儲存到資料庫,估計是通過查詢資料庫新事件),如果傳送失敗,進行重試,並使用保證交付的機制,使我們保持一致性。
就這麼簡單嗎? 並不是的。實際上,我們幾乎沒有方法傳送事件以確保事件已交付。在業務操作期間,我們將事件釋出到EventStore,如果發生故障,可能會重試。同時將事件儲存在EventLog表中。如果事件未成功釋出,則我們的後臺協調流程將在EventLog中識別出該事件,並將重新傳送它們,直到事件成功為止。我們將所有事件保留24小時(可配置),因此需要我們在此時間內解決所有基礎架構或其他問題。
我們還進行清理以管理EventLog表的大小。如前所述,所有早於24小時的事件都將被清除,這使我們可以使EventLog表保持較小。(banq注:EventLog表類似發件箱模式中發件箱)
將成功事件儲存到EventStore資料庫後,我們將使用LISTEN / NOTIFY Postgres機制 :在任何時間,我們都可以通過Postgres通知EventStream發生了某些事件。因此,無論何時將事件寫入資料庫,PostgreSQL都會觸發包含所有EventStream例項完整事件訊息的事件。後來,EventStream將這些事件釋出給所有以匹配條件訂閱它們的使用者。(banq注:嚴重依賴了Postgres特有機制)
處理重新連線
假設使用者開始獲取事件,但是處理速度很慢-在這種情況下,我們使用背壓機制。在某個時候,佇列可能已滿,因此我們暫停處理,一旦使用者完成對佇列的處理,它就可以開始請求更多事件。此外,對於服務的任何停機時間,使用者都可以根據PostgreSQL在插入上設定的時間戳在任何時間返回。
我們有兩個部分的流媒體過程。每當進行新訂閱時,它都基於離線模式啟動,因為它基於上次快照時間或上次處理的事件時間-這適用於任何副本。一旦離線流趕上來,它將切換到聯機模式,該模式使用LISTEN / NOTIFY Postgres機制-在這種情況下使用邏輯副本。
領域拆分
我們必須在分散式系統中處理的另一重要事情是域拆分-我們使用Saga模式解決它。由於所有業務事務都以“動作”表示:我們可以定義所有相關動作的Saga鏈,確保我們可以觀察到處理狀態並在發生任何失敗時重試,或使用事件使用者進行恢復。使用事件使用者的後備之所以起作用,是因為每個Action都會產生事件,因此,我們可以確保在任何給定時間點都可以從事件中恢復。這就要求我們編寫的每個動作都是冪等的,因為它會被處理Saga流或事件使用者(後備)呼叫幾次。
事件協調機制
每當事件釋出到EventStore時,我們都有一個後備機制
假設我們在同一個應用程式有三個例項需要位於同一業務交易事務中。在業務運營期間,他們還會傳送有關他們的事件。如果它們中的任何一個失敗,那麼未處理的事件將出現在EventLog中。現在,我們不希望所有應用程式參與協調失敗的事件,因為這將導致很多不必要的工作以及更新成功事件的失敗(因為其他應用程式會執行相同的工作)。為了解決重新傳送失敗事件的問題,我們需要其中一個應用程式成為所謂的事件協調器。釋出者的這個例項將進行對帳處理-基本上是指從EventLog中讀取30 s以上的未標記為已釋出的事件,然後重新傳送。因此,我們可以保證每個事件遲早都會發布。
結論
您可能會想到,我們擁有的解決方案非常複雜並且還在不斷髮展。我們仍然需要克服許多挑戰,但是我們的解決方案為我們提供了所需的靈活性和效能,因此可以滿足不斷增長的需求。這是有關事件流平臺的系列文章中的第一篇,請回來檢視更多內容!
相關文章
- 如何在Java中實現事件驅動的微服務架構Java事件微服務架構
- 微服務架構 | 11.1 整合 Seata AT 模式實現分散式事務微服務架構模式分散式
- go-zero微服務實戰系列(十、分散式事務如何實現)Go微服務分散式
- 微服務架構 | 11. 分散式事務微服務架構分散式
- 微服務架構中的分散式事務全面詳解 -DZone微服務微服務架構分散式
- Apache ShardingSphere 如何實現分散式事務Apache分散式
- DTM:Golang中微服務架構的分散式事務框架Golang微服務架構分散式框架
- 如何在微服務分散式架構中刪除資料? - bennorthrop微服務分散式架構
- 微服務架構分散式事務管理問題微服務架構分散式
- 分散式政企應用如何快速實現雲原生的微服務架構改造分散式微服務架構
- 如何設計基於事件驅動架構的銷售庫存微服務?- Jasbir事件架構微服務
- 微服務架構中分散式事務實現方案怎樣何取捨微服務架構分散式
- 微服務架構及分散式事務解決方案微服務架構分散式
- Seata-AT 如何保證分散式事務一致性分散式
- 結合領域事件和微服務的實現領域驅動設計 - Alagarsamy事件微服務
- 事務使用中如何避免誤用分散式事務分散式
- 分散式訊息佇列:如何保證訊息的順序性分散式佇列
- 如何遷移到微服務和事件溯源EventSourcing微服務事件
- 微服務架構下分散式事務解決方案-hoop(一)微服務架構分散式OOP
- 微服務事件驅動架構演進微服務事件架構
- 如何快速搞定微服務架構?微服務架構
- 如何在Java後端中實現事件驅動架構:從事件匯流排到事件溯源Java後端事件架構
- 在微服務領域Spring Boot自動伸縮如何實現微服務Spring Boot
- 微服務領域的軟體架構微服務架構
- 如何實現跨Mysql、Redis和Mongo分散式事務? - dongfuMySqlRedisGo分散式
- 實戰與原理:如何基於RocketMQ實現分散式事務?MQ分散式
- 微服務架構如何實現客戶端負載均衡微服務架構客戶端負載
- eBay推出首個微服務架構下可實現ACID的分散式事務協議:GRIT微服務架構分散式協議
- 在微服務架構中實施分散式事務鎖的幾個方案比較 - Prasanth Gullapalli微服務架構分散式
- 如何拆分你的微服務架構?微服務架構
- 最受歡迎的微服務語錄:不要試圖跨微服務構建分散式事務微服務分散式
- Python如何快速實現分散式任務?Python分散式
- Java中如何保證執行緒順序執行Java執行緒
- 如何在Redis中實現事務Redis
- 分散式事務(3)---RocketMQ實現分散式事務原理分散式MQ
- 微服務分散式架構之redis篇微服務分散式架構Redis
- 微服務架構下分散式session管理微服務架構分散式Session
- PHP 微服務之 [分散式事務]PHP微服務分散式