淺談數(shù)據(jù)一致性
淺談數(shù)據(jù)一致性
|0x00 數(shù)據(jù)不一致產(chǎn)生的原因
互聯(lián)網(wǎng)的工程開發(fā),與傳統(tǒng)軟件相比,往往要面臨非常復(fù)雜多變的業(yè)務(wù)場(chǎng)景,這是老生常談的問題了。雖然在工程開發(fā)與協(xié)同領(lǐng)域已經(jīng)有了比較多的實(shí)踐案例,但對(duì)于比較底層的一些技術(shù)框架的協(xié)同,由于選型的原因,往往是比較多元化的,這也就導(dǎo)致了一些基礎(chǔ)框架之間的協(xié)同會(huì)出現(xiàn)一些問題。
舉個(gè)例子,在搜索領(lǐng)域,往往會(huì)采用ES這一類的全文檢索引擎進(jìn)行加速,但由于搜索往往還要帶有廣告、推薦等信息,很多時(shí)候還需要讀取具備ACID的RDMS數(shù)據(jù)庫,或者是一些NoSQL數(shù)據(jù)庫,多種數(shù)據(jù)庫組合在一起,才能滿足業(yè)務(wù)上的需求。
這么多異構(gòu)的數(shù)據(jù)源組合在一起,雖然能夠?qū)⑾到y(tǒng)做的更大和更靈活,但也會(huì)帶來很多問題,例如:
工程上的實(shí)現(xiàn)更加繁瑣,沒有辦法將所有數(shù)據(jù)庫的操作封裝到統(tǒng)一的DAL層; 在一些可回滾的業(yè)務(wù)場(chǎng)景里,數(shù)據(jù)要在多個(gè)數(shù)據(jù)庫之間同步的進(jìn)行ACID操作。
例如廣告業(yè)務(wù)場(chǎng)景里,有一個(gè)業(yè)務(wù)場(chǎng)景是只計(jì)費(fèi)一次,流程有如下的步驟:
數(shù)據(jù)寫入Mysql;
數(shù)據(jù)寫入ES;
數(shù)據(jù)寫入Redis。
步驟1是為了將數(shù)據(jù)傳遞給財(cái)務(wù)系統(tǒng),步驟2是為了重新調(diào)整檢索順序,步驟3是為了一些事實(shí)的推薦場(chǎng)景應(yīng)用。如果第1步就失敗了,那么整個(gè)順序就不需要執(zhí)行,但如果第1步成功而第2步失敗,那么雖然計(jì)費(fèi)成功了,但是在檢索的時(shí)候就會(huì)出現(xiàn)問題,導(dǎo)致出現(xiàn)第二次的計(jì)費(fèi)。
如果業(yè)務(wù)對(duì)于一致性的要求不高,那么在工程側(cè)是可以不考慮一致性問題的,把現(xiàn)場(chǎng)日志記錄完整,通過后續(xù)的補(bǔ)救操作,比如對(duì)第二次計(jì)費(fèi)進(jìn)行退費(fèi)操作,依然可以解決問題。但如果業(yè)務(wù)場(chǎng)景是要求強(qiáng)一致性,顯然工程上可能就需要考慮犧牲部分性能,以滿足一致性的要求了。
|0x01 本地事務(wù)和分布式事務(wù)
在展開后續(xù)的敘述前,我們先普及一下本地事務(wù)和分布式事務(wù)的一些特點(diǎn)。
傳統(tǒng)軟件行業(yè)多使用關(guān)系型數(shù)據(jù)庫,如Mysql、PostgreSQL等。好處是通過ACID的事務(wù)特性,可以在數(shù)據(jù)庫層面保證數(shù)據(jù)的強(qiáng)一致性,ACID分別指:
原子性(Atomicity):一個(gè)事務(wù)要么全部提交成功,要么全部失敗回滾,不能只執(zhí)行其中的一部分操作; 一致性(Consistency):事務(wù)的執(zhí)行不能破壞數(shù)據(jù)庫數(shù)據(jù)的完整性和一致性; 隔離性(Isolation):事務(wù)的隔離性是指在并發(fā)環(huán)境中,并發(fā)的事務(wù)是相互隔離的,一個(gè)事務(wù)的執(zhí)行不能不被其他事務(wù)干擾; 持久性(Durability):一旦事務(wù)提交,那么它對(duì)數(shù)據(jù)庫中的對(duì)應(yīng)數(shù)據(jù)的狀態(tài)的變更就會(huì)永久保存到數(shù)據(jù)庫中。
雖然ACID確實(shí)能夠保證強(qiáng)一致性,但隨著業(yè)務(wù)系統(tǒng)的越來越復(fù)雜,絕大多數(shù)場(chǎng)景里,對(duì)于速度的要求是壓過了對(duì)于一致性的要求,這個(gè)時(shí)候?yàn)榱四軌蚪鉀Q業(yè)務(wù)快速跑起來的問題,我們就會(huì)考慮犧牲一部分的性能,來滿足業(yè)務(wù)的能力的問題。這時(shí)候CAP理論就應(yīng)運(yùn)而生了:
一致性(Consistency):在分布式系統(tǒng)中,更新操作執(zhí)行成功后所有的用戶都應(yīng)該讀取到最新值; 可用性(Availability):每一個(gè)操作總是能夠在一定時(shí)間內(nèi)返回結(jié)果; 分區(qū)容忍性(Partition Tolerance):是否可以對(duì)數(shù)據(jù)進(jìn)行分區(qū)。
在分布式系統(tǒng)下,為了保證分區(qū)容忍性,就必須要在一致性與可用性之間做出選擇,這時(shí)候“魚與熊掌不可兼得”。為了能部分程度上彌補(bǔ)這個(gè)問題,我們又提出了BASE理論:
基本可用(Basically Available):假設(shè)系統(tǒng),出現(xiàn)了不可預(yù)知的故障,但還是能用; 軟狀態(tài)(Soft state):允許系統(tǒng)中的數(shù)據(jù)存在中間狀態(tài),并認(rèn)為該狀態(tài)不影響系統(tǒng)的整體可用性; 最終一致性(Eventually Consistent):系統(tǒng)能夠保證在沒有其他新的更新操作的情況下,數(shù)據(jù)最終一定能夠達(dá)到一致的狀態(tài),因此所有客戶端對(duì)系統(tǒng)的數(shù)據(jù)訪問最終都能夠獲取到最新的值。
BASE與ACID理論不同的是,它是滿足CAP理論的,即通過“時(shí)間換空間”的思路,通過犧牲強(qiáng)一致性的方式,在處理系統(tǒng)請(qǐng)求的過程里,允許存在短時(shí)間的不一致狀態(tài),延遲保證數(shù)據(jù)的一致性。
所以,這里我們可以給“最終一致性”下一個(gè)定義,即:系統(tǒng)中的所有數(shù)據(jù)副本經(jīng)過一定時(shí)間后,最終能夠達(dá)成一致的狀態(tài)。
|0x02 解決數(shù)據(jù)一致性的模式
通過上一階段理論演進(jìn)的闡述,可以看出,互聯(lián)網(wǎng)工程領(lǐng)域往往通過“最終一致性”的方式,來保障數(shù)據(jù)的一致性。因此接下來提到的解決思路,都是圍繞“最終一致性”展開的。接下來主要介紹三種方式:
第一種是“可靠消息”,即通過保障消息傳遞的方式,來保障下游數(shù)據(jù)的一致性,這種方式本質(zhì)上屬于事件驅(qū)動(dòng)的方案設(shè)計(jì)。例如在電商領(lǐng)域用戶下單后,后續(xù)會(huì)發(fā)送消息給各個(gè)子系統(tǒng):銀行、倉儲(chǔ)、物流等,各個(gè)子系統(tǒng)根據(jù)消息的結(jié)果來做下一步的業(yè)務(wù)邏輯。
這種方案主要考慮的問題是:如何確保消息能夠傳達(dá),以及如何避免重復(fù)消息的傳遞,用更專業(yè)的語言來描述,就是“冪等”。
其實(shí)如果感覺到自己設(shè)計(jì)系統(tǒng)太過于復(fù)雜的時(shí)候,可以借鑒一些開源系統(tǒng)的實(shí)現(xiàn)方案,比如Kafka就支持“冪等性”。Kafka的思路是這樣的:設(shè)計(jì)唯一的ProducerID及一個(gè)從0開始單調(diào)遞增的SeqNum值,下游通過判斷SeqNum是否大于1來判斷是否接受消息。或者參考一些流式計(jì)算引擎,比如Flink和Storm,都有實(shí)現(xiàn)exactly-once的方法。
第二種是“TCC兩階段補(bǔ)償”,TCC是Try-Confirm-Cancel的簡(jiǎn)稱,這是當(dāng)下比較火的一種柔性事務(wù)方案。TCC的概念最早由Pat Helland于2007年發(fā)表的一篇名為《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。
TCC主要分為如下三個(gè)階段:
Try階段:完成所有業(yè)務(wù)檢查(一致性),預(yù)留業(yè)務(wù)資源(準(zhǔn)隔離性); Confirm階段:確認(rèn)執(zhí)行業(yè)務(wù)操作,不做任何業(yè)務(wù)檢查,只使用Try階段預(yù)留的業(yè)務(wù)資源; Cancel階段:取消Try階段預(yù)留的業(yè)務(wù)資源。
以航班的預(yù)定為例,很多時(shí)候因?yàn)閮r(jià)格問題,我們不會(huì)直接飛到目的地,而是通過中轉(zhuǎn)的方式抵達(dá),于是我們會(huì)預(yù)定兩張機(jī)票。但問題來了,這兩張機(jī)票不一定都會(huì)順利使用,如果遇到天氣、管制、機(jī)票預(yù)留等問題,其中一張取消了,那么整個(gè)行程就不會(huì)順利完成。這個(gè)時(shí)候,我們把機(jī)票的預(yù)定修改為三個(gè)接口:機(jī)票預(yù)留接口、確認(rèn)接口、取消接口,分兩次進(jìn)行操作,如果兩段行程任意一段機(jī)票預(yù)留失敗,那么調(diào)用兩段行程的取消接口,反之調(diào)用確認(rèn)接口。
這個(gè)概念與MR的兩階段計(jì)算思路比較類似,即通過一種折中的方案,來實(shí)現(xiàn)最終一致性。
第三種是“逆向接口補(bǔ)償”,使用額外的協(xié)調(diào)服務(wù)來保證微服務(wù)之間的最終一致性。微服務(wù)通常采用接口進(jìn)行調(diào)用,在常規(guī)的提供正向業(yè)務(wù)邏輯的基礎(chǔ)上,再要求每個(gè)接口提供一個(gè)逆向業(yè)務(wù)邏輯的方案。如果在順序調(diào)用接口的過程中,某個(gè)服務(wù)出現(xiàn)了錯(cuò)誤,那么再重復(fù)調(diào)用之前已成功的微服務(wù)接口的逆向接口,取消本次事務(wù)的操作。
這種場(chǎng)景在優(yōu)惠領(lǐng)域比較常見,比如用戶通過優(yōu)惠券買了一件商品,但商品庫存沒了,需要退貨,那么理論上優(yōu)惠券是需要返還給用戶的,這時(shí)候正向接口就是消耗優(yōu)惠券,而逆向接口就是返還優(yōu)惠券。
但各個(gè)接口之間的調(diào)用不一定會(huì)100%成功,所以補(bǔ)償方案也需要一個(gè)最終一致性解決方法,即針對(duì)單次原子的逆向操作,至少保證被調(diào)用一次。這時(shí)候最理想的方案就是系統(tǒng)記錄Log,通過事后分析再判斷進(jìn)行一次調(diào)用。
|0xFF 從全局角度再思考
不論是從數(shù)據(jù)庫層面,還是從工程層面,或者是人工兜底層面,數(shù)據(jù)一致性總有解決的方法,區(qū)別只是場(chǎng)景適用性與成本高低的問題。
隨著技術(shù)發(fā)展的越來越快,解決方案手段的不斷增加,技術(shù)架構(gòu)解耦就是一種必然的要求,在不同的場(chǎng)景下選用自己最適合的方案,但由此帶來的數(shù)據(jù)一致性問題也將成為技術(shù)融合道路上的一個(gè)阻礙。可以預(yù)見,未來的技術(shù)生態(tài),對(duì)于技術(shù)點(diǎn)的組合編排創(chuàng)新必然成為主旋律。就像Hadoop的出現(xiàn)是為了解決集群一致性的問題,數(shù)據(jù)驅(qū)動(dòng)的方法論也終將像框架一樣,成為下一代的創(chuàng)新點(diǎn)。
