深入理解分佈式事務Percolator(一)

轉載請附本文鏈接:https://blog.csdn.net/maxlovezyy/article/details/88572692

概念篇

本篇是在之前一篇介紹完分佈式事務的概念之後,再通過Percolator從新手視角去討論事務和分佈式事務,下一篇會對其設計細節做深度的分析和思考。

什麼是分佈式事務

假如你是一個先了解過分佈式一致性,後接觸分佈式事務的人,那估計很容易混淆這兩個概念。因爲本質上都是爲了宏觀數據上的一致性,那麼既然有了分佈式一致性協議,比如paxos/raft,爲什麼還要搞分佈式事務呢?一般來講,一致性協議保證的是某一數據實體的多副本之間的一致性;事務保證的是不同數據實體之間關係的一致性。下面舉各自可能面對的場景的例子來說明一下:

  • 一致性協議
    有一個邏輯數據單元,有N份數據副本,分佈在不同的存儲硬件上。我現在要對這個邏輯數據單元作更改,這個更改要廣播到N個副本上,讓它們之間達到某種意義的一直狀態。比如原來的內容是1,我現在要改成2,那麼這N個副本就要在滿足協議要求的情況下做出相同改變。比如majority的raft就要求N/2 + 1個副本都變成2就等於達到了一致。

  • 分佈式事務:事務是什麼自己去了解吧
    有M個邏輯單元互相通過關係R組成一個對外邏輯單元。假如拿銀行來做舉例,簡單來說有甲和乙,銀行賬戶裏各有10塊錢。甲轉給乙5元:對外結果只能是甲5乙15或者甲10乙10。而不能是甲5乙10,因爲這樣整體來看就丟了5元。實際上銀行有千千萬萬的賬戶,這些信息存儲在不同的機器上,我們分佈式事務要保證在分佈式的情況下,各個邏輯單元之間在關係R的情況下的一致性。

顯而易見的問題

前一節簡單介紹了分佈式事務要解決的問題。那麼隨之問題就來了,怎麼做才能實現事務?ACID其他的都好說,就這個A即原子性怎麼在分佈式的情況下實現?假設單機呢?很現實的一個問題就是,我現在涉及了多行數據,他們怎麼能做到一起成功或者一起失敗。不宕機的情況下都好說,如果考慮宕機導致的中斷呢?勢必就要引入一個共通的地方去記錄本次事務的進度等相關信息吧?一般在2階段提交裏這叫做協調者,那如果換成分佈式,協調者要是掛了呢?

Percolator的解決方案

其核心就是2PC的增強版,通過利用client作爲協調者,解決了協調者掛了對整體服務能力的影響,而在事務相關信息的一致性和持久性上充分利用了BigTable的簡單事務支持以及GFS的多副本可靠性能力,另外Percolator在數據模型上是mvcc。

怎麼保證的原子性

想保證多條指令作爲整體執行的原子性,有單機併發編程基礎的都應該知道,那就是通過加鎖(原子操作不適合),要麼你基於內存的mutex,要麼基於文件系統的file lock,甚至於可以使用分佈式鎖服務。。。Percolator的做法是對數據擴展出一個lock列,對於多行數據,它隨機選擇一行作爲主,通過BigTable的事務對這個primary主row的lock列寫入信息表明持有鎖,而其他的rows則隨後在其lock列寫入primary鎖的位置信息。這樣就完成了兩件事:一個是把事務中的所有rows關聯起來了;一個是互斥點唯一了,都在primary,如同加了分佈式鎖一樣。另外還需要考慮一個問題,Percolator爲什麼不怕lock信息丟失呢?因爲BigTable底層是GFS,是多副本,假定其能保證對外承諾的SLA下的可靠性,真發生了丟數據的問題那就人工處理唄。。。

具體實現

數據模型

宏觀上每一行有4列,分別是key、data、lock和write,另外每一個cell的數據都有個版本號timestamp,這是mvcc必須的。每一列的作用如下:

key:唯一表示
data:數據
lock:鎖標記
write:當前最新的key對應的data的版本號

流程

每一個事務開始的時候都會去授時服務獲取一個start_ts。

定義一個輔助函數:某行數據的prewrite

  1. 檢測write列和lock列與本次事務是否衝突,如果衝突則直接取消本次事務
  2. 以start_ts對該行數據的lock列寫入信息並寫入data

寫主要分兩個階段,一個是客戶端向Percolator SDK的事務對象寫入mutations的階段(Percolator SDK會緩存所有的mutations),一個是客戶端本身的commit階段(即調用Percolator SDK的事務對象的commit)。Percolator SDK中事務對象的commit分爲兩個階段,即分佈式事務的2PC

  1. 選出primary,對primary執行prewrite,失敗返回
  2. 對所有其他rows執行prewrite,失敗返回
  3. 獲取一個commit_ts,轉到步驟4
  4. 檢查是不是有自己時間戳加的鎖,如果有對primary解鎖並在write列寫入版本號commit_ts的事務完成標示,內容指向start_ts的data,之後轉到步驟5;如果沒有自己start_ts加的鎖,事務失敗。
  5. 異步對其他rows執行4的處理

讀本身很簡單,這裏就簡單說了。先獲取當前的時間戳start_ts,如果時間戳之內的數據有鎖,則等,等到一定程度還不行就清理掉鎖,執行讀操作。這裏不可以直接返回當前小於start_ts的最新版本數據,會造成幻讀。因爲如果當前讀不等鎖,再次讀的話可能鎖釋放數據提交了,假如提交的時間戳小於讀事務的開始時間戳,就會讀到比之前讀到的版本數據大但是依然小於start_ts的新數據,這樣就產生了不可重複讀或者幻讀。

FAQ

  1. 爲什麼寫提交的時候primary成功了其他的row異步就可以?
    因爲其他row的lock列都指向了primary,primary決議完怎麼做之後,其他rows就都知道該怎麼處理了。當其他rows面臨訪問的時候,如果當前有lock,則去看看primary怎麼處理的進而對自己執行abort或commit(訪問primary的lock和write列就知道該怎麼做了)

  2. 爲什麼client作爲協調者掛了無所謂?
    client掛了其事務也無非是完成/未完成的狀態,無論哪種狀態都不會導致數據不一致,仔細想想Percolator的lock和commit機制就都明白了。

  3. 爲什麼讀的時候如果等到一定程度還等不到無鎖就可以主動清理鎖以繼續?
    這和2其實是一個問題,只不過回答的時候額外多了一個考慮,那就是線性一致性的考慮。爲什麼這裏不會影響到用戶視角的數據的線性一致性呢?因爲計算機的世界但凡涉及網絡的請求都是3中情況:成功、失敗和不知道。讀等不及了要主動清理鎖無非是下面2種情況:
    a. 寫的協調者掛了。那對於發起寫的用戶結果就是不可預知,他需要後續自己發請求確認到底執行的如何。
    b. 寫的協調者hang了。清理鎖之後如果寫協調者又活過來了,其執行commit的時候也必然失敗,因爲它持有的版本號的lock已經沒了,只能取消,不會影響一致性。

  4. Percolator會有write skew問題嗎?
    會。

參考

這裏沒有談Percolator裏面的通知機制,沒看。關於事務其實percolator的paper寫的已經非常詳細了,細節直接看就懂了。

Percolator paper

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章