奈學教你五分鐘學會分散式事務

奈學教育發表於2020-06-06

從概念開始

我們先從事務的定義開始。事務即一系列讀存動作被當作一個執行單元,這些動作要麼全成功,要麼全失敗,執行動作的過程中保證資料的隔離性和一致性。

我們拋離資料庫這個特定場景,先假設一個資料儲存裝置,我們定義兩個標準操作,一個讀一個寫。當寫操作依賴於讀到的資料時,執行的順序決定了得到的結果。

當單執行緒時,任意讀或寫操作在這個資料容器上,他必然是符合上述所有的要求的。

當多執行緒的時候,任意讀寫,實際上就是導致標準的 race condition,大部分情況下我們是不知道執行結果的。對於單核cpu來說,多執行緒實際上是cpu模擬,可以理解成所有的操作是合併到一起順序執行的。在我們不加以控制的前提下,合併的操作佇列必然是相對之間無序的。要想多執行緒情況下,達到單執行緒一樣的事務性。最簡單粗暴的辦法,就是保證所有請求序列。

保證所有請求序列執行的最簡單粗暴的辦法就是鎖。任意執行緒操作的適合,上鎖,只有這個執行緒的所有動作做完之後,才能開始下一執行緒提供的動作。這樣一來不管多少事務並行過來,保證了組內的動作一定是序列的。多執行緒下的動作組的事務性也就保證了。實際上工程後來的進化也是這樣的,把執行順序會影響結果的操作鎖住,強制線性執行。

對於資料庫來說也是一樣,要完成事務的特性,本質還是鎖。資料庫實際上把讀鎖和寫鎖是分開的,顆粒度更細。mvcc的本質也是鎖,可以理解成利用 copy-on-write 讓寫鎖獨立出來,不影響其他的操作,資料庫事務進化的本質就是對鎖的最佳化,從表鎖到行鎖,不停的降低鎖的顆粒度,針對不同的場景使用顆粒度更小的鎖。

不一致性的由來

單資料庫例項讀寫必然是高度一致性的。問題是,單例項,更確切來說是單例項MySQL,是很難扛住所有流量的。絕大部分web應用必然是讀多寫少的,針對這一系統,大部分業務都做了讀寫分離,主寫從讀。這樣的情況下,主從實際上是有一個不一致視窗的。不去管這個一致性視窗有多麼的小,只要經過網路這樣的一個慢速裝置,不一致視窗,實際上必然存在。所幸的事情是,大部分應用對這個不一致性是可以容忍的。

再隨著業務的發展,單機甚至都可能扛不住所有流量。我們需要去分拆資料庫。這時問題就更大了,這不僅僅是外部的問題了,核心的問題可能是,在在沒有單機事務的庇護下,我們如何去實現一致性。

大部分人的第一個想法是,資料庫 XA ,對於兩個庫,引入外部協調者,然後做2pc。這樣真的可行麼。我們仔細想一下,如果資料庫扛不住流量分拆,那麼必然是分拆到兩個機器,那麼原本在記憶體中進行的協調操作必須經過網路這個慢速裝置,並且XA為了做2pc,必然加長鎖, 系統正常效能一下子幹掉9/10,還自帶隨機抽風。這樣搞必然走不通。

另外一部分的高階解法是,CockroachDB, TiDB,各種基於F1的高精尖分散式資料庫,徹底替換掉MySQL。公司最重要的部分,可能就是資料部分,完全替換資料庫的做法,實際上容易踩坑,並且踩進去還得爬出來,最氣的這坑是還是自己挖的。在筆者個人的看法裡,一個檔案系統的徹底成熟大概要經過5到10年的時間,ex4是差不多經過5年才成熟起來的。對於資料庫系統想來也不會差太多。底層資料儲存,可以嘗試,不建議勇猛。不是這個方法不可行,只是可能需要再等上一段時間。但是從另一角度來說,長期來看,這種方案很可能是最優解法。

重新定義需要解決的問題

分散式環境的著名問題是強CAP不可兼得。我們可以這樣想一下這句話,分散式環境下,要不等資料同步一致再提供服務,要麼我保證服務,不去管資料同步的問題。因為資料同步必然有時間視窗,所以強CAP 不可兼得。又因為服務可用對網際網路公司應該說是最基本的要求。大部分場景下,網際網路公司的選擇都是強A弱C,追求最終一致性,保證高可用。(這裡無意去爭辯CAP是否過時,只希望能夠表達清楚場景,如果對這裡有疑問的話,需要想一下,心中所列反例到底是A還是HA)

任意一個系統,想要達成最終一致性,場景無非是兩種。

第一個場景,某動作發生後,後續動作保證完成。

第二個場景,某動作發生後,中間動作硬性無法完成,那麼需要回滾操作,並清除之前操作的副作用。

第一個場景非常適合訊息佇列,訊息佇列在處理這個場景的時候非常合適,訊息佇列天然具有編排服務的能力,單事件觸發後續的多個服務,後續操作非同步完成,不降低系統的吞吐的下限。訊息做 at least once 的投遞,訊息消費對訊息冪等,訊息的傳送機制利用單機事務結合事務訊息,是對於這個場景非常優雅的解法。

第二個場景,更接近標準的事務場景,只不過因為跨例項,跨機器,單機事務是不可依靠的。

更確切的說,第二個場景就是分散式事務元件需要解決的問題。

設計與實現

我們基於 SAGAS 來模擬長事務,從而解決來解決上述的第二個場景。

SAGA是上個世紀80年代誕生的思想,當時想解決的問題,更多的是長事務的問題,因為事務過長,實際資源鎖的時限也過長,資源損耗嚴重 。在現有微服務演化的前提下,我們需求的不就是可模擬的類似長事務的行為麼,於是SAGAS就非常的適用於我們的場景。

SAGAS簡單來說,用短的單機事務拼接長跨機事務,這一組單機事務我們稱為事務組。當事務正常時正常處理,事務組執行中間有異常case時,反向補償整個事務組。我們把事務組想象成一個狀態機,那麼最終要麼在完成狀態,要麼最終在補償成功狀態。補償善後完成後,可以認為系統到達最終一致性的狀態。

我們依據上述抽象,設計開發落地了我們的分散式事務元件。

系統架構如下圖:

分散式事務元件流程如下,當使用者請求進入上層服務的時候,在當前執行緒上下文生成一個唯一的事務ID做標示事務組,rpc元件在上層服務中呼叫原子層時,會把事務組內所有對遠端的原子層的呼叫,記錄為一個事務組,持久化到遠端儲存層。

事務補償器執行狀態機,找到需要補償的事務組,多次嘗試補償呼叫,直至成功為止,事務組補償組間並行,組內序列,補償呼叫經過限流器,防止後端雪崩。

這樣,原子層透過事務保證一致性,邏輯層透過記錄和補償服務保證每一次邏輯層作業都會到達最終一致的狀態。事務元件請求的耗損僅為持久化遠端呼叫的時間加上初始化事務組的時間。對於業務有即時強一致性的要求的場景,實際上是業務需要一個分散式鎖來保證某一屬性是不存在一致性視窗的。由於補償服務本身不含有狀態,水平擴充套件是非常容易的。另一方面來說,因為事務組實際上是透過事務ID標示,我們可以透過事務ID去把整個事務的補償流程串起來,視覺化分析也非常的容易。

總結

1.事務本質還是併發和鎖的問題。

2.分散式環境下,一般情況一致性需要為可用性犧牲,保障最終一致性,兩種場景,兩種手段。

3.基於 sagapattern 模擬長事務來解決分散式事務問題。

更多免費技術資料及影片


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69976011/viewspace-2696693/,如需轉載,請註明出處,否則將追究法律責任。

相關文章