接着上一篇的內容,詳細介紹一些主流數據庫在分佈式場景下用到的算法和思想,主要說起數據一致性相關的一些策略,並分析其利弊和典型應用場景。git
對於數據庫來講,可能關心的最多的就是數據的一致性了,由此衍生出了不一樣場景下的算法和策略。
在上一篇末尾說起了兩種集羣結構:中心化和去中心化。github
一種是中心化的,由中心節點去存儲集羣信息並管理集羣狀態,其它節點只需響應數據請求,而無需知道集羣中其它節點的狀況。
這種模式的核心即是選舉或者指定一個節點做爲集羣的管理者,由管理者去協調跨節點的操做、備份數據和處理故障等。 算法
通常的,對於跨節點的操做,爲了保證事務的原子性,提出了兩步提交協議或三步提交協議,下面分別介紹。數據庫
兩步提交協議,顧名思義,就是將數據的提交分爲兩步:投票和決策。 segmentfault
首先,在第一階段,微信
中心節點(在這裏咱們稱之爲協調者)發起事務操做請求,包含事務內容,詢問是否能夠執行提交操做並等待響應;網絡
其它節點(在這裏稱之爲參與者)執行事務操做並記錄undo/redo log,最後返回是否贊成提交。架構
而後,在第二階段,協調者根據全部參與者的投票結果,若是是都贊成則通知全部參與者提交事務,不然回滾事務。
收到全部參與者迴應後,完成事務。 socket
接着,咱們考慮下兩步提交過程當中若是發生異常,會出現什麼樣的狀況,會不會影響結果的一致性,並嘗試解決。分佈式
在第一階段時,有節點宕機
有參與者宕機,此時協調者接收到錯誤響應,可認爲是失敗,將中斷事務。
協調者宕機,此時參與者等待協調者的操做通知,事務會阻塞直到協調者恢復。
對於此種狀況,解決的辦法是能夠設置多個協調者,一主多從,宕機後指定一臺從做爲新的主。
參與者也須要記錄事務的投票狀態,以便新的協調者從新找回事務狀態。
參與者和協調者都宕機了,如上一條,新的協調者將會獲取不到參與者的事務狀態(該參與者的狀態只有本身和原協調者知道),會一直阻塞地等待全部參與者恢復。
其它參與者也會處於兩階段之間,直到宕機的參與者恢復。
在第二階段,有節點宕機
有參與者宕機,此時未宕機的參與者會正常地提交/回滾事務,而因爲並不知道宕機的時機,因此可能會致使數據的不一致。
協調者宕機,如果在發送通知前,那麼參與者將阻塞地等待協調者恢復。可經過設置協調者的備份來解決,要求參與者記錄事務狀態。如果在發送通知後,不影響可忽略。
參與者與協調者都宕機了,如上兩條,可能會致使數據的不一致或阻塞。
注意,以上的宕機若是替換爲網絡分區,也會是一樣的狀況。
能夠看出,2pc的優勢是簡單直接,缺點是:
當有故障發生會阻塞事務的執行,進而影響到相關資源的釋放;
協調者的單點問題;
當二階段有參與者宕機或者網絡分區時,可能會致使數據不一致。
針對這些缺陷,出現了3pc。
三步提交協議,改進了2pc的一些缺陷,它增長了一個詢問是否可提交階段。如圖所示:
第一階段時,協調者詢問各參與者是否能夠執行事務提交,包含事務內容,並等待參與者的響應。
參與者收到請求後,若是認爲能夠成功執行事務,則返回贊成,不然停止事務。
第二階段時,協調者根據第一階段參與者的返回消息,決定是準備提交或是停止事務。若是都是贊成,那麼發送預提交請求。
參與者收到請求後,會執行事務操做,並記錄undo/redo log, 最後返回提交/停止事務。
第三階段時,協調者根據第二階段的響應,決定通知參與者提交/回滾事務,收到響應後完成事務。
3pc相比於2pc的優勢在於:
在協調者和參與者端都添加了超時機制,其中:參與者超時未應答均認爲是失敗;協調者在第二階段超時未發送請求視爲失敗,而第三階段超時未發送請求視爲成功,參與者在通過了指定超時時間後提交事務。這樣便具有了必定的容錯性。
不只如此,這樣還能夠有效減小阻塞時間。
提供了協調者的主備方案,避免了單點問題。
缺點:
第二階段時,參與者在接收到預提交請求後發生網絡分區,此參與者在超時後提交事務,而協調者在超時後認爲事務失敗並通知其它參與者回滾事務,最終致使數據不一致。若發生此狀況,只能經過上層去協調解決這個問題,如上一篇提到的兩種解決方案。(2pc也有相似缺陷)
比2pc多了一個階段,意味着同等狀況下,耗時要多一點。
另外一種則是去中心化的,由節點之間互相通訊去協商一致。比較有名的算法如Paxos。
Paxos算法在分佈式領域具備很是重要的地位,Google Chubby的做者Mike Burrows曾經說過,這個世界上只有一種一致性算法,那就是Paxos,其它的都是殘次品。
不過這個算法實在是難理解,難實現;之後有機會我會專門總結一篇文章分享下,有興趣的道友能夠先去看看《Paxos Made Simple》,寫得很不錯。
此外,考慮到集羣中的節點數量並非一成不變的,因此若是使用的是通常的Hash算法,那麼在集羣新增節點或刪除節點時,會致使節點間大量數據的遷移,進而影響可用性,故而提出了一致性Hash算法以減小數據的遷移量。
通常的Hash算法,如對key取模而後分散到不一樣的節點中:假設有3個節點,共有key分別爲1~7的數據,分配結果以下圖
如今,若是新增一個節點,那麼分配結果變爲:
能夠發現大部分的節點都被從新分配到了不一樣的節點上,即遷移數據是O(n)
複雜度(n爲數據總量),沒法平滑地擴縮容。
接下來再來看下一致性Hash,它的分配方式是對key和節點作相同的Hash運算,而後將key分配到恰好大於或等於它Hash值的節點上(若節點都比它Hash值小,則分配到最小的節點上,即造成一個「環」);
仍是上面的那個例子,對key和節點都作對7取模的Hash計算,而後分配。先是有三個節點:
新增一個節點:
能夠看出,增長一個節點後只有少許數據從5節點移動到4節點,極大的減小了數據遷移量。
可是,一致性Hash也有缺陷:查找效率低。通常須要逐個去比較Hash值直到找到恰好大於等於的節點,故查找複雜度爲O(k)
(k爲節點數量)。
能夠經過在節點中冗餘一份節點表來加快查找。
保證一致性,要麼是經過共享存儲,要麼是經過消息協調。
數據庫自己就是共享存儲。
不論是2pc、3pc仍是paxos,都是經過節點間的交換消息去達到一致的狀態,這也是分佈式系統的經常使用作法。
瞭解了這些策略的原理後,不論是用Zookeeper、RabbitMQ、Redis或其它消息組件(甚至是基於socket通訊)去實現它,都是水到渠成的事情了。
超時是個好設計,由於它是不需詢問即可以察覺錯誤的方式(畢竟沒有錯誤就不會超時了),不少設計中都會將超時做爲一種信號,並嘗試容錯/修復等操做。
在運行過程的一些錯誤並不能經過底層的策略徹底規避,須要根據具體業務在上層作相應的容錯措施。
冗餘是個好設計,幾乎在各類組件的設計都能見到,經過犧牲一點空間較大地提升檢索效率。
有機會的話,以後的篇章我會收集並比較幾種典型分佈式組件的具體實現,對這些組件有個更加直觀和深刻的理解,以便充實和改進本身的知識結構並分享出來。
最後爲方便查詢,整理了下往期文章到github中:https://github.com/dengyuankai272/blog
做者信息
本文系力譜宿雲LeapCloud旗下MaxLeap團隊_基礎服務組成員:呂舜 【原創】
力譜宿雲LeapCloud 首發:https://blog.maxleap.cn/archi...
呂舜,主攻Java,對Python、數據分析也有關注。從業期間,負責過訂閱系統、App製做雲服務、開源BaaS平臺、分佈式任務調度系統等產品的設計研發工做。現任MaxLeap基礎服務與架構成員,負責雲服務系統相關的設計與開發。
相關文章
微服務實戰:從架構到發佈(一)
微服務實戰:從架構到發佈(二)
移動雲平臺的基礎架構之旅(一):雲應用
從應用到平臺 – 雲服務架構的演進過程
做者往期佳做
RabbitMQ在分佈式系統的應用
關於分佈式系統的思考
歡迎掃如下二維碼,關注咱們的微信訂閱號: