在計(jì)算機(jī)系統(tǒng)的領(lǐng)域,一致性可以說是一個(gè)高頻詞,可能出現(xiàn)的場(chǎng)景很多。從分布式系統(tǒng)到數(shù)據(jù)庫的事務(wù),都有它的身影。
之前我們?cè)诮榻B數(shù)據(jù)庫事務(wù)的時(shí)候,談到過事務(wù)的一致性。在數(shù)據(jù)庫當(dāng)中,一致性是一種目的,不是一種手段。數(shù)據(jù)庫希望控制事務(wù)的原子性、隔離性和持久性來保證數(shù)據(jù)的一致性。這里的一致性更多的指的是實(shí)際和我們觀念的一致。也就是說結(jié)果都在我們預(yù)期之內(nèi)。而在分布式系統(tǒng)當(dāng)中,一致性有另外的含義,一個(gè)是多份數(shù)據(jù)副本之間的一致性問題,另一個(gè)是多階段提交的一致性問題。我們今天先來聊聊副本一致性問題。
這個(gè)問題出現(xiàn)的原因很簡(jiǎn)單,因?yàn)榉植际较到y(tǒng)當(dāng)中,數(shù)據(jù)往往會(huì)有多個(gè)副本。如果是一臺(tái)數(shù)據(jù)庫處理所有的數(shù)據(jù)請(qǐng)求,那么通過ACID四原則,基本可以保證數(shù)據(jù)的一致性。而多個(gè)副本就需要保證數(shù)據(jù)會(huì)有多份拷貝。這就帶來了同步的問題,因?yàn)槲覀儙缀鯖]有辦法保證可以同時(shí)更新所有機(jī)器當(dāng)中的包括備份所有數(shù)據(jù)。尤其是當(dāng)這些機(jī)器分布在全國各地甚至是世界各地的時(shí)候,由于網(wǎng)絡(luò)延遲,即使我在同一時(shí)間給所有機(jī)器發(fā)送了更新數(shù)據(jù)的請(qǐng)求,也不能保證這些請(qǐng)求被響應(yīng)的時(shí)間保持一致。只要存在時(shí)間差,就會(huì)存在某些機(jī)器之間的數(shù)據(jù)不一致的情況。也就是說,在分布式系統(tǒng)當(dāng)中的一致性,指的是數(shù)據(jù)一致性。
這其實(shí)是一個(gè)兩難問題,為了解決流量過大的壓力問題,我們?cè)O(shè)計(jì)了分布式系統(tǒng)。但是分布式系統(tǒng)又會(huì)帶來數(shù)據(jù)多份拷貝不同步違反一致性的問題,我們既不能容忍數(shù)據(jù)出錯(cuò),也不能放棄分布式系統(tǒng),唯一的辦法就是采取一些措施,來最大可能地降低這個(gè)問題的影響力。
多種多樣的一致性模型,就是這些措施的體現(xiàn)。讓我們從最簡(jiǎn)單的嚴(yán)格一致性說起。
嚴(yán)格一致性
嚴(yán)格一致性是最理想的情況,如果我們每次請(qǐng)求一個(gè)數(shù)據(jù),不管什么情況下,我們都能獲得它的最后一次改動(dòng)的結(jié)果。很遺憾的是,嚴(yán)格一致性是不可能實(shí)現(xiàn)的。
不可能實(shí)現(xiàn)的原因很簡(jiǎn)單,因?yàn)?strong>多臺(tái)機(jī)器之間的數(shù)據(jù)同步需要時(shí)間,無論這個(gè)時(shí)間多小,它都是確定存在的。只要存在,就不可能實(shí)現(xiàn)嚴(yán)格一致性。舉個(gè)簡(jiǎn)單的例子,我們有A和B兩臺(tái)機(jī)器。在t時(shí)刻,A機(jī)器修改了某條數(shù)據(jù),在1毫秒之前,B機(jī)器收到了一條查詢?cè)摂?shù)據(jù)的請(qǐng)求。當(dāng)B執(zhí)行這個(gè)查詢的時(shí)候,A機(jī)器已經(jīng)修改完成,那么究竟B查詢到的值應(yīng)該是什么呢?是A修改之前的還是修改之后的呢?在A機(jī)器看來,B的查詢發(fā)生在它修改之后,可是B機(jī)器看來卻恰恰相反,A修改值發(fā)生了在它收到請(qǐng)求之后。如果我們要保證嚴(yán)格一致性,那么究竟什么結(jié)果才是對(duì)的呢?
當(dāng)然上面這個(gè)例子只是最極端的情況,一般只會(huì)在理論上發(fā)生,但是通過對(duì)極端情況的分析,我們也可以看得出來,嚴(yán)格一致性是不可能實(shí)現(xiàn)的。
強(qiáng)一致性與弱一致性
數(shù)據(jù)不一致出現(xiàn)的根本原因在于多臺(tái)機(jī)器更新數(shù)據(jù)的時(shí)間差,我們更新多臺(tái)機(jī)器,總有先有后,很難保證完全同步。根據(jù)同步數(shù)據(jù)時(shí)采用同步還是異步策略,又可以將一致性分為強(qiáng)一致性與弱一致性。
使用同步策略更新數(shù)據(jù)時(shí),我們每次請(qǐng)求發(fā)給主節(jié)點(diǎn),主節(jié)點(diǎn)收到數(shù)據(jù)之后使用同步更新的策略將數(shù)據(jù)發(fā)送給從節(jié)點(diǎn)。當(dāng)所有的從節(jié)點(diǎn)更新成功之后,主節(jié)點(diǎn)會(huì)更新數(shù)據(jù)的狀態(tài),使它生效,之后返回response給用戶,告知更新成功。
顯然,通過同步更新數(shù)據(jù)的策略下,一致性的保障更強(qiáng)。如果我們?cè)谥鳈C(jī)上做好隔離措施,比如在更新結(jié)束之前,用戶不能發(fā)起下一次更新,那么盡可能地保證數(shù)據(jù)一致性不出問題。但是這種做法也有很大的弊端,最大的弊端就在于使用同步更新的操作,而且要所有從庫都更新成功才能返回,這樣的時(shí)間開銷非常大。最關(guān)鍵的一個(gè)缺點(diǎn)在于,如果一個(gè)從庫宕機(jī),那么主庫就不可能更新所有的從庫,那么新來的請(qǐng)求永遠(yuǎn)不會(huì)更新,這顯然是不能接受的。
和強(qiáng)一致性對(duì)應(yīng)的是弱一致性,我們不采用同步策略來更新數(shù)據(jù),而采用異步更新的方式。好處也很明顯,同步改成了異步,時(shí)間消耗大大縮減。但是問題也很明顯,除了異步本身帶來的問題之外,由于多個(gè)副本之間的數(shù)據(jù)更新發(fā)生不同時(shí),如果連續(xù)多次訪問落到了不同的副本上,就會(huì)出現(xiàn)多次訪問的結(jié)果不一致的情況。
本質(zhì)上來說分布式系統(tǒng)的一致性模型,只有強(qiáng)弱兩種。只不過在這兩種基礎(chǔ)的模型上,針對(duì)許多可能出現(xiàn)的問題還會(huì)進(jìn)行相應(yīng)的優(yōu)化。總體上而言,分布式系統(tǒng)對(duì)于性能的要求要高于一致性,所以大多分布式系統(tǒng)的一致性模型,還是基于弱一致性設(shè)計(jì)的。下面就來列舉幾種,比較經(jīng)典的弱一致性模型的優(yōu)化方案。
讀寫一致性
讀寫一致性在日常中經(jīng)常遇見,比如在某論壇當(dāng)中,用戶回復(fù)了某個(gè)帖子。但是當(dāng)用戶刷新的時(shí)候,可能會(huì)出現(xiàn)這個(gè)回復(fù)消失的情況。用戶會(huì)陷入困擾,不知道這個(gè)回復(fù)究竟有沒有成功。
會(huì)發(fā)生這種情況的原因也很簡(jiǎn)單,因?yàn)橛脩羲⑿碌臅r(shí)候,訪問的從庫可能還沒有獲取到用戶回復(fù)的數(shù)據(jù),所以顯示的結(jié)果當(dāng)中自然就沒有用戶剛剛回復(fù)的內(nèi)容。在這種情況下,我們需要保證讀寫一致性。也就是說用戶讀取自己寫入結(jié)果的一致性,保證用戶永遠(yuǎn)能夠第一時(shí)間看到自己更新的內(nèi)容。比如我們發(fā)一條朋友圈,朋友圈的內(nèi)容是不是第一時(shí)間被朋友看見不重要,但是一定要顯示在自己的列表上。
那如何實(shí)現(xiàn)呢?
方案有好幾種,一種方案是對(duì)于一些特定的內(nèi)容我們每次都去主庫讀取。比如我們讀取帖子當(dāng)中回復(fù)信息的時(shí)候,永遠(yuǎn)都去主庫讀取。但是這樣的問題也很明顯,可能會(huì)導(dǎo)致主庫的壓力過大。另一種方案是我們?cè)O(shè)置一個(gè)更新時(shí)間窗口,在剛剛更新的一段時(shí)間內(nèi),我們默認(rèn)都從主庫讀取,過了這個(gè)窗口之后,我們會(huì)挑選最近有過更新的從庫進(jìn)行讀取。
還有一種更好的方案是我們直接記錄用戶更新的時(shí)間戳,在請(qǐng)求的時(shí)候把這個(gè)時(shí)間戳帶上,凡是最后更新時(shí)間小于這個(gè)時(shí)間戳的從庫都不予以響應(yīng)。也就是說只有包含用戶寫入這個(gè)更新的庫可以響應(yīng)這個(gè)請(qǐng)求,就可以保證實(shí)現(xiàn)用戶端的讀寫性一致了。
單調(diào)讀
單調(diào)讀解決的是最經(jīng)典的弱一致性的不一致問題,出現(xiàn)的場(chǎng)景也很簡(jiǎn)單。由于主從節(jié)點(diǎn)更新數(shù)據(jù)的時(shí)間不一致,導(dǎo)致用戶在不停地刷新的時(shí)候,有時(shí)候能刷出來,再次刷新之后會(huì)發(fā)現(xiàn)數(shù)據(jù)不見了,再刷新又可能再刷出來,就好像遇見靈異事件一樣。我記得以前微博或者人人就存在這個(gè)問題,單調(diào)讀就是針對(duì)的這個(gè)場(chǎng)景,可以保證不會(huì)出現(xiàn)這種情況。
解決的方法其實(shí)很簡(jiǎn)單,就是根據(jù)用戶ID計(jì)算一個(gè)hash值,再通過hash值映射到機(jī)器。同一個(gè)用戶不管怎么刷新,都只會(huì)被映射到同一臺(tái)機(jī)器上。這樣就保證了不會(huì)讀到其他從庫的內(nèi)容,帶來用戶體驗(yàn)不好的影響。當(dāng)然,這只是一種解決方案,其他的解決方法還有很多,這里不一一討論。
因果一致性
因果一致性針對(duì)的數(shù)據(jù)之間邏輯上的因果問題,舉個(gè)例子,比如說用戶在知乎里提問題和回答問題。想要回答問題,必須要保證有對(duì)應(yīng)的問題。也就是說一定是先有問題,再有的回答。可是問題和回答并不一定存儲(chǔ)在同一個(gè)節(jié)點(diǎn)上,很有可能出現(xiàn)問題存入節(jié)點(diǎn)A,回答存入節(jié)點(diǎn)B的情況。因?yàn)榇嬖谕窖舆t,所以就可能出現(xiàn)查詢的用戶只看得到回答,卻找不到對(duì)應(yīng)問題的情況,違反了事物之間的因果性。
為了解決這個(gè)問題,一種方案是在寫入的時(shí)候遵循某種邏輯順序,那么在讀取的時(shí)候,就可以保證不會(huì)出現(xiàn)因果錯(cuò)亂的情況。但問題是,很多因果性并不想問題和回答這么明顯,一些隱藏的因果性可能很難被輕易判斷,就需要引入更高深的技術(shù),感興趣的同學(xué)可以去搜索一下向量時(shí)鐘深入了解。
最終一致性
聽名字這種方案似乎很厲害,其實(shí)最終一致性是所有分布式一致性模型當(dāng)中最弱的。可以認(rèn)為是沒有任何優(yōu)化的“最”弱一致性,它的意思是說,我不考慮所有的中間狀態(tài)的影響,只保證當(dāng)沒有新的更新之后,經(jīng)過一段時(shí)間之后,最終系統(tǒng)內(nèi)所有副本的數(shù)據(jù)是正確的。
有大神打了這么一個(gè)比方,就好像你去點(diǎn)星巴克。你并不知道星巴克什么時(shí)候做好,你在做好之前去拿,拿到的結(jié)果是錯(cuò)的。但是你知道,經(jīng)過一段時(shí)間之后,你一定可以拿到你想要的。至于星巴克做好需要多久,往往沒有定論,可以是幾百分之一秒,也可以是幾個(gè)小時(shí)。
聽起來這個(gè)方案很不靠譜,在確定它可行之前,我們還想問幾個(gè)問題,首先,系統(tǒng)能不能保證一段時(shí)間是多久?如果天荒地老怎么辦?其次,因?yàn)樽罱K才能收斂,那么在收斂之前,多個(gè)副本之間的值可能都不同,究竟又該以哪個(gè)為準(zhǔn)?
好在,這兩個(gè)問題都能回答。對(duì)于第一個(gè)問題,答案是系統(tǒng)沒辦法確定究竟需要多久收斂,但是可以確定最大的收斂時(shí)間。有點(diǎn)像是物理學(xué)上的半衰期,我們不知道一個(gè)粒子究竟需要多久衰變,但是可以確定足夠多的粒子當(dāng)中的一半衰變所需要的時(shí)間。第二個(gè)問題更好回答,當(dāng)有多份數(shù)據(jù)出現(xiàn)的時(shí)候,通常的做法是選擇其中時(shí)間戳較大的,也就是說出現(xiàn)較晚的值。
雖然最終一致性看起來很不靠譜,但是它最大程度上保證了系統(tǒng)的并發(fā)能力,也因此,在高并發(fā)的場(chǎng)景下,它也是使用最廣的一致性模型。
到這里為止,分布式系統(tǒng)當(dāng)中常見的一致性模型就介紹完了。分布式系統(tǒng)有一個(gè)很大的特點(diǎn),就是我們看專業(yè)名詞的時(shí)候往往云里霧里,不知所云。但是當(dāng)我們?nèi)チ私馑澈蟮脑O(shè)計(jì)理念與出現(xiàn)的原因,就能發(fā)現(xiàn)它的有趣。衷心希望大家都能從中發(fā)現(xiàn)自己的樂趣,都有學(xué)有收獲。