聊聊分布式數(shù)據(jù)庫對2PC的優(yōu)化
本文轉(zhuǎn)載自微信公眾號「jinjunzhu」,作者jinjunzhu。轉(zhuǎn)載本文請聯(lián)系jinjunzhu公眾號。
在單體數(shù)據(jù)庫時代,數(shù)據(jù)庫本身就支持ACID事務(wù),開發(fā)人員甚至只要在方法上加一個@Transactional注解就可以搞定事務(wù)了,非常簡單。但是到了分庫分表和分布式數(shù)據(jù)庫時代,傳統(tǒng)數(shù)據(jù)庫的ACID屬性只能在單節(jié)點上起作用,全局事務(wù)需要一個全局的事務(wù)管理器來維護(hù),復(fù)雜性很高。
而在分布式事務(wù)領(lǐng)域,全局事務(wù)使用的最多的指導(dǎo)方案就是2PC,也叫兩階段提交,但是2PC也有一些缺陷,今天我們就來看看分布式數(shù)據(jù)庫是怎么對這些缺陷做優(yōu)化的。
兩階段提交(2PC)
兩階段提交協(xié)議主要有2種,一種是應(yīng)用層的TCC,比如阿里巴巴的seata就實現(xiàn)了TCC模式,這種模式的特點是每個服務(wù)都需要提供try/confirm/cancel這3個實現(xiàn),這3個實現(xiàn)需要在業(yè)務(wù)代碼中實現(xiàn),對業(yè)務(wù)侵入高。
今天我分享的是面向資源的2PC協(xié)議,最早由Jim Gray提出,整個事務(wù)分為2個階段,prepare階段和commit階段,這2個階段由協(xié)調(diào)節(jié)點和DB資源管理器協(xié)作完成。
這里我們還是以經(jīng)典的電商系統(tǒng)為例,整個系統(tǒng)分為訂單、賬戶和庫存3個服務(wù),我們收到客戶的購買請求后,協(xié)調(diào)節(jié)點需要協(xié)調(diào)訂單服務(wù)生成訂單,賬戶服務(wù)扣減商品款,庫存服務(wù)扣減商品庫存,假如這3個服務(wù)的數(shù)據(jù)庫在不同切片上,這個協(xié)調(diào)過程具體如下:
1.prepare階段
協(xié)調(diào)節(jié)點向所有服務(wù)發(fā)送prepare請求,每個服務(wù)收到prepare請求后會嘗試執(zhí)行本地事務(wù),但不會真正提交本地事務(wù)。這個嘗試執(zhí)行的過程會檢查到是否具備執(zhí)行事務(wù)的條件,比如資源是否被鎖定等,當(dāng)所有服務(wù)都嘗試執(zhí)行成功后會給協(xié)調(diào)節(jié)點返回一個yes,如下圖:
2.commit/rollback階段
如果prepare階段所有服務(wù)有返回了yes,那么協(xié)調(diào)節(jié)點就會通知各個服務(wù)執(zhí)行commit操作,這時各個服務(wù)就會真正的提交本地事務(wù)。如下圖:
如果prepare階段有服務(wù)返回了no,協(xié)調(diào)節(jié)點就需要通知所有服務(wù)進(jìn)行本地事務(wù)回滾。
2PC存在問題
上面我們簡單地分析了2PC協(xié)議的執(zhí)行過程,那么2PC有什么問題呢?
1.性能問題
本地事務(wù)在prepare階段鎖定資源,比如賬戶服務(wù)要扣減xiaoming這個賬戶的金額100元,那必須把xiaoming這個賬戶先鎖定。這樣如果有其他事務(wù)也要修改xiaoming這個賬戶,就必須等待前面的事務(wù)完成。這樣就造成了延遲和性能下降。
2.協(xié)調(diào)節(jié)點單點故障
協(xié)調(diào)節(jié)點是單節(jié)點的,如果發(fā)生故障,整個事務(wù)會一直阻塞。比如第一個階段prepare成功了,但是第二個階段協(xié)調(diào)節(jié)點發(fā)出commit指令之前宕機了,所有服務(wù)的數(shù)據(jù)資源處于鎖定狀態(tài),后面的事務(wù)只能等待。
3.數(shù)據(jù)不一致
如果第一階段prepare成功了,但是第二階段commit的時候,如果協(xié)調(diào)節(jié)點通知庫存服務(wù)失敗了,這樣就相當(dāng)于生成了訂單,扣減了賬戶,但是沒有扣減庫存。這導(dǎo)致了數(shù)據(jù)的不一致。
Percolator模型
主流的NewSQL數(shù)據(jù)庫,比如TiDB,是用Percolator模型來解決的。如下官網(wǎng)鏈接:
- https://pingcap.com/blog-cn/percolator-and-txn/
Percolator模型來自于Google論文:
- 《Large-scale Incremental Processing Using Distributed Transactions and Notifications》
原文可以看下面連接,網(wǎng)上也有好多翻譯版的:
- https://www.cs.princeton.edu/courses/archive/fall10/cos597B/papers/percolator-osdi10.pdf
Percolator的前提是本地事務(wù)的數(shù)據(jù)庫支持多版本并發(fā)控制協(xié)議,也就是mvcc。現(xiàn)在主流數(shù)據(jù)庫比如mysql、oracle都是支持的。
a)初始階段
還是看上面我們提到的經(jīng)典電商案例,初始階段,我們假設(shè)訂單數(shù)量是0,賬戶服務(wù)是1000,庫存服務(wù)是100,客戶下了1個訂單后,訂單服務(wù)增加1個訂單,賬戶服務(wù)扣除金額100,庫存服務(wù)扣除商品數(shù)量1。各個切片的初始數(shù)據(jù)如下表:
":"前面的是時間戳或者數(shù)據(jù)版本,后面是數(shù)據(jù)值。這3張表中,第一條記錄不保存真正的數(shù)據(jù),而是保存了指向真正數(shù)據(jù)的指針,比如訂單表中,6這個版本的數(shù)據(jù)指向了5個版本的數(shù)據(jù),訂單數(shù)量是0。
b)prepare階段
在prepare階段,協(xié)調(diào)節(jié)點向每個服務(wù)發(fā)送了prepare命令,這3張表分別進(jìn)入了prepare階段。在prepare階段,Percolator定義了主鎖的概念,每個分布式事務(wù)只能有一個服務(wù)獲得主鎖,比如本案例的訂單服務(wù),其他服務(wù)的鎖指向這個主鎖的指針,如下表:
prepare階段,每個服務(wù)會寫日志,并且根據(jù)時間戳記錄事務(wù)的私有版本,這樣其他事務(wù)就不能操作這三條數(shù)據(jù)了。
c)commit階段
在commit階段,協(xié)調(diào)節(jié)點只需要跟訂單服務(wù)通信,因為訂單服務(wù)擁有primary lock,也就是說協(xié)調(diào)節(jié)點只跟擁有primary lock的切片通信。這時數(shù)據(jù)如下表:
這時我們注意到除了order服務(wù)的鎖沒有了,而且增加了版本8指向版本7,說明訂單服務(wù)已經(jīng)沒有私有版本了,但是賬戶服務(wù)和庫存服務(wù)的私有版本還在。Percolator的獨特之處就是在這里,它會啟動異步線程來更新賬戶服務(wù)和庫存服務(wù)。最終數(shù)據(jù)如下表:
因為協(xié)調(diào)節(jié)點只需要跟獲取primary lock的切片進(jìn)行通信,要么成功要么失敗這樣就避免了commit時節(jié)點不能全部成功導(dǎo)致的數(shù)據(jù)不一致問題。
而prepare階段記錄了日志,如果某個切片commit失敗,可以根據(jù)日志進(jìn)行再次commit,這樣就保證了數(shù)據(jù)最終一致。
如果協(xié)調(diào)節(jié)點宕機了,異步線程可以做資源的釋放工作,避免了因單點故障通信失敗造成的資源不能釋放。
這里我們要注意2點:
- primary lock的選擇是隨機的,比如本例中并不一定會選擇訂單服務(wù)
- 協(xié)調(diào)節(jié)點發(fā)送commit后訂單服務(wù)先提交成功,這時如果其他事務(wù)要讀取賬戶服務(wù)和庫存服務(wù)的2條數(shù)據(jù),雖然2條數(shù)據(jù)上面還有l(wèi)ock,但是查找primary@order.bal發(fā)現(xiàn)已提交,所以是可以讀取的。
總結(jié)
2PC協(xié)議有3個問題,性能問題、單點故障和數(shù)據(jù)不一致。
Percolator模型簡化了協(xié)調(diào)節(jié)點和切片的通信流程,讓協(xié)調(diào)節(jié)點只跟其中一個primary切片通信,一方面,減少了通信開銷,另一方面,避免了因為單點故障,commit階段部分節(jié)點通信失敗導(dǎo)致的數(shù)據(jù)不一致問題。
Percolator在prepare階段記錄了日志,這樣即使協(xié)調(diào)節(jié)點故障了,恢復(fù)后也可以根據(jù)日志來做事務(wù)恢復(fù)。
Percolator使用異步線程來做資源的釋放工作,這樣即使協(xié)調(diào)節(jié)點故障了,也不用擔(dān)心資源得不到釋放。
知名的NewSQL數(shù)據(jù)庫TiDB就是參照Percolator模型來對2PC協(xié)議進(jìn)行優(yōu)化的。
但是我們要知道,2PC的性能問題還是存在的,好在主流的分布式數(shù)據(jù)庫都做了優(yōu)化,性能損耗只會越來越小。