天天看點

資料庫事務系列-HBase行級事務模型

HBase是BigTable的開源實作,事務模型也與BigTable一脈相承 – 僅支援行級别的事務。雖然Jeff Dean大神在接受采訪時公開承認目前在技術領域最後悔的事情就是沒有在BigTable中加入跨行事務模型,以至于之後很多團隊都在BigTable之上重複造各種各樣的分布式事務輪子。這點筆者是認同的,現在确實有很多團隊在HBase之上造了很多輪子(Tephra | Trafodian | Omid),試想如果這個工作做在了BigTable裡面,這些團隊的人是不是可以做更多其他有意義的事情了~ 所幸的是之後Google又釋出了一篇介紹分布式事務模型的的paper – Percolator,現在很多團隊都參考該論文實作分布式事務,包括TiDB、Omid等,也算是一種彌補吧。

HBase資料會首先寫入WAL,再寫入Memstore。寫入Memstore異常很容易可以復原,是以保證寫入/更新原子性隻需要保證寫入WAL的原子性即可。HBase 0.98之前版本需要保證WAL寫入的原子性并不容易,這由WAL的結構決定。假設一個行級事務更新R行中的3列(c1, c2, c3),來看看之前版本和目前版本的WAL結構:

1. 之前版本WAL結構:

<logseq1-for-edit1>:<KeyValue-for-edit-c1>

<logseq2-for-edit2>:<KeyValue-for-edit-c2>

<logseq3-for-edit3>:<KeyValue-for-edit-c3>

每個KV都會形成一個WAL單元,這樣一行事務更新多少列就會産生多少個WAL單元。在将這些WAL單元append到日志檔案的時候,一旦出現當機或其他異常,就會出現部分寫入成功的情況,原子性更新就無法保證。

2. 目前版本WAL結構:

<logseq#-for-entire-txn>:<WALEdit-for-entire-txn>

<logseq#-for-entire-txn>:<-1, 3, <Keyvalue-for-edit-c1>, <KeyValue-for-edit-c2>, <KeyValue-for-edit-c3>>

通過這種結構,每個事務隻會産生一個WAL單元。這樣就可以保證WAL寫入時候的原子性。

1. 為什麼需要寫寫并發控制?

現在假設有兩個并發寫入請求同時進來,分别對同一行資料進行寫入。下圖所示RowKey為Greg,現在分别更新列族info下的Company列和Role列:

資料庫事務系列-HBase行級事務模型
資料庫事務系列-HBase行級事務模型

如果沒有任何并發控制政策的話,寫入資料(先寫WAL,再寫memstore)可能會出現不同KV寫入”交叉”現象,如下圖所示:

資料庫事務系列-HBase行級事務模型

這樣的話,使用者最終讀取到的資料就會産生不一緻,如下:

資料庫事務系列-HBase行級事務模型

2. 如何實作寫寫并發控制?

實作寫寫并發其實很簡單,隻需要在寫入(或更新)之前先擷取行鎖,如果擷取不到,說明已經有其他線程拿了該鎖,就需要不斷重試等待或者自旋等待,直至其他線程釋放該鎖。拿到鎖之後開始寫入資料,寫入完成之後釋放行鎖即可。這種行鎖機制是實作寫寫并發控制最常用的手段,後面可以看到MySQL也是使用行鎖來實作寫寫并發的。

3. 如何實作批量寫入多行的寫寫并發?

HBase支援批量寫入(或批量更新),即一個線程同時更新同一個Region中的多行記錄。那如何保證目前事務中的批量寫入與其他事務中的批量寫入的并發控制呢?思路還是一樣的,使用行鎖。但這裡需要注意的是必須使用兩階段鎖協定,即:

(1) 擷取所有待寫入(更新)行記錄的行鎖

(2) 開始執行寫入(更新)操作

(3) 寫入完成之後再統一釋放所有行記錄的行鎖

不能更新一行鎖定(釋放)一行,多個事務之間容易形成死鎖。兩階段鎖協定就是為了避免死鎖,MySQL事務寫寫并發控制同樣使用兩階段鎖協定。

4. 筆者疑惑

其實筆者一直在這裡有個疑惑,還是使用上面例子說明吧。大家都知道,HBase是支援多版本的,那麼在資料寫入的時候加鎖給待寫入資料都打上版本資訊,此時釋放鎖。即使之後出現交叉的現象,對于讀請求來說,最終讀到的資料都是根據最大版本号來的。即使該表隻設定了一個版本号,那也可以根據版本号将之前版本的資料清理掉,和交叉不交叉并沒有太大關系。不知道大家怎麼了解這個問題?有興趣的可以留言評論!

1. 為什麼需要讀寫并發控制?

現在我們通過在寫入更新之前加鎖、寫入更新之後釋放鎖實作寫寫并發控制,那讀寫之間是不是也需要一定的并發控制呢?如果不加并發控制,會出現什麼現象呢?接着看下圖:

資料庫事務系列-HBase行級事務模型

上圖分别是兩個事務更新同一行資料,現在假設第一個事務已經更新完成,在第二個事務更新到一半的時候進來一個讀請求,如果沒有任何并發控制的話,讀請求就會讀到不一緻的資料,Company列為Restaurant,Role列為Engineer,如下圖所示:

資料庫事務系列-HBase行級事務模型

可見,讀寫之間也需要一種并發控制來保證讀取的資料總能夠保持一緻性,不會出現各種詭異的不一緻現象。

2. 如何實作讀寫并發控制?

實作讀寫并發最簡單的方法就是仿照寫寫并發控制 – 加鎖。但幾乎所有資料庫都不會這麼做,性能太差,對于讀多寫少的應用來說必然不可接受。那還有其他方法嗎?

當然,這就是今天要重點提到的MVCC機制 – Mutil Version Concurrent Control。HBase中MVCC機制實作主要分為兩步:

(1) 為每一個寫(更新)事務配置設定一個Region級别自增的序列号

(2) 為每一個讀請求配置設定一個已完成的最大寫事務序列号

示意圖如下所示:

資料庫事務系列-HBase行級事務模型

上圖中兩個寫事務分别配置設定了序列号1和序列号2,讀請求進來的時候事務1已經完成,事務2還未完成,是以配置設定事務1對應的序列号1給讀請求。此時序列号1對本次讀可見,序列号2對本次讀不可見,讀到的資料是:

資料庫事務系列-HBase行級事務模型

具體實作中,所有的事務都會生成一個Region級别的自增序列,并添加到隊列中,如下圖最左側隊列,其中最底端為已經送出的事務,隊列中的事務為未送出事務。現假設目前事務編号為15,并且寫入完成(中間隊列紅色框框),但之前的寫入事務還未完成(序列号為12、13、14的事務還未完成),此時目前事務必須等待,而且對讀并不可見,直至之前所有事務完成之後才會對讀可見(即讀請求才能讀取到該事務寫入的資料)。如最右側圖,15号事務之前的所有事務都成功完成,此時Read Point就會移動到15号事務處,表示15号事務之前的所有改動都可見。

資料庫事務系列-HBase行級事務模型

 Note:上文所講的自增序列就是上一篇文章的SequenceId!!!可能有朋友有疑問:如果這兩個自增序列是同一個序列,那是不是這個隊列的順序必須與事務寫入WAL的順序一緻?如果不一緻有什麼問題?如果要求一緻的話怎麼才能實作?

是以,MVCC的精髓是寫入的時候配置設定遞增版本資訊(SequenceId),讀取的時候配置設定唯一的版本用于讀取可見,比之大的版本不可見。這裡需要注意版本必須遞增,而且版本遞增的範圍一定程度上決定了事務是什麼事務,比如HBase是Region級别的遞增版本,那麼事務就是region級别事務。MySQL中版本是單機遞增版本,那麼MySQL事務就支援單機跨行事務。Percolator中版本是叢集遞增版本,那麼Percolator事務就是分布式事務。

HBase事務持久化可以了解為WAL持久化,目前實作了多種持久化政策:SKIP_WAL,ASYNC_WAL,SYNC_WAL,FSYNC_WAL。SKIP_WAL表示不寫WAL,這樣寫入更新性能最好,但在RegionServer當機的時候有可能會丢失部分資料;ASYNC_WAL表示異步将WAL持久化到硬碟,因為是異步操作是以在異常的情況下也有可能丢失少量資料;SYNC_WAL表示同步将WAL持久化到作業系統緩存,再由作業系統将資料異步持久化到磁盤,這種場景下RS宕掉并不會丢失資料,當作業系統宕掉會導緻部分資料丢失;FSYNC_WAL表示WAL寫入之後立馬落盤,性能相對最差。目前實作中FSYNC_WAL并沒有實作!使用者可以根據業務對資料丢失的敏感性在用戶端配置相應的持久化政策。

本文轉載自:http://hbasefly.com

<a href="http://hbasefly.com/2017/07/26/transaction-2/" target="_blank">原文連結</a>