一篇簡明扼要的分佈式系統設計概要,能夠好好參考。html
原文連接:前端
上篇:http://www.cnblogs.com/ccdev/p/3338412.htmlnode
中篇:http://www.cnblogs.com/ccdev/p/3340484.html算法
下篇:http://www.cnblogs.com/ccdev/p/3341234.html瀏覽器
來源:張峻崇緩存
連接:www.cnblogs.com/ccdev/p/3338412.html安全
寫這篇文章的目的,主要是把今年以來學習的一些東西積澱下來,同時做爲以前文章《高性能分佈式計算與存儲系統設計概要》的補充與提高,然而本人水平很是有限,回頭看以前寫的文章也有許多不足,甚至是錯誤,但願同窗們看到了錯誤多多見諒,更歡迎與我討論並指正。服務器
我大概是從2010年末起開始進入高併發、高性能服務器和分佈式這一塊領域的研究,到如今也差很少有三年,但其實不少東西仍然是隻知其一;不知其二,我所提到的許許多多概念,也許任何一個我都不能講的很清楚,還須要繼續鑽研。但咱們平時在工做和學習中,多半也只能從這種只知其一;不知其二開始,慢慢琢磨,不斷改進。併發
好了,下面開始說咱們今天要設計的系統。負載均衡
這個系統的目標很明確,針對千萬級以上PV的網站,設計一套用於後臺的高併發的分佈式處理系統。這套系統包含業務邏輯的處理、各類計算、存儲、日誌、備份等方面內容,可用於類微博,SNS,廣告推送,郵件等有大量線上併發請求的場景。
如何抗大流量高併發?(不要告訴我把服務器買的再好一點)提及來很簡單,就是「分」,如何「分」,簡單的說就是把不一樣的業務分拆到不一樣的服務器上去跑(垂直拆分),相同的業務壓力分拆到不一樣的服務器去跑(水平拆分),並時刻不要忘記備份、擴展、意外處理等討厭的問題。提及來都比較簡單,但設計和實現起來,就會比較困難。之前個人文章,都是「從整到零」的方式來設計一個系統,此次我們就反着順序來。
那咱們首先來看,咱們的數據應該如何存儲和取用。根據咱們以前肯定的「分」的方法,先肯定如下2點:
(1)咱們的分佈式系統,按不一樣的業務,存儲不一樣的數據;(2)一樣的業務,同一個數據應存儲多份,其中有的存儲提供讀寫,而有的存儲只提供讀。
好,先解釋下這2點。對於(1)應該容易理解,好比說,我這套系統用於微博(就假想咱們作一個山寨的推特吧,給他個命名就叫「山推」 好了,如下都叫山推,Stwi),那麼,「我關注的人」這一個業務的數據,確定和「我發了的推文」這個業務的數據是分開存儲的,那麼咱們如今把,每個業務所負責的數據的存儲,稱爲一個group。即以group的方式,來負責各個業務的數據的存儲。接下來講(2),如今咱們已經知道,數據按業務拆到group裏面去存取,那麼一個group裏面又應該有哪些角色呢?天然的,應該有一臺主要的機器,做爲group的核心,咱們稱它爲Group Master,是的,它就是這個group的主要表明。這個group的數據,在Group Master上應該都能找到,進行讀寫。另外,咱們還須要一些輔助角色,咱們稱它們爲Group Slaves,這些slave機器作啥工做呢?它們負責去Group Master處拿數據,並儘可能保持和它同步,並提供讀服務。請注意個人用詞,「儘可能」,稍後將會解釋。如今咱們已經有了一個group的基本輪廓:
一個group提供對外的接口(廢話不然怎麼存取數據),group的底層能夠是實際的File System,甚至是HDFS。Group Master和Group Slave能夠共享同一個File System(用於不能丟數據的強一致性系統),也能夠分別指向不一樣的File System(用於弱一致性,容許停寫服務和系統宕機時丟數據的系統),但總之應認爲這個"File System"是無狀態,有狀態的是Group Master和各個Group Slave。
下面來講一個group如何工做,同步等核心問題。首先,一個group的Group Master和Group Slave間應保持強一致性仍是弱一致性(最終一致性)應取決於具體的業務需求,以咱們的「山推」來講,Group Master和Group Slave並不要求保持強一致性,而弱一致性(最終一致性)即能知足要求,爲何?由於對於「山推」來說,一個Group Master寫了一個數據,而另外一個Group Slave被讀到一個「過時」(由於Group Master已經寫,但此Group Slave還未更新此數據)的數據一般並不會帶來大問題,好比,我在「山推」上發了一個推文,「關注個人人」並無即時同步地看到個人最新推文,並無太大影響,只要「稍後」它們能看到最新的數據便可,這就是所謂的最終一致性。但當Group Master掛掉時,寫服務將中斷一小段時間由其它Group Slave來頂替,稍後還要再講這個問題。假如咱們要作的系統不是山推,而是淘寶購物車,支付寶一類的,那麼弱一致性(最終一致性)則很難知足要求,同時寫服務掛掉也是不能忍受的,對於這樣的系統,應保證「強一致性」,保證不能丟失任何數據。
接下來仍是以咱們的「山推「爲例,看看一個group如何完成數據同步。假設,如今我有一個請求要寫一個數據,因爲只有Group Master能寫,那麼Group Master將接受這個寫請求,並加入寫的隊列,而後Group Master將通知全部Group Slave來更新這個數據,以後這個數據才真正被寫入File System。那麼如今就有一個問題,是否應等全部Group Slave都更新了這個數據,纔算寫成功了呢?這裏涉及一些NWR的概念,咱們做一個取捨,即至少有一個Group Slave同步成功,才能返回寫請求的成功。這是爲何呢?由於假如這時候Group Master忽然掛掉了,那麼咱們至少能夠找到一臺Group Slave保持和Group Master徹底同步的數據並頂替它繼續工做,剩下的、其它的Group Slave將「異步」地更新這個新數據,很顯然,假如如今有多個讀請求過來併到達不一樣的Group Slave節點,它們極可能讀到不同的數據,但最終這些數據會一致,如前所述。咱們作的這種取捨,叫「半同步」模式。那以前所說的強一致性系統應如何工做呢?很顯然,必須得等全部Group Slave都同步完成才能返回寫成功,這樣Group Master掛了,沒事,其它Group Slave頂上就行,不會丟失數據,可是付出的代價就是,等待同步的時間。假如咱們的group是跨機房、跨地區分佈的,那麼等待全部Group Slave同步完成將是很大的性能挑戰。因此綜合考慮,除了對某些特別的系統,採用「最終一致性」和「半同步」工做的系統,是符合高併發線上應用需求的。並且,還有一個很是重要的緣由,就是一般線上的請求都是讀>>寫,這也正是「最終一致性」符合的應用場景。
好,繼續。剛纔咱們曾提到,若是Group Master宕機掛掉,至少能夠找到一個和它保持同不的Group Slave來頂替它繼續工做,其它的Group Slave則「儘可能」保持和Group Master同步,如前文所述。那麼這是如何作到的呢?這裏涉及到「分佈式選舉」的概念,如Paxos協議,經過分佈式選舉,總能找到一個最接近Group Master的Group Slave,來頂替它,從而保證系統的可持續工做。固然,在此過程當中,對於最終一致性系統,仍然會有一小段時間的寫服務中斷。如今繼續假設,咱們的「山推」已經有了一些規模,而負責「山推」推文的這個group也有了五臺機器,並跨機房,跨地區分佈,按照上述設計,不管哪一個機房斷電或機器故障,都不會影響這個group的正常工做,只是會有一些小的影響而已。
那麼對於這個group,還剩2個問題,一是如何知道Group Master掛掉了呢?二是在圖中咱們已經看到Group Slave是可擴展的,那麼新加入的Group Slave應如何去「偷」數據從而逐漸和其它節點同步呢?對於問題一,咱們的方案是這樣的,另外提供一個相似「心跳」的服務(由誰提供呢,後面咱們將講到的Global Master將派上用場),group內全部節點不管是Group Master仍是Group Slave都不停地向這個「心跳」服務去申請一個證書,或認爲是一把鎖,而且這個鎖是有時間的,會過時。「心跳」服務按期檢查Group Master的鎖和其有效性,一旦過時,若是Group Master工做正常,它將鎖延期並繼續工做,不然說明Group Master掛掉,由其它Group Slave競爭獲得此鎖(分佈式選舉),從而變成新的Group Master。對於問題二,則很簡單,新加入的Group Slave不斷地「偷」老數據,而新數據總因爲Group Master通知其更新,最終與其它全部結點同步。(固然,「偷」數據所用的時間並不樂觀,一般在小時級別)
咱們完成了在此分佈式系統中,一個group的設計。那麼接下來,咱們設計系統的其餘部分。如前文所述,咱們的業務及其數據以group爲單位,顯然在此係統中將存在many many的groups(別告訴我你的網站總共有一個業務,像咱們的「山推」,那業務是一堆一堆地),那麼由誰來管理這些groups呢?由Web過來的請求,又將如何到達指定的group,並由該group處理它的請求呢?這就是咱們要討論的問題。
咱們引入了一個新的角色——Global Master,顧名思義,它是管理全局的一個節點,它主要完成以下工做:(1)管理系統全局配置,發送全局控制信息;(2)監控各個group的工做狀態,提供心跳服務,若發現宕機,通知該group發起分佈式選舉產生新的Group Master;(3)處理Client端首次到達的請求,找出負責處理該請求的group並將此group的信息(location)返回,則來自同一個前端請求源的該類業務請求自第二次起不須要再向Global Master查詢group信息(緩存機制);(4)保持和Global Slave的強一致性同步,保持自身健康狀態並向全局的「心跳」服務驗證自身的狀態。
如今咱們結合圖來逐條解釋上述工做,顯然,這個系統的完整輪廓已經初現。
首先要明確,無論咱們的系統如何「分佈式」,總之會有至少一個最主要的節點,術語可稱爲primary node,如圖所示,咱們的系統中,這個節點叫Global Master,也許讀過GFS + Bigtable論文的同窗知道,在GFS + Bigtable裏,這樣的節點叫Config Master,雖然名稱不同,但所作的事情卻差很少。這個主要的Global Master可認爲是系統狀態健康的標誌之一,只要它在正常工做,那麼基本能夠保證整個系統的狀態是基本正常的(什麼?group或其餘結點會不正常不工做?前面已經說過,group內會經過「分佈式選舉」來保證本身組內的正常工做狀態,不要告訴我group內全部機器都掛掉了,那個機率我想要忽略它),假如Global Master不正常了,掛掉了,怎麼辦?顯然,圖中的Global Slave就派上用場了,在咱們設計的這個「山推」系統中,至少有一個Global Slave,和Global Master保持「強一致性」的徹底同步,固然,若是有不止一個Global Slave,它們也都和Global Master保持強一致性徹底同步,這樣有個好處,假如Global Master掛掉,不用停寫服務,不用進行分佈式選舉,更不會讀服務,隨便找一個Global Slave頂替Global Master工做便可。這就是強一致性最大的好處。那麼有的同窗就會問,爲何咱們以前的group,不能這麼搞,非要搞什麼最終一致性,搞什麼分佈式選舉(Paxos協議屬於既難理解又難實現的坑爹一族)呢?我告訴你,仍是壓力,壓力。咱們的系統是面向日均千萬級PV以上的網站(「山推」嘛,推特是億級PV,咱們千萬級也不過度吧),但系統的壓力主要在哪呢?細心的同窗就會發現,系統的壓力並不在Global Master,更不會在Global Slave,由於他們根本不提供數據的讀寫服務!是的,系統的壓力正是在各個group,因此group的設計纔是最關鍵的。同時,細心的同窗也發現了,因爲Global Master存放的是各個group的信息和狀態,而不是用戶存取的數據,因此它更新較少,也不能認爲讀>>寫,這是不成立的,因此,Global Slave和Global Master保持強一致性徹底同步,正是最好的選擇。因此咱們的系統,一臺Global Master和一臺Global Slave,暫時能夠知足需求了。
好,咱們繼續。如今已經瞭解Global Master的大概用途,那麼,一個來自Client端的請求,如何到達真正的業務group去呢?在這裏,Global Master將提供「首次查詢」服務,即,新請求首次請求指定的group時,經過Global Master得到相應的group的信息,之後,Client將使用該信息直接嘗試訪問對應的group並提交請求,若是group信息已過時或是不正確,group將拒絕處理該請求並讓Client從新向Global Master請求新的group信息。顯然,咱們的系統要求Client端緩存group的信息,避免屢次重複地向Global Master查詢group信息。這裏其實又挖了許多爛坑等着咱們去跳,首先,這樣的工做模式知足基本的Ddos攻擊條件,這得經過其餘安全性措施來解決,避免group老是收到不正確的Client請求而拒絕爲其服務;其次,當出現大量「首次」訪問時,Global Master儘管只提供查詢group信息的讀服務,仍有可能不堪重負而掛掉,因此,這裏仍有很大的優化空間,比較容易想到的就是採用DNS負載均衡,由於Global Master和其Global Slave保持徹底同步,因此DNS負載均衡能夠有效地解決「首次」查詢時Global Master的壓力問題;再者,這個工做模式要求Client端緩存由Global Master查詢獲得的group的信息,萬一Client不緩存怎麼辦?呵呵,不用擔憂,Client端的API也是由咱們設計的,以後才面向Web前端。
以後要說的,就是圖中的「Global Heartbeat」,這又是個什麼東西呢?可認爲這是一個管理Global Master和Global Slave的節點,Global Master和各個Global Slave都不停向Global Heartbeat競爭成爲Global Master,若是Global Master正常工做,按期更新其狀態並延期其得到的鎖,不然由Global Slave替換之,原理和group內的「心跳」同樣,但不一樣的是,此處Global Master和Global Slave是強一致性的徹底同步,不須要分佈式選舉。有同窗可能又要問了,假如Global Heartbeat掛掉了呢?我只能告訴你,這個很不常見,由於它沒有任何壓力,並且掛掉了必須人工干預才能修復。在GFS + Bigtable裏,這個Global Heartbeat叫作Lock Service。
如今接着設計咱們的「山推」系統。有了前面兩篇的鋪墊,咱們的系統如今已經有了五臟六腑,剩下的工做就是要讓其羽翼豐滿。那麼,是時候,放出咱們的「山推」系統全貌了:
前面囉嗦了半天,也許很多同窗看的不明不白,好了,如今開始看圖說話環節:
(1)整個系統由N臺機器組合而成,其中Global Master一臺,Global Slave一臺到多臺,二者之間保持強一致性並徹底同步,可由Global Slave隨時頂替Global Master工做,它們被Global Heartbeat(一臺)來管理,保證有一個Global Master正常工做;Global Heartbeat因爲無壓力,一般認爲其不能掛掉,若是它掛掉了,則必須人工干預才能恢復正常;
(2)整個系統由多個groups合成,每個group負責相應業務的數據的存取,它們是數據節點,是真正抗壓力的地方,每個group由一個Group Master和一個到多個Group Slave構成,Group Master做爲該group的主節點,提供讀和寫,而Group Slave則只提供讀服務且保證這些Group Slave節點中,至少有一個和Group Master保持徹底同步,剩餘的Group Slave和Group Master可以達到最終一致,它們之間以「半同步」模式工做保證最終一致性;
(3)每個group的健康狀態由Global Master來管理,Global Master向group發送管理信息,並保證有一個Group Master正常工做,若Group Master宕機,在該group內經過分佈式選舉產生新的Group Master頂替原來宕機的機器繼續工做,但仍然有一小段時間須要中斷寫服務來切換新的Group Master;
(4)每個group的底層是實際的存儲系統,File system,它們是無狀態的,即,由分佈式選舉產生的Group Master能夠在原來的File system上繼續工做;
(5)Client的上端可認爲是Web請求,Client在「首次」進行數據讀寫時,向Global Master查詢相應的group信息,並將其緩存,後續將直接與相應的group進行通訊;爲避免大量「首次」查詢沖垮Global Master,在Client與Global Master之間增長DNS負載均衡,可由Global Slave分擔部分查詢工做;
(6)當Client已經擁有足夠的group信息時,它將直接與group通訊進行工做,從而真正的壓力和流量由各個group分擔,並處理完成須要的工做。
好了,如今咱們的「山推」系統設計完成了,可是要將它編碼實現,還有很遠的路要走,細枝末節的問題也會暴露更多。若是該系統用於線上計算,若有大量的Map-Reduce運行於group中,系統將會更復雜,由於此時不光考慮的數據的存儲同步問題,操做也須要同步。如今來檢驗下咱們設計的「山推」系統,主要分佈式指標:
一致性:如前文所述,Global機器強一致性,Group機器最終一致性;
可用性:Global機器保證了HA(高可用性),Group機器則不保證,但知足了分區容錯性;
備份Replication:Global機器採用徹底同步,Group機器則是半同步模式,均可以進行橫向擴展;
故障恢復:如前文所述,Global機器徹底同步,故障可不受中斷由slave恢復工做,但Group機器採用分佈式選舉和最終一致性,故障時有較短期的寫服務須要中斷並切換到slave機器,但讀服務可不中斷。
還有其餘一些指標,這裏就再也不多說了。還有一些細節,須要提一下,好比以前的評論中有同窗提到,group中master掛時,由slave去頂替,但這樣一來該group內其餘全部slave須要分擔以前成這新master的這個slave的壓力,有可能繼續掛掉而形成雪崩。針對此種狀況,可採用以下作法:即在一個group內,至少還存在一個真正作「備份」用途的slave,平時不抗壓力,只同步數據,這樣當出現上述狀況時,可由該備份slave來頂替成爲新master的那個slave,從而避免雪崩效應。不過這樣一來,就有新的問題,因爲備份slave平時不抗壓力,加入抗壓力後必然產生必定的數據遷移,數據遷移也是一個較麻煩的問題。常採用的分攤壓力作法如一致性Hash算法(環狀Hash),可將新結點加入對整個group的影響降到較小的程度。
另外,還有一個較爲棘手的問題,就是系統的日誌處理,主要是系統宕機後如何恢復以前的操做日誌。比較常見的方法是對日誌做快照(Snapshot)和回放點(checkpoint),並採用Copy-on-write方式按期將日誌做snapshot存儲,當發現宕機後,找出對應的回放點並恢復以後的snapshot,但此時仍可能有新的寫操做到達,併產生不一致,這裏主要依靠Copy-on-write來同步。
最後再說說圖中的Client部分。顯然這個模塊就是面向Web的接口,後面鏈接咱們的「山推」系統,它能夠包含諸多業務邏輯,最重要的,是要緩存group的信息。在Client和Web之間,還能夠有諸如Nginx之類的反向代理服務器存在,作進一步性能提高,這已經超出了本文的範疇,但咱們必須明白的是,一個高併發高性能的網站,對性能的要求是從起點開始的,何爲起點,即用戶的瀏覽器。
如今,讓咱們來看看GFS的設計:
很明顯,這麼牛的系統我是設計不出來的,咱們的「山推」,就是在學習GFS + Bigtable的主要思想。說到這,也必須提一句,可能我文章中,名詞擺的有點多了,如NWR,分佈式選舉,Paxos包括Copy-on-write等,有興趣的同窗可自行google瞭解。由於說實在的,這些概念我也無法講透徹,只是只知其一;不知其二。另外,你們可參考一些分佈式項目的設計,如Cassandra,包括淘寶的Oceanbase等,以加深理解。