分布式數(shù)據(jù)庫(kù)系統(tǒng)的容錯(cuò)處理 – 100% 成功率, 超時(shí)和性能
之前寫(xiě)過(guò)一篇文章, 介紹"可靠通信三原則". 對(duì)于一個(gè)分布式數(shù)據(jù)庫(kù), 如果想實(shí)現(xiàn) 100% 高可用(也即客戶(hù)端的請(qǐng)求永遠(yuǎn)不會(huì)返回失敗), 同樣可以用可靠通信三原則中的重試?yán)碚摵腿ブ乩碚搧?lái)解決. 但在實(shí)踐上, 需要在成功率, 耗時(shí)(速度和性能)各方面進(jìn)行取舍. 本文分享實(shí)際經(jīng)驗(yàn), 介紹什么樣的選擇是普適的, 各位可以參考.
客戶(hù)端訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)服務(wù)器, 發(fā)起大量的請(qǐng)求, 絕對(duì)不可能做到每一個(gè)請(qǐng)求都是成功的. 因?yàn)榫W(wǎng)絡(luò)原因, 請(qǐng)求可能失敗. 因?yàn)榉?wù)器內(nèi)部處理沖突, 或者分布式節(jié)點(diǎn)間協(xié)調(diào)沖突, 都可能導(dǎo)致請(qǐng)求失敗.
所謂容錯(cuò)處理, 就是在遇到錯(cuò)誤的時(shí)候進(jìn)行重試. 因?yàn)殄e(cuò)誤必然發(fā)生, 只有重試才能消除錯(cuò)誤的影響, 就好像 IP 層必然會(huì)丟包, 但 TCP 協(xié)議通過(guò)重傳達(dá)到某種程度的可靠傳輸.
某些實(shí)現(xiàn)了 Basic Paxos + 日志復(fù)制狀態(tài)機(jī)模型的系統(tǒng), 因?yàn)樗^的"Leaderless", 會(huì)產(chǎn)生大量沖突. 即使是使用 Raft, 在某些情況下意外發(fā)生選舉, 也會(huì)導(dǎo)致請(qǐng)求沖突.
面對(duì)沖突(失敗)到底應(yīng)該由誰(shuí)來(lái)重試呢? 這涉及到工程實(shí)踐上模塊職責(zé)劃分的問(wèn)題, 模塊職責(zé)的劃分, 往往比代碼實(shí)現(xiàn)更重要. 一般來(lái)說(shuō), 發(fā)生重試的位置越底層, 性能會(huì)越好; 發(fā)生重試的位置越上層, 判斷是否應(yīng)該重試的依據(jù)就能更全面.
我們簡(jiǎn)單把數(shù)據(jù)庫(kù)系統(tǒng)(生態(tài))劃分為幾個(gè)大的模塊, 從底層(左)到上層(右)是:
- replication -> server -> client SDK -> user
最常見(jiàn)的做法是讓 user 自己重試, 例如常見(jiàn)的 Redis SDK, 如果某臺(tái) server 宕機(jī)導(dǎo)致請(qǐng)求失敗, 那么要求用戶(hù)換一個(gè) IP, 重新創(chuàng)建連接, 再次重復(fù)請(qǐng)求.
某些系統(tǒng)會(huì)封裝專(zhuān)屬的 client SDK, 例如, 把官方的 Redis SDK 做一下簡(jiǎn)單封裝, 攔截每一個(gè)請(qǐng)求的結(jié)果, 如果發(fā)現(xiàn)錯(cuò)誤, SDK 內(nèi)部就自動(dòng)重試. 這樣做, user 就不需要有重試邏輯, 代碼可以簡(jiǎn)化. 是這樣的, 多個(gè)協(xié)作的模塊, 如果某個(gè)模塊攬了一些職責(zé), 那它的上層模塊就能省些工夫.
如果 user 既不想重試, client SDK 也不想重試, 那怎么辦呢? 能不能把職責(zé)全推給 server 呢? 絕對(duì)不可能, 參見(jiàn)這篇文章的總結(jié). 那么, 為什么 SDK 重試之后, user 就不需要重試呢? 因?yàn)?SDK 和 user 是在同一個(gè)運(yùn)行空間內(nèi), 它們是一個(gè)整體, 兩者之間沒(méi)有可靠傳輸問(wèn)題.
那么, 既然 client SDK 必須有重試邏輯, server 是否就不需要有重試邏輯了呢? 理論上可以, 但實(shí)踐上, server 自身依然要降低自己的故障率, 降低故障率的必要手段就是 重試 . 例如, server 請(qǐng)求 paxos 模塊同步一條操作日志, 但因?yàn)榉穷A(yù)期的 multi-master 出現(xiàn), 導(dǎo)致和其它節(jié)點(diǎn)爭(zhēng)搶同一個(gè)位置失敗, 這時(shí), server 如果直接報(bào)錯(cuò)給 client, 那么, server 的故障數(shù)量就加一. 但是, server 可以重試, 再次調(diào)用 paxos 模塊, 去爭(zhēng)搶下一個(gè)位置, 直到成功. 這樣, client 就會(huì)很少見(jiàn)到 server 報(bào)錯(cuò).
但是, 無(wú)論是 server 還是 client 都不可能無(wú)限次重試, 因?yàn)槊恳淮沃卦嚩紩?huì)消耗時(shí)間, 最極端的情況可能要重試幾個(gè)小時(shí)直到永遠(yuǎn), 這當(dāng)然不行, 所以, 需要引入 超時(shí)機(jī)制 , 重試一定次數(shù)之后即使還是失敗, 也必須報(bào)錯(cuò)給上層.
重試會(huì)增加總的耗時(shí), 這樣, 給上層帶來(lái)的不好效果就是, 上層覺(jué)得下層速度慢, 性能差. 所以, 必須有系統(tǒng)思維, 做出判斷, 做綜合的取舍. 從經(jīng)驗(yàn)上看, 無(wú)論 server 還是 client SDK, 都必須分析細(xì)化, 盡可能重試, 以提高成功率. 大部分情況下, 開(kāi)發(fā)者往往過(guò)多地放棄重試, 而較少地進(jìn)行重試, 畢竟, 多一種重試場(chǎng)景, 就多寫(xiě)一段代碼, 人總是會(huì)想偷懶的.
要設(shè)計(jì)一個(gè)高可靠的系統(tǒng), 可靠傳輸三原則是非常有用的基礎(chǔ)理論, 但不是銀彈. 本質(zhì)上, 軟件開(kāi)發(fā)就是大量的分析細(xì)化體力活, 以及對(duì)系統(tǒng)復(fù)雜度的把控.
重試帶來(lái)的額外問(wèn)題就是去重, 這也是可靠傳輸三原則里的第二項(xiàng)原則. 你可能聽(tīng)過(guò)"冪等性"這個(gè)詞匯, 和去重是一回事. 如果一個(gè)操作是非冪等的, 那么, 就不能重試.
但是, 實(shí)踐上, 我們可以把冪等性的職責(zé)向上推, 盡可能推給上層. 畢竟, 至少對(duì)于 user 來(lái)說(shuō), 100% 的成功率, 優(yōu)先級(jí)比對(duì)冪等性的疑慮要高得多. 用戶(hù)同意下層不考慮冪等性, 而大膽地去重試, 但是, 對(duì)下層偶然的失敗會(huì)非常敏感. 簡(jiǎn)單說(shuō)就是: 別管什么冪等性, 在超時(shí)時(shí)間限制以?xún)?nèi), 大膽重試!






























