備份落后的問題
備份的原因不僅僅是容錯(cuò)性,像前面說到的,減小延遲和擴(kuò)展性也是目標(biāo)。Leader-based的備份會(huì)把所有的寫請(qǐng)求通過一個(gè)節(jié)點(diǎn)完成,但是讀請(qǐng)求可以調(diào)用其他本分節(jié)點(diǎn)。對(duì)于那些讀負(fù)載很大,但是寫請(qǐng)求比較少的應(yīng)用來說,有一個(gè)非常好的方法。創(chuàng)建大量的Follower,把讀請(qǐng)求分配到這些Follower中。這樣可以減輕Leader負(fù)載的同時(shí),讓讀請(qǐng)求就近訪問,減小延遲。
在這種讀擴(kuò)展(read-scaling)的架構(gòu)下,你可以簡(jiǎn)單地通過加Follower的方法提高讀請(qǐng)求的負(fù)載。但是實(shí)際情況中,只有異步的同步方式才可用,如果你企圖把所有的Follower都設(shè)成同步請(qǐng)求,一個(gè)節(jié)點(diǎn)異常就會(huì)導(dǎo)致整個(gè)系統(tǒng)掛掉。節(jié)點(diǎn)越多,越容易出問題,所以把所有的節(jié)點(diǎn)都設(shè)成同步請(qǐng)求顯然是不現(xiàn)實(shí)的。但是從異步同步的Follower中讀取數(shù)據(jù)會(huì)有一個(gè)問題,F(xiàn)ollower的數(shù)據(jù)可能是過期的。這就導(dǎo)致數(shù)據(jù)庫無法保證一致性,當(dāng)你同時(shí)發(fā)送兩個(gè)請(qǐng)求分別到Leader和Follower時(shí),返回結(jié)果可能不一樣,因?yàn)長(zhǎng)eader的備份數(shù)據(jù)可能還沒同步到Follower中。這種不一致時(shí)暫時(shí)的,當(dāng)你停止寫入,然后等一會(huì),F(xiàn)ollower最終會(huì)跟上Leader的進(jìn)度并且與之一致。所以我們會(huì)稱他為最終一致(eventual consistency)
最終這個(gè)詞就跟字面上的含義一樣,備份節(jié)點(diǎn)到底落后了多少不好說,一般可能只有不到一秒鐘,可能你根本感覺不到。但是如果系統(tǒng)負(fù)載太大,這種落后就可以輕松達(dá)到幾秒甚至分鐘級(jí)。當(dāng)這種落后太大的時(shí)候,不一致性的問題就不再是理論上的問題而是一個(gè)實(shí)際需要處理的問題了。這節(jié)我們講3種典型的問題,并且講些解決他們的方法。
讀取剛寫入的內(nèi)容
很多應(yīng)用都允許用戶在提交數(shù)據(jù)之后查看他剛提交的內(nèi)容。當(dāng)用戶提交的時(shí)候,請(qǐng)求肯定通過Leader,但是讀取的時(shí)候可能是通過Follower讀取的。在異步備份中,就會(huì)有像Figure 5.3的問題。如果用戶在寫入后很短的時(shí)間內(nèi)去查看數(shù)據(jù),數(shù)據(jù)沒有到Follower,用戶看上去就好像數(shù)據(jù)丟了一樣,顯然,上帝們就不太高興了。

這種情況下,我們需要一個(gè)讀寫一致, (read-after-write consistency / read-your-writes consistency)。我們需要保證用戶一旦刷新頁面,就能看到自己所有最新的更新,不過其他用戶的更新就不用保證了。只有這樣才能讓用戶放心,他的數(shù)據(jù)都正確的保存了。 要如何才能實(shí)現(xiàn)呢?有幾種方法
- 如果讀取的內(nèi)容用戶可能剛剛更新,從leader讀取,否則從Follower讀取。這就要求你不實(shí)際去查詢數(shù)據(jù)庫但是能夠知道某條數(shù)據(jù)可能更新了。舉個(gè)例子,一般一個(gè)用戶的檔案信息只有用戶自己可以修改,所以當(dāng)一個(gè)人查看自己的檔案的時(shí)候,從Leader讀取,查其他人檔案的時(shí)候,從Follower讀取。
- 如果很多東西可能被修改,那上面那個(gè)方法就不好使了。那某些其他的字段可能能幫助你知道要不要從Leader讀取。比如你可以跟蹤記錄你上次更新的時(shí)間,如果超過1分鐘,從Follower讀取,否則從Leader讀取??赡苓€需要監(jiān)控Follower的更新的落后情況,用以決定應(yīng)該怎么搞。
- 客戶端可以記下自己最近更新的時(shí)間戳,然后系統(tǒng)保證所有Follower當(dāng)處理到這個(gè)用戶的讀取請(qǐng)求時(shí),至少要更新到對(duì)應(yīng)的時(shí)間戳才可以正常返回。如果一個(gè)備份節(jié)點(diǎn)沒有更新到這個(gè)時(shí)間戳,那要么請(qǐng)求會(huì)被分配給其他的備份節(jié)點(diǎn),或者請(qǐng)求等到備份更新之后才返回。這個(gè)時(shí)間戳可以是邏輯時(shí)間戳,比如更新的日志序列號(hào),也可以是系統(tǒng)時(shí)間戳。
- 但是這樣也會(huì)帶來額外的復(fù)雜度,因?yàn)橐坏┌l(fā)現(xiàn)請(qǐng)求必須通過Leader返回,那如果你的服務(wù)是跨地區(qū)的就慘了,你的請(qǐng)求要跨越千山萬水發(fā)到Leader對(duì)應(yīng)的數(shù)據(jù)中心去處理。耗時(shí)可想而知。
另外有一個(gè)問題是用戶可能通過多個(gè)設(shè)備訪問你的應(yīng)用,比如同時(shí)用手機(jī)和網(wǎng)頁。這個(gè)時(shí)候你還需要一個(gè)跨設(shè)備的讀寫一致(cross-device read-after-write consistency),這就又有新問題需要考慮了
- 想要知道用戶的最后更新時(shí)間戳就變得更難了,原來一個(gè)設(shè)備自己記著就挺麻煩了,現(xiàn)在他因?yàn)闆]辦法知道其他設(shè)備上發(fā)生了什么,所以這個(gè)功能就不能存在本地設(shè)備中而要存到一個(gè)中心服務(wù)器去了。
- 同樣還是多個(gè)設(shè)備可能需要跨數(shù)據(jù)中心訪問Leader的問題,跟上面問題差不多。因?yàn)殡m然地域相同,但是你的不同設(shè)備走的網(wǎng)絡(luò)不一定一樣,原本可能就近訪問的請(qǐng)求也要調(diào)到Leader的數(shù)據(jù)中心去了。
單調(diào)讀 (Monotonic Reads)
第二個(gè)異常情況是當(dāng)你從一個(gè)異步的Follower中讀取數(shù)據(jù)時(shí),你可能會(huì)讀到過去時(shí)間的內(nèi)容。
舉個(gè)例子,如Figure 5-4所示,user2345的同一個(gè)請(qǐng)求發(fā)送給了兩個(gè)Follower,一個(gè)落后的比較少,一個(gè)比較多,這就好像用戶刷新了兩次頁面,隨機(jī)分給了不同的機(jī)器。第一次他會(huì)發(fā)現(xiàn)user 1234更新了一條評(píng)論,但是第二次由于Follower沒有更新到這個(gè)時(shí)間,就卻看不到了。這就導(dǎo)致用戶第二次請(qǐng)求看到的內(nèi)容比第一次看到的時(shí)間要早。如果第一次也沒返回,問題就還好,因?yàn)榉凑膊恢?,但是?dāng)下這種一會(huì)看到了,一會(huì)又看不到的情況就讓人感覺很差了。

單調(diào)讀(Monotonic reads)就是用來保證這個(gè)問題不會(huì)出現(xiàn)的。他比強(qiáng)一致性要求低一些,但是又比最終一致(eventual consistency)的要求高。在這個(gè)場(chǎng)景下,當(dāng)你請(qǐng)求數(shù)據(jù)時(shí),你還是有可能看到老的數(shù)據(jù),但是他保證如果你順序的請(qǐng)求幾次,后面的請(qǐng)求一定比先前的請(qǐng)求的結(jié)果更新。也就是你不會(huì)看到比這次更老的數(shù)據(jù)。
實(shí)現(xiàn)他的一個(gè)方法是讓客戶端永遠(yuǎn)從一個(gè)備份節(jié)點(diǎn)中讀取數(shù)據(jù),比如客戶端不再是隨機(jī)從備份節(jié)點(diǎn)中挑一個(gè)請(qǐng)求,而是根據(jù)自己的id做hash選定某一個(gè)備份節(jié)點(diǎn)。如果這個(gè)節(jié)點(diǎn)崩了,再去訪問其他的節(jié)點(diǎn)。
讀取數(shù)據(jù)的前后一致性(Consistent Prefix Reads)
第三個(gè)問題是讀取數(shù)據(jù)的邏輯順序可能被破壞,想象這么一個(gè)對(duì)話場(chǎng)景
Mr. Poons:
你一般能預(yù)測(cè)多久的未來? Mrs. Cake?
Mrs. Cake:
一般十秒左右,Mr. Poons.
這個(gè)對(duì)話里面有一個(gè)邏輯前后順序,Mrs Cake 一定是聽到了Mr. Poons的訊問后才會(huì)回到問題。但是如果有第三個(gè)人通過Follower在聽他們的對(duì)話,可能會(huì)因?yàn)閮删湓挷煌腇ollower的落后的進(jìn)度不一樣,所以可能會(huì)變成下面這樣。原因如Figure 5-5所示
Mrs. Cake:
一般十秒左右,Mr. Poons.
Mr. Poons:
你一般能預(yù)測(cè)多久的未來? Mrs. Cake?

我們需要另外一個(gè)種保證機(jī)制以防止這類問題的出現(xiàn),讀取數(shù)據(jù)的前后一致性(consistent prefix reads)。它要求讀取數(shù)據(jù)的順序必須和寫入數(shù)據(jù)的順序一樣。這個(gè)在分區(qū)(partitioned)數(shù)據(jù)庫中問題尤為明顯,這個(gè)第6章講。如果數(shù)據(jù)庫按照寫入請(qǐng)求的順序處理請(qǐng)求,那一般不會(huì)有問題,但是分區(qū)的數(shù)據(jù)庫中,不同的分區(qū)往往獨(dú)立處理接收到的請(qǐng)求,所以針對(duì)寫入請(qǐng)求沒辦法知道他在全局的順序。這個(gè)時(shí)候邏輯前后順序就不好搞了。
有一個(gè)方法是讓這種有前后一致性問題的請(qǐng)求寫入都發(fā)給同一個(gè)分區(qū),但是實(shí)際操作卻很難實(shí)現(xiàn)。這個(gè)方法也是以后會(huì)有專門一節(jié)來講的。
更新落后的解決方案
如果你的數(shù)據(jù)庫能實(shí)現(xiàn)最終一致,那你就要考慮如果你的備份節(jié)點(diǎn)落后了比較多,比如幾分鐘或者幾小時(shí),會(huì)不會(huì)有問題?如果是沒問題,那就不用管了。如果不行,那就要考慮提供更強(qiáng)的保證了,比如前面講到的讀寫一致。
前面也將過了,有提供更強(qiáng)保證的方法,比如讓Leader處理特定的讀請(qǐng)求,但是具體實(shí)現(xiàn)起來往往比較難且容易出錯(cuò)。如果能讓開發(fā)者不用操心這些問題,就很爽了。事務(wù)(transactions)能幫助你搞定這個(gè),他能夠讓數(shù)據(jù)庫提供更強(qiáng)的保證,讓開發(fā)者也更省心。單節(jié)點(diǎn)的數(shù)據(jù)庫早就提供事務(wù)支持了,但是分布式系統(tǒng)往往就不支持了,因?yàn)檫@會(huì)給性能和可用性帶來極大的挑戰(zhàn)。并且他們認(rèn)為前面講到的那些問題在一個(gè)可擴(kuò)展的系統(tǒng)中是很難避免的,能達(dá)到最終一致性就可以了。在某種程度上,他們說的對(duì),但是想的太過于簡(jiǎn)單了,所以我們后面會(huì)有些許不同的視角來看這個(gè)問題,第7-9章還會(huì)討論事務(wù)的內(nèi)容,另外第三章還會(huì)介紹一些他的替代方案。