漫談Google Percolator分布式事務

2021-10-11 11:12:32 字數 3886 閱讀 9033

前段時間忙雙11忙到廢寢忘食,這期間又被各種奇奇怪怪的小病折騰了半個多月,整個人狀態不是很好,部落格也連續吃灰到現在,請看官勿怪。好在今天感覺還不錯,可以繼續寫點東西了。

為了應對業務資料的**性增長以及mysql業務庫分庫分表現狀的各種不便,筆者的團隊近期用一周時間突擊調研tidb,並部署了由16個節點組成的tidb集群,同時開始逐漸探索利用它替代mysql的可能性。在調研過程中,我們了解到tidb能夠100%支援acid事務。並且不同於傳統的xa,tidb採用的是google提出的percolator分布式事務協議。本文聊一聊percolator的部分細節,之後的文章再說它在tidb事務(包括樂觀事務和悲觀事務)中的具體應用。

bigtable是google實現的分布式結構化大資料儲存系統,我們耳熟能詳的hbase就是bigtable的開源版本實現。其資料模型可以視為多維的、支援mvcc的k-v map,即:

(row:string, column:string, timestamp:int64) -> data:string

bigtable原生支援單行事務,即能夠保證一行內乙個或多個列族上的多個操作的acid特性。但是,很多情況下使用者都需要在單個事務中更改多行資料,只有單行事務顯然不夠用。而基於bigtable的分布式特性,跨行事務與單行事務相比更加複雜,需要注意的三個要點列舉如下:

在這三個要點的基礎上,google的大佬們又設計了percolator分布式事務協議,借助bigtable原生的單行事務實現了跨行事務。percolator的設計理念集中體現在osdi 2010的一篇**《large-scale incremental processing using distributed transactions and notifications》中。下圖示出啟用percolator之後的bigtable服務架構。

bigtable依靠chubby(等同於zookeeper)提供分布式協調服務。圖中的每乙個大矩形表示一台伺服器,其上執行的服務包括tablet server(等同於hbase regionserver)、chunk server(等同於hdfs datanode),以及新加入的percolator worker。另外,還引入了timestamp oracle(簡稱tso)作為全域性授時服務。也就是說,percolator的實現僅需要增加2個服務,以及在客戶端提供與percolator協議相容的庫。

percolator事務分為兩個階段:預寫(pre-write)和提交(commit),本質上相當於乙個加強的2pc。另外,所有啟用了percolator事務的表中,每乙個列族都會預先增加兩個列,分別是:

另外,為了簡化場景,假設儲存使用者資料的列只有乙個,名為data。

預寫階段

客戶端啟動事務,從tso獲取時間戳,記為start_ts,並向percolator worker發起pre-write請求。

在該事務包含的所有寫操作中選取乙個作為主(primary)操作,其餘的作為次(secondary)操作。主操作將作為整個事務的互斥點,標記事務的狀態。

先預寫主操作,成功後再預寫次操作。

在預寫過程中,對每乙個寫操作都要執行檢查:

檢查通過後,以start_ts作為版本號將資料寫入data列,但不更新write列,亦即此時寫入的資料仍然不可見。

對操作行加鎖,即更新lock列的鎖資訊:主操作行的lock直接標為primary,次操作行的lock則標為主操作行的行鍵和列名。

注意:處理每一行時,上述步驟3、4、5每次都會在同乙個bigtable單行事務中進行,保證原子性。

提交階段

客戶端從tso獲取時間戳,記為commit_ts,並向percolator worker發起commit請求。

檢查主操作行對應的lock列所在的primary標記是否存在,如果不存在(可能已經被清理,見後文所述)則失敗,取消事務;如果存在則繼續。

以commit_ts作為版本號,將start_ts更新到write列中。也就是說在本階段完成後,預寫階段寫入的資料將會可見。

對該行解鎖,即刪除lock列的鎖資訊。

若步驟1~4均成功,說明主操作行成功,代表整個事務實際上已經提交。接下來只需非同步地提交每個次操作即可,即重複步驟3、4的更新write列和清除lock列操作。

注意:上述步驟2、3、4會在同乙個bigtable單行事務中進行,保證原子性。另外,如果次操作的提交失敗,則仍然要回滾事務。

示例:經典轉賬流程

下圖來自原始**,描述了percolator協議下的一次轉賬流程(bob向joe轉賬$7)。注意每一列中冒號之前的是版本號,冒號之後的則是該列儲存的資料。附帶的說明清楚易懂,筆者就不多廢話了。

快照隔離級別

傳統關係型資料庫中定義的隔離級別有4種(ru、rc、rr、s),而percolator提供的隔離級別是快照隔離(snapshot isolation, si),它也是與mvcc相輔相成的。si的優點是:

si下的事務都會帶有兩個時間戳,即上文講解percolator流程時提到的start_ts(下圖中空心方塊)與commit_ts(下圖中實心圓點)。si硬性要求乙個事務提交時的commit_ts大於所有之前產生的start_ts和commit_ts,當然這已經由tso來保證了。

看圖說話:

si存在的主要問題是寫偏斜(write-skew),看官可自行查詢資料去了解,不再贅述。

分散的鎖機制

由上文的描述可以看出,percolator巧妙地反其道而行之,把事務相關的鎖資訊分散到了每一行中,並通過將主操作作為互斥點,免去了重新設計一套全域性鎖機制的麻煩。另外,預寫階段的步驟3中檢查到任意鎖衝突都會取消事務,簡單粗暴地避免了死鎖的發生。最後,percolator構建在帶冗餘的分布式檔案系統(**的語境中是gfs)之上,所以不必擔心鎖資訊會丟失。

快照讀與鎖清理

相對於寫流程,讀流程就簡單很多了:從tso獲取當前start_ts,然後檢查lock列,判斷早於當前start_ts的區間內是否有鎖。如果沒有,則從write列中根據commit_ts獲取到最新提交的start_ts,再根據獲取到的start_ts從data列中獲取資料。如果有鎖,則意味著存在未提交的事務,需要等待持鎖的事務提交,才能讀取最新的資料。讀流程也是在bigtable單行事務中進行的。

這裡會產生兩個問題:

很顯然,基於持鎖事務結果的不確定性(可能提交也可能回滾),這樣會打破si對讀操作的保證,亦即產生幻讀。看官稍稍思考一下即可理解。

答案是由讀操作來自主完成,即不會無限等待下去,而是在一定時長的延遲之後直接將卡住的主操作鎖資訊清理掉,並讀取最新版本的資料。

分兩種情況討論:其一,讀操作直接讀到了原事務中具有primary鎖資訊的行,說明原事務未提交成功,需要回滾(即清理鎖);其二,讀操作讀到了原事務中具有secondary鎖資訊的行,此時仍然要去對應的primary行上查詢鎖是否存在,如果存在,說明原事務未提交成功,將其回滾;如果不存在,說明已提交成功,將其前滾(即清理鎖的同時更新對應的write列)。

可見,主操作鎖和從操作鎖存在的緊密關聯能夠有效保證percolator事務的安全性與活性,即:同一事務中的任意兩個寫操作結果肯定一致,且所有操作的結果要麼是提交成功,要麼是提交失敗。

客戶端的作用

如果套用2pc的概念,客戶端(在tidb內部是tikv-client)顯然扮演了協調者(coordinator)的角色。傳統2pc的協調者存在單點問題,但是percolator中的客戶端並沒有此問題——就算客戶端失敗,事務也只會有成功與失敗兩種狀態,不會破壞一致性。

有缺點麼?

當然有。

寫了這麼多,似乎找回一點手感了,2023年剩下的這二十多天慢慢恢復更新吧。

民那晚安晚安。

漫談海外N多事(1)漫談團隊管理

有段句話說的對 管理既是一門科學,又是一門藝術,管理的科學性在於管理作為乙個活動過程,其間存在著一些列基本客觀規律,有一套分析問題 解決問題的科學方 並在實踐中得到不斷地驗證和豐富,可複製和學習,並可指導人們視線有效的管理.管理的藝術性就是強調其實踐性和創新性.要有效地實現管理,管理者比需要管理實踐...

漫談紅外遙控

漫談紅外遙控 紅外遙控是目前家用電器中用得較多的遙控方式,為此,本欄目特邀請李洪明先生撰寫了關於紅外遙控的系列文章,其中包括 漫談紅外遙控 單通道紅外遙控電路 8通道紅外遙控電ba5140 ba520410 18路紅外遙控電路等四篇,其餘三篇將在今後幾期陸續刊出。在講紅外遙控之前,首先講一講什麼是紅...

div css布局漫談

1.css布局常用的方法 float none left right 取值 none 預設值。物件不飄浮 left 文字流向物件的右邊 right 文字流向物件的左邊 它是怎樣工作的,看個一行兩列的例子 xhtml 這裡是第一列 這裡是第二列 css wrap column1 column2 cle...