LMAX微服務級別的分散式事務實現
本文探討了如何在應用層面實現分散式事務,也就是如何在應用服務之間的實現分散式共識演算法,這對於微服務的分散式柔性事務有很大參考意義。
這種系統的核心是通過確定性執行、複製共識日誌以及快照狀態等三個方式實現,這樣的共識方法好處是簡單、易於除錯、容錯性強和高科伸縮性。
幾年前看到LMAX架構,其中Disruptor後來開源了。LMAX讓我產生了“應用級別共識”的想法,自從我們在2015年開始探索其潛力以來,我們獲益良多,2016年,我們能夠做到部署在兩套交易系統,其中包括一套金融交易。
LMAX有什麼特點?LMAX是外匯商品和指數的金融交易系統,該系統流程可以實時處理客戶的買賣訂單。外匯非常不穩定,變化很快,因此交易所需要以非常低的延遲(亞毫秒)處理大量訂單延遲。核心系統是匹配引擎,負責匹配客戶的買入和賣出,匹配引擎是有狀態的:它需要儲存客戶訂單直到它們完成匹配或取消。
這種系統面臨的挑戰是它們具有很強的資源競爭性,但是不適合使用分片來實現並行性或吞吐量:例如, EUR/USD(歐元兌美元)本身非常不穩定,需要管理這對貨幣的所有訂單在同一個訂單book中:你真的不能分片這個問題,怎麼解決這個問題呢?
傳統方式總是嘗試計算是無狀態在應用級別使用某個快取或資料庫中的狀態,但這種設計無法解決高吞吐量同時,對低延遲如此苛刻的要求。
什麼模型解決了LMAX問題?LMAX團隊嘗試了高度競爭的資料庫方法和其他各種方法,包括SEDA,Actor模型等,然後他們嘗試了一個七十年代和八十年代的想法:狀態機複製。 將相同的順序的訊息應用到狀態機,因為訊息是順序的,能使得狀態修改有效,因為狀態機是確定性的。
所以這就是總體思路:編寫匹配的引擎邏輯(業務邏輯),遵循一些簡單的規則,確保確定性。然後部署此程式碼在網路中的幾個節點,再以相同的訊息順序(買 ,賣和取消訂單等)應用於所有節點。
在這個階段你可能想知道如何你可以構建這個順序訊息?訂單來自不同的地方(UI,API等)因此,一個開箱即用的“單一順序” 並不存在。 下面是解決之道:
1.放置一個演算法來選舉一個節點做領導者leader,即主節點。
2. 主節點處理所有傳入的訊息並對它們進行排序(客戶端群集總是與領導者交談)。
3. 主節點將順序複製到從節點。
4. 一旦從節點收到訊息就會確認。
5.一旦主節點收到的從節點確認數目符合符合叢集總數的法定人數(比如1/2 + 1個),這個訊息將被標記為已提交committed,並已準備好由業務處理邏輯。
6. 主節點還告知從節點已經提交確認committed的訊息。
7. 從節點關注者現在可以將訊息應用於他們自己的狀態機(業務邏輯)。
共識演算法
如果你聽說過有關共識演算法,例如Paxos或Raft演算法,剛才上面的LMAX演算法也很類似,但是LMAX沒有使用Raft。那時該論文尚未發表。
共識演算法傳統上用於構建分散式資料庫或分散式協調系統(Chubby,Zookeepe r,Etcd,Consul等)。LMAX案例中有趣的是他們沒有使用資料庫級別的共識演算法,而是在應用程式級別共識演算法。
由此付出的工程努力通常與設計分散式資料庫的研發根本不同,我不認為有很多人都會試圖在服務層級別中直接使用共識演算法。
共識演算法解決了分散式系統中的難題:它們保證即使發生故障,節點故障或網路分割槽,一組節點也會同意並複製相同的狀態。Raft等演算法保證了線性化到叢集的客戶端(CAP定理的C
),它們也是容錯的。 但是需要大多數節點在叢集中是可用的,這適合那些將一致性(CAP的C)看得比可用性(CAP的A)更重要的系統,比如同步系統。
開發應用的好處相比資料庫級別共識演算法,這種應用級別共識演算法在開發應用有哪些好處?
簡單
我認為當我實現架構時,最讓我感到驚訝的是第一次是這麼簡單。一旦叢集基礎設施到位,實現狀態機(確定性業務邏輯)非常簡單,當然比我用其他方法看到的要簡單得多,在我沒有看到另一種設計之前。清晰的分離關注:共識模組和業務邏輯。
這也是一個應用DDD的好環境,我們的交易業務邏輯程式碼是沒有使用任何框架或技術基礎設施:簡單的舊物件,資料結構和演算法,都在一個執行緒上執行。我們建立的交易交換模型是相當先進的楷模,而且,說實話,我想不出任何其他設計使我們能夠滿足客戶的功能要求和高可用性 。至少肯定沒有這麼快的上市時間。
一致性
這種架構的一個非常重要的優勢是可以獲得保證業務邏輯中的狀態在節點之間是一致的。
“傳統” 系統處理事務的樣子:
1. 從客戶端收到訊息。
2. 從某個儲存(資料庫,快取等)載入相應的資料。3.處理訊息,對資料應用一些邏輯並做出決定。
4. 檢視提交的新資料到一致性儲存,但因為我們正處在一個分散式系統,其他人(另一個節點,執行緒等)可能有在此期間改變了資料,我們需要以某種形式的樂觀鎖定或者對資料庫進行CAS(比較和交換)操作,才能確保我們不會覆蓋另外一個渠道已經修改的資料。5.如果CAS失敗,我們必須重新載入資料和重試 。
將上述內容與我們在“內部”實現一致演算法進行對比:
1.接收客戶端訊息。
2.對我們已經在記憶體中的資料應用邏輯(在這種系統中我們傾向於載入前期最需要我們在執行時的熱資料)。
3.由於系統是一致的,因此沒有什麼需要回退的 所有節點中的狀態都相同,直接向客戶端確認即可
這種方式取代了需要處理多種可能的故障情況的大量程式碼,讀寫資料庫都可能發生的錯誤,會導致髒資料或不一致。
在服務層中實現強一致性的另一個後果是:積極地在業務邏輯中載入所需的所有資料而不用擔心,這樣可以顯著簡化外部系統或資料儲存區程式碼,並且產生更好的效能,計算和資料存在於架構不同層中,
不需要資料庫
“不需要資料庫”可能聽起來有爭議,其實意思是你並不是真的需要一個具有這種設計的資料庫。與大多數共識演算法一樣,Raft是將系統收到的所有訊息儲存在叢集的每個節點本地:被稱為Raft日誌。
該日誌用於以下幾種情況:
1. 如果重新啟動群集,則可以將日誌應用於所有節點,從而播放系統回到啟動前同一狀態 - 記住狀態機是確定性的,所以重新應用相同的事件順序將產生相同的狀態。
2. 如果群集中的某個節點出現故障或重新啟動,也可以使用該日誌:當它加入時,它可以重放其本地日誌,然後查詢領導者以檢索任何 它可能已經錯過了它的訊息。
3. 此屬性對於解決程式碼中的錯誤也非常有用:如果系統出現故障時,只需要檢索Raft日誌和本地重放, 相同版本的程式碼可使用偵錯程式進行除錯,這樣做,你會重現生產環境出現的同樣問題。任何有高度診斷併發系統經驗的人應該瞭解該方法的顯著優勢。
4.快照 由於Raft日誌可以無限增長,因此它通常與快照結合使用:系統從Raft日誌中的某個的序列號中獲取應用程式狀態的快照,並將快照儲存在磁碟上。然後可以重新啟動系統,或者通過載入最新的快照然後應用所有快照來修復失敗的節點。
沒有“臨時”彈性
與其他架構的另一個根本區別在於您如何處理彈性。由於應用程式是基於一致性演算法構建的,因此無需考慮彈性。消除了很多複雜性。
確定的業務邏輯
前面提到業務邏輯需要確定,當你以同樣順序兩次呼叫你的業務邏輯時,系統獲得的結果應該是相同狀態,冪等的。這就意味著你的業務邏輯不應該:
1. 使用系統時間。如果查詢系統時間,每次您將獲得不同的輸出。時間需要在基礎設施中抽象出來併成為Raft日誌其中的一部分。每條訊息都由領導者加上時間戳並複製到從節點日誌,業務系統應該使用這種邏輯時間而不是系統時間,在系統中“注入”時間也有副作用:它使得時間相關的程式碼非常容易測試(你可以快進)。
2. 使用不仔細定義的隨機數。如果系統需要生成隨機數,你需要確保所有節點上使用相同種子的隨機生成器。使用當前訊息時間(非系統時間!)作為種子。
3. 使用不確定的庫。這可能聽起來非常嚴格,但是記住我們在這裡討論系統的業務邏輯,並且根據我的經驗,普通的舊物件效果很好。
它真的那麼容易嗎?
簡單並不簡單 - 一些在LMAX工作的開發人員說,馬丁打破了他們架構:一旦他們使用它,就很難在任何其他系統上工作因為太痛苦了。
權衡 請注意,強大的一致性需要付出代價:叢集中的所有節點都參與共識演算法並處理系統接收的每條訊息。如果您的系統不需要強大的一致性,可使用“無共享”架構,每個節點處理請求都是獨立的。 還要注意,一致性演算法至少需要三個節點:你會需要至少三個程序,在三個不同的伺服器上執行,最好是三個,此外,由於訊息需要在被髮送之前複製到大多數節點提交,節點之間的延遲將直接影響吞吐量,由於這個原因,節點通常部署在同一個地區。
尋找一個名字 我認為這種架構風格適用於許多類別的交易系統(實時工作流程,RFQ引擎,OMS,匹配引擎,信用檢查系統,智慧訂購路由器,對衝引擎等),在金融和金融之外也非常適合相關應用。在某些情況下,與傳統應用相比會產生更好的結果。
但當然和任何架構一樣,它比其他系統更適合某些系統。
當我與其他人談論架構時,我們傾向於將其稱為“LMAX”架構,但我認為它值得擁有自己的名字。我沒有找到更好的東西匹配這個“應用層面的共識”架構。