Omid: 一種無鎖的分散式事務方案
說明
Omid是Yahoo! 提出的一種分散式事務技術方案,同Google的Percolate同樣實現了Snapshot的隔離級別。與Percolate不同的是,Omid使用了獨立的TSO服務來維護所有事務資訊以避免Percolate帶來的效能損失以及垃圾鎖被動清理問題。本文簡單描述了Omid演算法思想以及一些優化。
原始演算法
引入一個TSO服務(Transaction Status Oracle)來獨立地維護所有事務狀態,包括活躍事務、已提交事務、abort事務。而且,由於實現了Snapshot Isolation,每個事務需要維護兩個時間戳:事務開始時間 和事務提交時間
,事務的時間戳同樣由TSO負責分配,TSO會保證每個事務的時間戳全域性唯一且單調遞增。
事務開始時(包括只讀事務)首先向TSO申請分配 ,接下來將事務中涉及修改的所有row內容寫入至資料儲存端,寫入會將資料連同
一併儲存至後端的KV儲存。Omid假設資料儲存端是一個帶版本功能的kv資料庫(如Hbase、BigTable之類)。
當事務所有row成功寫入後,接下來事務開始提交。客戶端向TSO服務發起一次提交請求的RPC呼叫中會攜帶該事務的開始時間戳 和事務修改的所有row。TSO收到此請求後進行如下處理:


,如果有,說明在該事務執行過程中,有其他事務已經提交了對該事務中某一row的修改,本次提交的事務中包含的row資料可能是髒的,因此,需要abort本次事務;
2. 如果1檢測通過,為事務分配提交時間戳
;
3. 為事務涉及的每一個row更新最後提交時間戳lastCommit(row) =
;
4. 返回commit成功給發起提交請求的客戶端
在Omid中,TSO維護了所有事務的狀態,且包含所有row的最後提交時間。

由於事務狀態維護在TSO,對於只讀事務,勢必也需要TSO參與。具體來說,對於只讀事務,客戶端首先向TSO申請事務開始時間戳 ,接下來客戶端以
從資料儲存節點讀取最近一次內容(即版本小於
的最大的版本內容),但是由於資料儲存端沒有維護事務狀態資訊,所以客戶端無法確認讀取到的是否是髒資料(即還未提交的更新),接下來客戶端還需要拿獲取到的資料版本向TSO服務發起一次 inSnapshot 請求來確認資料是否合法,inSnapshot請求中攜帶的引數包括:
:只讀事務的開始時間戳
:記錄row內容被更新事務的起始時間戳,我們前面說過事務更新時,事務中的每個row都會攜帶該事務的起始時間戳
TSO服務接收到inSnapshot請求後,會判斷:
如果事務 已經提交且
,那說明只讀事務讀取到的內容是已經提交且可以被
可見的,否則要麼是事務
尚未提交或者其對
不可見。
Omid Design
在原始設計中存在以下幾個問題:
TSO中需要維護所有row的最後提交時間,而且這些資訊需要維護在記憶體中以保證效率,且TSO難以做到水平擴充套件
Omid針對上面的問題對原始設計進行了優化。
記憶體使用優化
在Omid中,只維護最近一段時間內的所有row的提交時間戳。過早被提交的row則會被淘汰,同時維護一個 ,記錄了所有被淘汰的row記錄的提交時間的最大值。因此,對於所有被淘汰的row,其
。
在Omid中,存在著如下幾個連結串列:
uncommit txn list:活躍事務連結串列,記錄正在執行中的事務;
commited txn list:已提交事務連結串列,記錄那些已經完成的事務記錄,需要說明的是,這裡會儲存所有row的最後提交時間,這也是淘汰機制的主要物件
abort txn list:abort事務連結串列,所有被abort的事務都會記錄在這裡
每次執行淘汰後,還會將當uncommit txn list中所有開始時間戳小於Tmax的事務加入至abort txn list中,相當於主動淘汰執行時間過長的事務(一般淘汰間隔時間在幾十秒,一個事務超過幾十秒可能也確實出現了異常)。
如果接下來客戶端有針對該row的提交,按照原始演算法,需要從記憶體的已提交記錄表commit_list中查詢該row的最後提交時間戳T_c(row),如果該row資訊被清除了,判斷邏輯變成下面這樣:
Commit request (txni, W) : {cmt, abrt} { if Tmax > Ts(txni) then return abort; end if for each row r ∈ W do if lastCommit(r) > Ts(txni) then return abort; end if end for // Commit txni Tc(txni) ← TimestampOracle.next(); for each row r ∈ W do lastCommit(r) ← Tc(txni); end for return commit }
如果:
-
,說明待提交的事務已經足夠老,老到其相關的row記錄都已經可能被清除導致沒法判斷了,這裡就粗暴地abort該事務。注意:這裡的判斷其實是不一定準確的,因為雖然
,但是涉及到具體的row,有可能
,此時該事務是可以提交成功的。這就是丟棄歷史狀態帶來的負面作用;
-
,說明待提交的事務開始時間戳比所有已經淘汰的row的最後提交時間戳都更大,因此,不用再與那些被淘汰的row比較,只需要關注那些尚未淘汰的即可,此時的判斷邏輯與原始演算法的並無二致。
如果前面的事務衝突性檢查順利通過,接下來進入提交最後階段:更新事務修改row的最後提交時間戳,這個與原始演算法也相同。
對於inSnapshot請求,由於丟棄了row修改的歷史記錄,其處理演算法發生了較大變化:
// 判斷事務txnr(事務開始時間)讀取的記錄資料是否有效 // txnf(事務開始時間)是事務txnr在資料儲存端獲取到的最新版本 inSnapshot(txnf , txnr) : {true, false} { if Tc(txnf ) != null then return Tc(txnf ) < Ts(txnr); end if if Tmax < Ts(txnf ) or aborted(txnf ) then return false; end if return true; }
判斷規則:
- 如果txnf提交記錄未被淘汰,那直接判斷其提交時間戳
與
的關係,這個與原始演算法保持一致;
- 如果未找到txnf的提交記錄,那可能有兩種情況:
- txnf已經被提交但是其提交記錄被淘汰,此時必有
;
- 未找到txnf的提交記錄也有兩種可能:
- txnf開始時間戳
,說明事務在上一次淘汰後開始,但目前處於尚未提交狀態,這時候認為該事務依然在執行中;
- txnf開始時間戳
,且未發現其Tc(txnf),說明事務執行時間過長,按照演算法設計,其一定會被加入abort list中;
- 上面兩種情況下,客戶端讀到的事務txnf的最新的資料都應該是無效的,給客戶端返回false
- 反之,如果
且txnf不在abort list中,說明事務txnf確實已經提交,只是提交記錄是被淘汰了,此時應該告知客戶端其看到的內容是有效的
RPC請求頻繁優化
原始設計中,客戶端每次從儲存服務端讀取最新的資料版本後還需要向SO服務發起一次inSnapshot查詢請求,在Omid中通過一定手段優化了這次請求。
Omid優化思想也比較簡單:將SO服務端的事務狀態複製到客戶端,客戶端在本地即可判斷inSnapshot。這裡的事務狀態包含以下內容:
Tmax
所有活躍事務記錄
所有已提交且未被淘汰事務的row修改記錄
所有abort事務記錄
具體來說,客戶端在開始任何一個事務之前都必須要向SO申請一個時間戳Ts(包括只讀事務)。而SO返回的響應內容也很小(只包含一個時間戳欄位而已),於是,Omid在這裡面做了一些文章:SO伺服器在返回給客戶端的響應中也包含了當前最新的事務狀態。
另外,Omid採用了增量更新與壓縮的方式進一步壓縮資料傳輸量:每次客戶端的分配Ts請求的響應中只攜帶自上次以來SO發生更新的記錄內容且被壓縮傳送,如果客戶端的請求夠頻繁,那麼預期經壓縮後的響應體也不會足夠大。另外,對於那些長期無請求的客戶端,SO也採取了定期推送的策略以避免過大的響應。
Omid的一些實現
SO可靠性與可用性
SO維護的事務元資訊對系統可用性至關重要,而在執行過程中元資訊常駐記憶體,如何避免記憶體狀態丟失?首先,SO需要持久化的內容包括:Tmax、Tc list以及abort list,活躍事務資訊則無需持久化,這是因為一個事務如果不在Tc list以及abort list中,則其便是活躍事務,而且inSnapshot請求的判斷不依賴活躍事務資訊。
SO使用了WAL來儲存事務資訊的更新,根據論文描述,確切地說是使用了Bookeeper系統,同時也使用了Write Batch等優化手段。
客戶端啟動
在客戶端啟動時,其尚未複製任何的SO伺服器對於事務狀態的資訊,成功建立連線後,SO伺服器會為其分配一個啟動時間戳Tinit,可知Tinit > Tmax,如果此時從資料伺服器中讀取的資料版本為Ts,且有Tinit > Ts > Tmax,此時客戶端是無法決策資料版本合法性,必須向SO伺服器申請仲裁。而隨著客戶端的運轉,客戶端會逐漸複製SO伺服器的最新事務狀態資訊,後續的判斷便可本地處理,不再依賴SO伺服器仲裁。
客戶端靜默問題
前面提到客戶端如果長時間不與SO伺服器通訊,客戶端的事務狀態資料便落後於SO。Omid中會定期(30s)向靜默客戶端推送狀態資料。
維護每個客戶端的事務更新位點
一旦有新的事務提交,SO便會將事務的資訊( )進行壓縮後放入全域性的commitinfo內。對於每一個客戶端連線,SO維護了一個在commitinfo內的位點資訊,該位點指示下次該從何處給客戶端傳送狀態資訊。這樣做的好處是對所有的客戶端只維護了一份事務狀態資訊。測試也顯示,使用該方案可以將客戶端數量輕鬆擴充套件至1000+而對整體效能影響處於可接受範圍之內。