在分散式事務失敗的情況下實現一致性:線上事件處理OLEP(事件溯源) - ACM權威
近半個世紀以來,ACID事務(滿足原子性,一致性,隔離性和永續性的特性)一直是確保資料儲存系統一致性的首選。眾所周知的原子性屬性:在發生故障時,可確保事務寫入的全部或全部都不會; 隔離防止同時執行的事務干擾; 和永續性確保在發生故障時不會丟失已提交事務所做的寫入。
雖然事務在單個資料庫產品的範圍內執行良好,但是跨越來自不同供應商的多個不同資料儲存產品的事務存在問題:許多儲存系統不支援它們,而那些通常表現不佳的儲存系統。如今,大規模應用程式通常通過組合針對不同訪問模式優化的多種不同資料儲存技術來實現。分散式事務在大多數此類設定中未能獲得採用,而大多數大型應用程式依賴於臨時,不可靠的方法來維護其資料系統的一致性。
然而,近年來,事件日誌作為大規模應用中的資料管理機制的使用已經增加。這一趨勢包括資料建模的事件溯源/事件採購方法、變更資料捕獲系統的使用,以及Apache Kafka等基於日誌的釋出/訂閱系統的日益普及。雖然許多資料庫在內部使用日誌(例如,預寫日誌或複製日誌),但新一代基於日誌的系統是不同的:它們不是將日誌用作實現細節,而是將它們提升到應用程式程式設計模型的級別。
由於此方法使用應用程式定義的事件來解決傳統上屬於事務處理域的問題,因此我們將其命名為OLEP(線上事件處理),以與OLTP(線上事務處理)和OLAP(線上分析處理)進行對比。本文解釋了OLEP出現的原因,並展示了它如何允許應用程式保證跨異構資料系統的強一致性,而無需藉助原子提交協議或分散式鎖定。OLEP系統的體系結構允許它們實現始終如一的高效能,容錯和可伸縮性。
今日應用程式架構:多語言永續性
針對不同的訪問模式設計了不同的資料儲存系統,並且沒有一種單一適用的儲存技術能夠有效地滿足所有可能的資料使用。因此,今天許多應用程式使用幾種不同儲存技術的組合,這種方法有時稱為多語言永續性。例如:
•全文搜尋。當使用者需要對資料集(例如,產品目錄)執行關鍵字搜尋時,需要全文搜尋索引。雖然一些關聯式資料庫(如PostgreSQL)包含基本的全文索引功能,但更高階的用法通常需要專用的搜尋伺服器,如Elasticsearch。為了改進索引或搜尋結果排名演算法,可能需要不時地重建搜尋引擎的索引。
•資料倉儲。大多數企業從其OLTP資料庫中匯出運營資料,並將其載入到資料倉儲中以進行業務分析。對於此類分析工作負載(例如,面向列的編碼)執行良好的儲存佈局與OLTP儲存引擎的儲存佈局非常不同,因此需要使用不同的系統。
•流處理。訊息代理允許應用程式在事件發生時訂閱事件流(例如,表示使用者在網站上的動作),並且流處理器提供用於解釋和響應那些流的基礎結構(例如,檢測欺詐或濫用的模式) 。
•應用程式級快取。為了提高只讀請求的效能,應用程式通常維護經常訪問的物件的快取(例如,在memcached中)。當底層資料發生更改時,應用程式會使用自定義邏輯來相應地更新受影響的快取條目。
請注意,這些儲存系統並不完全相互獨立。相反,一個系統通常在另一個系統中儲存資料的複製或物化檢視。因此,當一個系統中的資料更新時,通常需要在另一個系統中更新。
OLTP事務是預定義的和短的
在傳統檢視中,也就是如今由大多數關聯式資料庫產品實現的,事務是互動式會話,其中客戶端的查詢和資料修改命令與客戶端上的任意處理和業務邏輯交織。此外,交易持續時間沒有時間限制,因為會話傳統上可能包括人工互動。
然而,今天的現實看起來有所不同。大多數OLTP資料庫事務由使用者通過HTTP傳送到Web應用程式或Web服務的請求觸發。在絕大多數應用程式中,事務的跨度不會超過單個HTTP請求的處理。這意味著,當服務將響應傳送給使用者時,底層資料庫上的任何事務都已提交或中止。在跨越多個HTTP請求的使用者工作流程中(例如,將專案新增到購物車,進行結帳,確認送貨地址,輸入付款詳細資訊以及進行最終確認),沒有任何一個事務跨越整個使用者工作流程; 只有簡短的非互動式事務來處理工作流的單個步驟。
此外,OLTP系統通常執行一組相當小的已知事務模式。在此基礎上,一些資料庫系統將事務的業務邏輯封裝為應用程式提前註冊的儲存過程。為了執行事務,使用某些輸入引數呼叫儲存過程,然後該過程在單個執行執行緒上執行完成,而不與資料庫外的任何節點通訊。
異構分散式事務是有問題的
區分兩種型別的分散式事務很重要:
•同類分散式事務是指參與節點都執行相同資料庫軟體的事務。例如,Google的Cloud Spanner和VoltDB是最近支援同類分散式事務的資料庫系統。
•異構分散式事務跨越不同供應商的幾種不同儲存技術。例如,X / Open XA(擴充套件體系結構)標準定義了跨異構系統執行2PC(兩階段提交)的事務模型,JTA(Java Transaction API)使XA可用於Java應用程式。
雖然一些同類事務實現已經證明是成功的,但異構事務仍然存在問題。就其性質而言,它們只能依賴參與系統的最低公分母。例如,如果應用程式程式在準備階段失敗,則XA事務會阻止執行; 此外,XA不提供死鎖檢測,也不支援樂觀併發控制方案。
此處列出的許多系統(如搜尋索引)不支援XA或任何其他異構事務模型。因此,確保跨不同儲存技術的寫入的原子性仍然是應用程式的挑戰性問題。
建立在事件日誌之上
多語言永續性特點:需要在兩個獨立的儲存系統(如OLTP資料庫(如RDBMS)和全文搜尋伺服器)中維護記錄的應用程式。如果異構分散式事務可用,則系統可以確保跨兩個系統的寫入的原子性。但是,大多數搜尋伺服器不支援分散式事務,使系統容易受到這些潛在的不一致性的影響:
• 非原子寫入。如果發生故障,可以將記錄寫入其中一個系統而不寫入另一個系統,使它們彼此不一致。
• 不同的寫入順序。如果同一記錄有兩個併發更新請求A和B,則一個系統可以按順序A,B處理它們,而另一個系統按順序B,A處理它們。因此,系統可能不同意哪個寫入是最新的,讓他們不一致。
下圖給出了這些問題的簡單解決方案:當應用程式想要更新記錄而不是直接寫入兩個儲存系統時,它會將更新事件附加到日誌中。資料庫和搜尋索引各自訂閱此日誌,並按照它們在日誌中出現的順序將更新寫入其儲存。通過日誌對更新進行排序,資料庫和搜尋索引以相同的順序應用相同的寫入集,使它們彼此保持一致。實際上,資料庫和搜尋索引是對日誌中事件序列的物化檢視。該方法解決了上述兩個問題:
•將單個事件附加到日誌是原子的; 因此,要麼兩個訂閱者都看到一個事件,要麼兩者都沒有。如果訂戶失敗並恢復,它將恢復處理之前未處理的任何事件。因此,如果將更新寫入日誌,則最終將由所有訂戶處理。
•日誌的所有訂閱者都以相同的順序檢視其事件。因此,每個儲存系統將以相同的順序順序寫入記錄。
在此示例中,日誌僅順序化寫入,但應用程式可以隨時從儲存系統讀取。由於日誌訂閱者是非同步的,因此讀取索引可能會返回資料庫中尚不存在的記錄,反之亦然; 對於許多應用來說,這種瞬態不一致性不是問題。對於那些需要它的應用程式,也可以通過日誌序列化讀取; 稍後將介紹此示例。
日誌抽象
有幾個日誌實現可以擔任這個角色,包括Apache Kafka,CORFU(來自Microsoft Research),Apache Pulsar和Facebook的LogDevice。所需的日誌抽象具有以下屬性:
• 持久。日誌將寫入磁碟並複製到多個節點,從而確保在發生故障時不會丟失任何事件。
• 僅附加。只能通過在末尾附加新事件將新事件新增到日誌中。除了附加之外,日誌可以允許丟棄舊事件(例如,通過截斷早於某個保留期的日誌段或通過執行基於金鑰的日誌壓縮)。
• 順序讀取。日誌的所有訂閱者以相同的順序檢視相同的事件。為每個事件分配一個單調遞增的LSN(日誌序列號)。訂閱者從指定的LSN開始讀取日誌,然後按日誌順序接收所有後續事件。
• 容錯。在出現故障時,日誌仍然可用於讀寫。
• 分割槽。單個日誌可以具有它可以支援的最大吞吐量(例如,單個網路介面或單個磁碟的吞吐量)。然而,可以假設系統線性擴充套件,具有許多分割槽 - 也就是說,許多獨立日誌可以分佈在許多機器上 - 並且在不同的日誌分割槽之間沒有排序保證。可以將多個邏輯日誌多路複用到單個物理日誌分割槽中。
關於日誌的訂閱者做出以下假設:
•訂戶可以維護根據日誌中的事件讀取和更新的狀態(例如,資料庫),並且可以在崩潰中倖存。此外,訂戶可以將更多事件附加到任何日誌(包括其自己的輸入)。
•訂戶定期檢查已處理的最新LSN到穩定儲存。當使用者崩潰時,恢復後它將從最新的檢查點LSN恢復處理。因此,訂戶可以處理一些事件兩次(在最後一個檢查點和崩潰之間處理的事件),但它從不跳過任何事件。每個訂戶至少處理一次日誌中的事件。
•使用確定性邏輯在單個執行緒上按順序處理單個日誌分割槽中的事件。因此,如果訂戶崩潰並重新啟動,它可能會將重複事件附加到其他日誌。
現有的基於日誌的流處理框架(如Apache Kafka Streams和Apache Samza)滿足了這些假設。基於有序日誌確定性地更新狀態對應於經典狀態機複製原則。由於在從故障中恢復時可以多次處理事件,因此狀態更新也必須是冪等的。
旁白:恰好一次語義
一些基於日誌的流處理器(如Apache Flink)支援所謂的一次性語義,這意味著即使事件可能被處理多次,處理的效果也會像處理完一次一樣。此行為是通過管理處理框架內的副作用並將這些副作用與將標記處理的日誌部分的檢查點一起原子提交來實現的。
但是,當日志消費者寫入外部儲存系統時,,無法確完全一次的語義,因為這樣做需要跨流處理器和儲存系統的異構原子提交協議,這在許多情況下是不可用的。例如全文搜尋索引儲存系統。因此,具有完全一次語義的框架在與外部儲存互動時仍然表現出至少一次處理並依賴於冪等性來消除重複處理的影響。
原子性和執行約束
需要原子性的典型示例是在銀行/支付系統中,即使兩個帳戶儲存在不同的節點上,從一個帳戶到另一個帳戶的資金轉移必須以原子方式進行。此外,這樣的系統通常需要保持一致性屬性或不變數(例如,帳戶不能透支超過某個設定限制)。圖3顯示瞭如何使用OLEP方法而不是分散式事務來實現此類支付應用程式。具有實心頭的箭頭表示將事件附加到日誌,而具有空心頭的箭頭表示訂閱日誌中的事件。它的工作原理如下:
1.當使用者希望將資金從源帳戶轉移到目標帳戶時,他或她首先將支付請求事件附加到源帳戶的日誌。此事件僅表明轉移資金的意圖 ; 這並不意味著轉移成功。該事件帶有唯一ID以標識請求。
2.單執行緒支付執行程式程式訂閱源帳戶日誌。它維護一個包含源帳戶和當前餘額的事務的資料庫。該過程基於當前餘額和可能的其他因素確定性地檢查是否應該允許支付請求。此日誌使用者與儲存過程的執行非常相似。
3.如果執行程式決定授予付款請求,它會將該事實寫入其本地資料庫,並將事件附加到幾個不同的日誌:至少是源帳戶日誌的付款事件和目的地帳戶日誌的收款事件。如果此付款需要支付費用(例如,由於透支帳戶或貨幣轉換),則可以將附加的付款事件附加到源帳戶日誌,並且相應的收費付款事件可以附加到收費帳戶的日誌。原始事件ID包含在所有這些生成的事件中,以便可以跟蹤它們的原點。
4.由於執行程式訂閱了源帳戶日誌,因此付款事件將被傳遞迴執行程式。它使用唯一的事件ID來確定它已經處理了此付款並將其記錄在其資料庫中。
5.其他帳戶上的付款事件(例如目標帳戶上的收款)同樣由單執行緒執行程式處理,每個帳戶有一個單獨的執行程式。通過基於原始事件ID抑制重複,使事件處理成為冪等的。
6.處理使用者請求的伺服器也可以訂閱源帳戶日誌,從而在處理完付款請求時得到通知。該狀態資訊可以返回給使用者。
如果付款執行程式崩潰並重新啟動,它可能會重新處理在崩潰之前部分處理的一些付款請求。由於執行程式是確定性的,因此在恢復時它將做出相同的決定來批准或拒絕請求,從而可能將重複的支付事件附加到源,目標和費用日誌。但是,根據事件中的ID,下游程式很容易檢測並忽略此類重複項。
多分割槽處理
在該支付示例中,每個帳戶具有單獨的日誌,因此可以儲存在不同的節點上。此外,每個支付執行者只需要訂閱來自單個賬戶的事件,不同的賬戶由不同的執行者處理。這些因素允許系統線性擴充套件到任意數量的帳戶。
在此示例中,是否允許付款請求的決定僅取決於源帳戶的餘額; 您可以假設到目標帳戶的付款總是成功,因為它的餘額只會增加。因此,支付執行者只需要針對源帳戶中的其他事件順序化支付請求。如果其他日誌分割槽需要對決策做出貢獻,則支付請求的批准可以作為多階段過程來執行,其中每個階段將請求相對於特定日誌是順序化的。
將“事務”拆分為流處理器的多級流水線允許每個階段僅基於本地資料進行處理; 它確保永遠不會阻止一個分割槽等待與另一個分割槽的通訊或協調。與多分割槽事務不同,這種流水線設計允許OLEP系統線性擴充套件。
事件處理的優點
除了這種可伸縮性優勢之外,以OLEP樣式開發應用程式還有幾個優點:
•由於每個日誌都可以支援許多獨立訂閱者,因此可以根據事件日誌輕鬆建立新的派生檢視或服務。例如,在圖3的支付方案中,如果達到客戶信用卡上的特定支出限制,則新帳戶日誌訂戶可以向客戶的智慧手機傳送推送通知。可以通過從頭到尾使用事件日誌來構建現有資料集上的新搜尋索引或檢視。
•如果應用程式錯誤導致將錯誤事件附加到日誌中,則很容易恢復:訂閱者可以程式設計為忽略不正確的事件,並且可以重新計算從事件派生的任何檢視。相反,在支援任意插入,更新和刪除的資料庫中,從不正確的寫入中恢復更加困難,可能需要從備份中還原資料庫。
•類似地,使用僅附加日誌而不是可變資料庫除錯要容易得多,因為可以重放事件以診斷特定情況下發生的事件。
•出於資料建模的目的,與自由形式的資料庫突變相比,僅追加事件日誌越來越受歡迎; 這種方法在領域驅動設計社群中稱為事件溯源。基本原理是事件比表上的插入/更新/刪除操作更準確地捕獲狀態轉換和業務流程,並且這些狀態更新可以描述為處理事件所產生的副作用。例如,“學生取消課程註冊”事件清楚地表達了意圖,而副作用“從登錄檔中刪除了一行”和“一個取消原因被新增到學生反饋表”則不太清楚。
(banq注:後兩句是資料庫世界語言,其中有“表”和“行”等詞語,這些陳述語句所指目標是在資料庫這個範疇的世界,而不是指向真實世界,條條大路通羅馬,如果這個羅馬是紙上的羅馬,就永遠到不了。)
•從資料分析的角度來看,事件日誌比資料庫中的狀態更有價值。例如,在電子商務環境中,業務分析師不僅可以在結賬時檢視購物車的最終狀態,而且還可以檢視新增到購物車和從購物車中移除的完整商品序列,因為已移除的商品包含資訊(例如,一種產品可替代另一種產品,或者顧客可能會在以後的某種情況下返回購買某種商品)。
•對於分散式事務,如果任何一個參與節點不可用,則整個事務必須中止,因此故障會被放大。相反,如果日誌具有多個訂戶,則它們彼此獨立地進行:如果一個訂戶發生故障,則不會妨礙釋出者或其他訂戶的操作,因此包含故障。
OLEP方法的缺點
在前面的示例中,日誌使用者會更新資料儲存中的狀態,雖然OLEP方法確保日誌中的每個事件最終都會被每個消費者處理,即使面對崩潰,在處理事件之前沒有時間上限。
這意味著如果客戶端從兩個不同的消費者或日誌分割槽更新的兩個不同的資料儲存中讀取,則客戶端讀取的值可能彼此不一致。例如,源帳戶和目標帳戶一個處理完支付,另外一個還在讀取日誌沒有處理完支付。因此,即使帳戶最終會收斂到一致狀態,但在某個特定時間點讀取時,它們可能會不一致。
請注意,在ACID上下文中,防止這種異常屬於隔離的標題,而不是原子性 ; 僅具有原子性的系統不保證將以一致的狀態讀取兩個帳戶。以“讀取已提交”隔離級別執行的資料庫事務(包括PostgreSQL,Oracle DB和SQL Server在內的許多系統中的預設隔離級別),這種方式在從兩個帳戶讀取時可能會遇到相同的異常。防止此異常需要更強的隔離級別:“可重複讀取”,快照隔離或可序列化。
(banq注:但是更強的隔離級別意味效能更低,意思是資料庫的預設ACID設定也不能保證真正強一致,即使是升級到更高階隔離級別,鑑於實現複雜,包括Oracle資料庫都有漏洞,可查詢相關文章,總之,不一致性在資料庫ACID真正產品中也是存在的,只是在ACID理論上不存在,我們都被理論馴化了,忘記第一性原則。)。
目前,OLEP方法不為直接傳送到資料儲存的讀取請求提供隔離(而不是通過日誌順序化)。希望未來的研究能夠實現更強的隔離級別,例如跨日誌更新的資料儲存中的快照隔離。(banq注:讀取請求隔離通過訂閱主題topic的設計可實現)
案例研究:紐約時報
紐約時報維持在Apache kafka儲存了1851年以來出版的所有文字內容,影象檔案儲存在單獨的系統中,但影象的URL和標題也儲存為日誌事件。
每當釋出或更新一段內容(稱為資產)時,都會在該日誌中附加一個事件。有幾個系統訂閱了這個日誌:例如,每篇文章的全文都寫入索引服務進行全文搜尋; 需要更新各種快取頁面(例如,具有特定標籤的文章列表,或特定作者的所有文章); 和個性化系統通知可能對新文章感興趣的讀者。
每個資產都被賦予唯一識別符號,並且事件可以建立或更新具有給定ID的資產。此外,事件可以引用其他資產的識別符號 - 非常類似於關聯式資料庫中的規範化模式,其中一個記錄可以引用另一個記錄的主鍵。例如,影象(具有標題和其他後設資料)是可以由一個或多個文章引用的資產。
日誌中的事件順序滿足兩個規則:
•只要一個資產引用另一個資產,釋出引用資產的事件就會出現在引用資產之前的日誌中。
•更新資產時,最新版本是日誌中最新事件釋出的版本。
例如,編輯器可能會發布影象,然後更新文章以引用影象。然後,日誌的每個使用者按順序通過三個狀態:
1.存在舊版本的文章(不引用影象)。
2.影象也存在,但尚未被任何文章引用。
3.文章和影象都存在,文章引用影象。
不同的日誌消費者將在不同的時間以相同的順序通過這三種狀態。日誌順序確保沒有消費者處於文章引用尚不存在的影象的狀態,從而確保引用的完整性。
此外,每當更新影象或標題時,需要在快取和搜尋索引中更新引用該影象的所有文章。這種一致性模型很容易適用於日誌,它提供了分散式事務的大部分好處,而且沒有效能成本。
結論
跨異構儲存技術的分散式事務支援要麼不存在,要麼受到差的操作和效能特徵的影響。相比之下,OLEP越來越多地用於在這種設定中提供良好的效能和強一致性保證。
在資料系統中,將日誌(例如,預寫日誌)用作內部實現細節是很常見的。OLEP方法是不同的:它使用事件日誌而不是事務作為資料管理的主要應用程式程式設計模型。仍然使用傳統資料庫,但它們的寫入來自日誌而不是直接來自應用程式。工業界的一些有影響力的人物已經探索過這種方法,例如Jay Kreps,4 Martin Fowler和Greg Young,其名稱包括事件採購/溯源和CQRS(Command / Query Responsibility Segregation)。
OLEP的使用不僅僅是開發人員的實用主義,而是提供了許多優點。這些包括線性可擴充套件性; 一種有效管理多語言永續性的方法; 支援增量開發,迭代新增或刪除新的應用程式功能或儲存技術; 通過直接訪問事件日誌,對除錯提供出色的支援; 並提高可用性(因為當其他節點發生故障時,執行節點可以繼續進行)。
因此,預計OLEP將越來越多地用於在使用異構儲存技術的大規模系統中提供強一致性。
相關文章
- .NET分散式Orleans - 6 - 事件溯源分散式事件
- 分散式事務處理方案,微服事務處理方案分散式
- Laravel 分散式事務處理Laravel分散式
- Dubbo 分散式事務一致性實現分散式
- 使用Kafka實現事件溯源Kafka事件
- springcloud分散式事務處理 LCNSpringGCCloud分散式
- 事件溯源的好處在於可在軟體中捕獲現實世界 – Jessitron事件
- 阿里是如何處理分散式事務的阿里分散式
- Redis實現分散式鎖(setnx、getset、incr)以及如何處理超時情況KJBPRedis分散式
- 13.SpringCloudSeata處理分散式事務SpringGCCloud分散式
- Oracle分散式事務典型案例處理Oracle分散式
- SpringCloud Alibaba Seata處理分散式事務SpringGCCloud分散式
- 分散式事務(3)---RocketMQ實現分散式事務原理分散式MQ
- 分散式事務之最終一致性實現方案分散式
- mount程式在systemctl守護的情況下,mount dir程式被oom後重新啟動失敗的處理方法OOM
- 使用EventNext實現基於事件驅動的業務處理事件
- 分散式事務(4)---RocketMQ實現分散式事務專案分散式MQ
- oracle分散式事務異常處理方法Oracle分散式
- 事件流與事件溯源事件
- 事件協作和事件溯源事件
- ORACLE分散式事務鎖各種場景下的處理詳解Oracle分散式
- Chronicle事件溯源的最佳實踐事件
- 事件處理事件
- postgresql連線失敗如何處理SQL
- 分散式事務(2)---強一致性分散式事務解決方案分散式
- 分散式事務理論加實戰分散式
- 使用Datomic實現沒有麻煩的事件溯源事件
- ORACLE懸疑分散式事務問題處理Oracle分散式
- 分散式 | 資料庫連線如何正確處理 TCP 連線三次握手失敗分散式資料庫TCP
- 許可權處理 - 用redis實現分散式session~ (cookie && session )Redis分散式SessionCookie
- echarts 繫結事件處理函式Echarts事件函式
- JavaScript 註冊事件處理函式JavaScript事件函式
- 分散式事務,強一致性方案有哪些?|分散式事務系列(二)分散式
- PHP 事件溯源PHP事件
- eBay透過事件溯源實現持續交付事件
- 分散式系列七: 分散式事務理論分散式
- 分散式事務的幾種實現方式分散式
- .NET開源的處理分散式事務的解決方案分散式