高並發服務端分布式系統設計概要(上)
======張峻崇 原創。轉載請注明出處。======
又是快一年沒寫博客了,2013年也只剩尾巴,也不知道今年都忙了些什么。寫這篇文章的目的,主要是把今年以來學習的一些東西積淀下來,同時作為之前文章《高性能分布式計算與存儲系統設計概要》的補充與提升,然而本人水平非常有限,回頭看之前寫的文章也有許多不足,甚至是錯誤,希望同學們看到了錯誤多多見諒,更歡迎與我討論並指正。
好了,下面開始說我們今天要設計的系統。
這個系統的目標很明確,針對千萬級以上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通知其更新,最終與其它所有結點同步。(當然,“偷”數據所用的時間並不樂觀,通常在小時級別)
中篇鏈接:http://www.cnblogs.com/ccdev/p/3340484.html
下篇鏈接: http://www.cnblogs.com/ccdev/p/3341234.html