高性能分布式计算与存储系统设计概要——暨2012年工作3年半总结
作者:張峻崇?原文鏈接在此。
2012年底,末日之后,看到大家都在寫年末總結,我也忍不住想一試。工作已經3年半了,頭一次寫總結。雖然到現在仍是無名小碼農一名,但工作這些年,技術著實有不少積累。成長最大的,當然就是這篇文章標題提到的——高性能分布式計算與存儲系統的設計和研發過程,這也是我自2010年供職于國內最大的某著名網站之后,和這個系統一起成長,親眼見證和伴隨著它的發展,從一個嬰兒一樣的"Demo"程序,成長為現在可以處理千萬級日PV的強大系統,直到2012年我離開。我也順勢積累了Unix/Linux服務器、多線程、I/O、海量數據處理、注重高性能與效率的C/C++編程等寶貴的碼農財富,當然,遺憾和不足,仍然是有許多的。
2012年,其實是自工作以來,技術積淀最多的一年。因為,在2012年,我終于學會了獨立思考,我不再像以前一樣,許許多的技術只是需要用到的時候,匆忙的google(有時候還要先匆忙的先FQ),我發現,“好記性不如爛筆頭”,古訓確實毋庸置疑,有大量的、瑣碎的技術經驗、編程細節、技巧,需要積淀下來,可能單條的細節與技巧,并不會對一個人的職業生涯產生什么影響,但把它們都積聚起來,就會強大許多,很實際的,帶來的技術提升,能帶來更高的Offer。所以,2012年,我開始到博客園寫技術博客,和眾多園友分享我對技術的一知半解,共同進步;也終于耐下心,為自己做了一個簡單的個人主頁,雖然10年前,我就可以做出這樣的東西……;我成為了更忠實的蘋果粉,所以我嘗試去做iOS創業,雖然這和我的主要工作研究方向并不一致,當我看到自己做的demo在自己iPhone 4s上跑起來,我突然又有一了一種久違的興奮——那是每一個程序員,都體會過的,小小的成就感;2012年,我開始接觸和了解許多以前從來不懂的技術:Hadoop、GoogleFS、JVM、XCode、ARC……小到如何將vim打造成一個IDE……;2012年,我暫時離開了生活工作了6年的北京,來到了陌生的上海,雖然明年我可能就會回到北京,在上海這個繁華的城市,我又體會到了一種和京城碼農感覺一樣的,技術氛圍和文化;最后必須一提的,2012年,我結婚了,并喜得一龍子,在這篇總結里,衷心的對我老婆說一聲:“老婆,謝謝你,我愛你。”
? ? ? ? 接下來,該進入這篇文章的正題了, 就是簡單地談談,我這兩年,主要做的東西——高性能分布式計算與存儲系統。
? ? ? ? 這個系統看名字十分牛比,所涉足的目前互聯網最領先的技術領域。具體有什么用途? 在我之前供職的公司,它主要是作為中間層,給網站頁面提供緩存服務的,并且,它對付的難題,是大數據、海量數據,相信,每一個日PV超過千萬級的網站,都必須會有類似的系統存在,如果,你曾經看過,博客園里的《淘寶技術發展》等類似文章,就一定不會對我接來將要提到的許多概念和術語感到陌生。對于這樣大流量,需要處理大數據的網站而言,由Web的邏輯直接調用管理數據存儲,是非常不科學的,實際上也是不可能的,大數據、高并發的對數據庫進行讀寫,通常數據庫都會掛掉,從而使網站也掛掉,必須要在Web和數據庫之間,通過技術手段實現一種“轉換”或“控制”,或“均衡”或“過渡”,我不知道這樣用詞是否正確,你只要明白其中的意思就好了。這樣的技術手段有許多,所實現的東東也有許多,我們用到的,就是被稱為“中間層”的一個邏輯層,在這個層,將數據庫的海量數據抓出來,做成緩存,運行在服務器的內存中,同理,當有新的數據到來,也先做成緩存,再想辦法,持久化到數據庫中,就是這樣簡單的思路,但實現起來,從零到有,可以說難如登天,但是,任何事物,都是在曲折中,不斷發展前進的,這是中學我們就學過的哲學理論。這個系統,就被我們稱簡為“緩存系統”,它最大的好處,就是砍掉了每天上千萬次的數據庫讀寫操作,取代而之的,是讀取服務器中提供緩存服務的進程所控制的內存,所以你知道,這里面節省了多少的資源申請、競爭、I/O……當然,后面你也會發現,它會帶來許多新的問題,最顯著的問題,就是數據的同步和一致性,后面我會講到。
? ? ? ??現在,讓我們先看看, 這個系統,發展到我離開它的時候,長什么樣子?(由于涉及到商業機密,具體的技術不能提供)
?
? ? ? ? 就是這樣的一張架構圖,代表著可以處理每日上千萬PV的系統,涉及到許多的技術,讓我們一個部分一個部分解讀它。
? ? ? ? 首先,從當我有一個web請求到達時,將會發生怎樣的事情說起。比如,我是一個用戶,我在這個網站登陸,我的“個人”頁面上,將會加載許許多多的東西,有許許多多的圖片、文字、消息等,我們舉其中一個例子,我將要得到我的好友列表——friend list。通過常識可以知道,這個friend list,不是隨機的、臨時的,而肯定是一個(一組)持久化存儲于數據庫里的數據,我們就是一個用戶請求得到他的friend list說起,來解讀這張架構圖。如果我的網站流量很小,每天不超過10萬PV,峰值可能就幾百個上千個用戶,同時請求他們的friend list,那么,現今任何一種語言配上任何一種數據庫的搭配,只要稍做處理,都可以很好的完成這個工作——從數據庫中,讀出該用戶的friend list,然后訪回給web,如果用戶對好友列表作了任何修改,web馬上將修改內容寫入數據庫,形成新的friend list。然而,當訪問流量持續提升,達到千萬級、甚至億級PV的時候,剛才說的方法就不可行了。因為,同時可能有幾十萬甚至上百萬用戶,通過web請求從數據庫中讀(如果寫將會更糟糕)上百條萬數據,數據庫將不堪重負,形成巨大的延遲甚至掛掉。通過上面的系統,來解決這樣的問題。
? ? ? ??現在,我們要設計和研發的上述系統,當一個web頁面提交一個獲取friend list的請求后,它首先將根據一定的規則,通過負載均衡,然后到達相應的master節點。上面我們提到的是DNS負載均衡,這得眾多負載均衡技術中的一種方法。也就是說,我有許許多多的master節點(上圖的scalabe表明,我是可擴展的,只要有條件,可隨意橫向擴展節點,以提高速度、容災、容量等指標),每個master節點的IP地址(域名)當然不一樣,通過DNS負載均衡,合理地把該請求,送到相對“空閑”的master節點服務器。現在解釋一下master節點服務器和slave節點服務器的功能:slave節點,主要用于"Running services",即,實際處理請求的緩存服務進程,通常運行在slave節點上;master節點,主要用于分發通過負載均衡的請求(當然,master節點上也可以運行一些“緩存服務進程”,即并發流量不高、較輔助的一些服務),找到用于處理實際請求的合適的slave節點,將該請求交給它處理,再次實現了一道“負載均衡”,同時,需要分布式計算的內容,將可能同時分發到幾個slave節點,之后再對結果進行合并返回(Map-Reduce原理)。
? ? ? ? 好了,現在我們已經知道,一個friend list請求已經通過DNS負載均衡、通過master節點進行分配,到達了相應的slave節點上。我們還知道,所說的“緩存”?,正是slave節點中所運行的services進程中所管理的內存,提供同樣功能的service可能會有很多份,同時運行在不同slave節點上,以提供高并發和分布式計算的功能。例如,獲得friend list就是這樣的service,因為這個功能太常用了,所以,在我們的系統中,這樣的服務可能同時提供5份、10份甚至更多,那么我這個獲取friend list的請求,究竟被分配到哪個slave節點上的service處理呢?這正是剛才提到的master節點來完成這一工作。再比如,我現在需要獲取“二度關系”的列表(關于六度人脈理論,可google),所謂“二度關系”,就是好友的好友,那么我要取這樣的列表,即friend's every friend list,這樣的請求,將會把取每個friend list分配(Map)到不同slave節點上去做(根據一定的規則),然后再進行合并(Reduce)(當然,熟悉算法的同學可能已經發現,這樣去獲取請求,非常的笨拙,有沒有更好的方法呢?當然有!因為好友的好友,其實就是好友的friend list與我和好友的共同好友common friend list的“差集”,對嗎?,所以我不用去取好友的每個好友的friend list,而只用取2次就可以通過計算完成請求,這又節省了多少資源呢?假如我有100個好友,1000個,10000萬個?會節省多少次計算呢?這也證明,一個良好的算法,對改善程序性能,有多么大的幫助!)
? ? ? ? 好,我們繼續。現在,我的獲取friend list的請求,已經在被某個slave節點中的負責這一功能的service進程處理,它將根據一定規則,給出兩種可能的處理方式:
? ? ? ? 1、 我這個用戶非常活躍,經常登陸網站(一定的規則,認為緩存未到過期時間),且我這個slave節點自上次“重建緩存”(即重新從數據庫中讀取數據,建立緩存,后面會談)后,沒有發生過down機重啟行為(又一定的規則),我也沒有收到過master節點發送過來要求更新緩存(即從數據庫中比較數據并更新)的Notification(通知),或是在一定條件下我這個slave節點對它掌握的緩存數據版本(版本管理系統原理,思考一下svn的工作原理)和數據庫進行了一次比較(注意,比較數據版本可認為只是一個int值,且是原子操作,這和比較整條數據是否一致在性能上有天壤之別)發現是最新的數據版本,那么,我這個slave節點將直接返回緩存數據,而沒有任何數據庫讀操作,也就是說,我這一次獲取friend list的請求,得到的是緩存數據,當然,這個緩存數據肯定是最新的、正確的、和數據庫中的持久化數據是一致的,后面會提到怎樣來盡量保證這一點;
? ? ? ? 2、第1點中的“一定規則”不滿足時,即我這個slave節點的緩存和數據庫中的數據可能存在不一致的沒有其它辦法,我必須從數據庫中讀取數據,更新緩存,然后再返回。但同時注意,slave節點中的service服務進程,將認為此用戶現在活躍,可能還會請求一些相關、類似的數據(如馬上可能進行添加好友、刪除好友等操作),所以去數據庫讀取數據的時候,將不會只讀friend list,可能與用戶有關的其它一部分數據,會被同時讀取并更新緩存,如果負責這一部分數據的緩存服務并不是當前的service進程,或在其它slave節點,或同時還有幾份service進程在工作,那么slave節點將提交“更新緩存”請求給master節點,通過master節點發出Notification給相關slave節點的相關service進程,從而,盡可能使每一次讀取數據庫的作用最大化,而如果稍后用戶果然進行了我們猜測的行為(可認為cache命中),結果將同第1點,直接通過緩存返回數據而且保證了數據的正確和一致性。
? ? ? ? 好了,剛剛提到的都是“讀操作”,相比“寫操作”, 其數據一致性更容易保證,之后我們將講述“寫操作”的工作原理。現在,讓我們先跳過這一部分,繼續看架構圖。slave節點之后,就是實際的數據存儲了,使用了MySQL、Redis,MySQL主從之間的協同是DBA的工作,不在此篇討論,Redis主要存儲K-V鍵值對數據,比如用戶id和用戶昵稱,是最常用的K-V對之一,通過Redis進行存儲,再結合上述的工作過程,可保證這個系統的高性能。而架構圖最右下角的Hadoop與MongoDB,是可選的MySQL替代方案,其實,正是未來的主要發展方向。如果slave節點中的service服務進程與Hadoop良好結合,系統的性能將更上一層樓。順便說一句,master、slave節點都是由C++開發的。Why C++?可參考酷殼上的一篇文章《C++ Performance per $》。
在上面,我們主要討論了,這個系統怎樣處理大數據的“讀”操作,當然還有一些細節沒有講述。下篇,我們將主要講述,“寫”操作是如何被處理的。我們都知道,如果只有“讀”,那幾乎是不用做任何數據同步的,也不會有并發安全問題,之所以,會產生這樣那樣的問題,會導致緩存和數據庫的數據不一致,其實根源就在于“寫”操作的存在。下面,讓我們看一看,當系統需要寫一條數據的時候,又會發生怎樣的事情?
? ? ? ?同樣,我們還是以friend list為例。現在,我登陸了這個網站,獲取了friend list之后,我添加了一個好友,那么,我的friend list必定要做修改和更新(當然,添加好友這一個動作肯定不會只有修改更新friend list這一個請求,但我們以此為例,其它請求也是類似處理),那么,這個要求修改和更新friend list的請求,和獲取friend list請求類似,在被slave節點中的服務進程處理之前,也是先通過DNS負載均衡,被分配到合適的master節點,再由master節點,分配到合適的相對空閑的負責這一功能的slave點上。現在假設,前面我們已經講過,獲取friend list這樣的請求,非常常用,所以,提供這一供能的服務進程將會有多份,比如,有10份,服務進程編號為0~9,同時運行在10個(也可能僅運行在1個~9個slave節點上!)slave節點上,具體分配請求的時候,選擇哪一個slave節點和哪一份服務進程呢?這當然有許多種規則去影響分配策略,我們就舉一個最簡單的例子,采用用戶id對10取模,得到0~9的結果,即是所選擇的服務進程編號,假設我的用戶id尾號為9,那么我這個請求,只會被分配到編號為9的服務進程去處理(當然,所有用戶id尾號為9的都是如此),編號為9的服務進程,也只負責為數據庫中用戶id尾號為9的那些數據做緩存,而用戶id尾號為0~8的緩存則由其它服務進程來處理。如果所需的請求是以剛才這種方式工作的,那么現在我要求修改和更新friend list的這個請求,將只會被分配到服務進程編號為9的進程來處理,我們稱之為“單點模型”(也就是說,同一條數據只會有一份可用緩存,備份節點上的的不算),你可能已經猜到了,還會有“多點模型”——即同時有好幾個服務進程都會負責同樣的緩存數據,這是更復雜的情況,我們稍后再討論。
?
? ? ? ?現在,我們接著說“單點模型” 。這個修改和更新friend list的請求到了編號為9的服務進程中后,如何被處理呢?緩存肯定先要被處理,之后才考慮緩存去和數據庫同步一致,這大家都知道(否則還要這個系統干嘛?)大家還知道,只要涉及到并發的讀寫,就肯定存在并發沖突和安全問題,這又如何解決呢?我們有兩種方式,來進行讀寫同步。
1、 第一種方式,就是傳統的,加鎖方式——通過加鎖,可以有效地保證緩存中數據的同步和正確,但缺點也非常明顯,當服務進程中同時存在讀寫操作的線程時,將會存在嚴重的鎖競爭,進而重新形成性能瓶頸。好在,通常使用這種方式處理的業務需求,都經過上述的一些負載均衡、分流措施之后,鎖的粒度不會太大,還是上述例子,我最多也就鎖住了所有用戶id尾號為9的這部分緩存數據更新,其它90%的用戶則不受影響。再具體些,鎖住的緩存數據可以更小,甚至僅鎖住我這個用戶的緩存數據,那么,鎖產生的性能瓶頸影響就會更小了(為什么鎖的粒度不可能小到總是直接鎖住每個用戶的緩存數據呢?答案很簡單,你不可能有那么多的鎖同時在工作,數據庫也不可能為每個用戶建一張表),即鎖的粒度是需要平衡和調整的。好,現在繼續,我要求修改和更新friend list的請求,已經被服務進程中的寫進程在處理,它將會申請獲得對這部分緩存數據的鎖,然后進行寫操作,之后釋放鎖,傳統的鎖工作流程。在這期間,讀操作將被阻塞等待,可想而知,如果鎖的粒度很大,將有多少讀操作處于阻塞等待狀態,那么該系統的高性能就無從談起了。
2、有沒有更好的方法呢?當然有,這就是無鎖的工作方式。首先,我們的網站,是一個讀操作遠大于寫操作的網站(如果需求相反,可能處理的方式也就相反了),也就是說,大多數時候,讀操作不應該被寫操作阻塞,應優先保證讀操作,如果產生了寫操作,再想辦法使讀操作“更新”一次,進而使得讀寫同步。這樣的工作方式,其實很像版本管理工具,如svn的工作原理:即,每個人,都可以讀,不會因為有人在進行寫,使得讀被阻塞;當我讀到數據后,由于有人寫,可能已經不是最新的數據了,svn在你嘗試提交寫的時候,進行判斷,如果版本不一致,則重新讀,合并,再寫。我們的系統也是按類似的方式工作的:即每個線程,都可以讀,但讀之前先比較一下版本號,然后讀緩存數據,讀完之后準備返回給Web時,再次比較版本號,如果發現版本已經被更新(當然你讀的數據頂多是“老”數據,但不至于是錯誤的數據,Why?還是參考svn,這是"Copy and Write"原理,即我寫的那一份數據,是copy出來寫的,寫完再copy回去,不會在你讀出的那一份上寫),則必須重新讀,直到讀到的緩存數據版本號是最新的。前面已經說過,比較和更新版本號,可認為是原子操作(比如,利用CAS操作可以很好的完成這一點,關于CAS操作,可以google到一大堆東西),所以,整個處理流程就實現了無鎖化,這樣,在大數據高并發的時候,沒有鎖瓶頸產生。然而,你可能已經發現其中的一些問題,最顯著的問題,就是可能多讀不止一次數據,如果讀的數據較多較大,又要產生性能瓶頸了(苦!沒有辦法),并且可能產生延遲,造成差的用戶體驗。那么,又如何來解決這些問題呢?其實,我們是根據實際的業務需求來做權衡的,如果,所要求的請求,允許一定的延遲存在,實時性要求不是最高,比如,我看我好友發的動態,這樣的緩存數據,并不要求實時性非常高,稍稍有延遲是允許的,你可以想象一下,如果你的好友發了一個狀態,你完全沒有必要,其實也不可能在他點擊“發布”之后,你的動態就得到了更新,其實只要在一小段時間內(比如10秒?)你的動態更新了,看到了他新發布了狀態,就足夠了。假設是這樣的請求,且如果我采用第1種加鎖的方式所產生的性能瓶頸更大,那么,將采用這種無鎖的工作方式,即當讀寫有沖突時候,讀操作重新讀所產生的開銷或延遲,是可以忍受的。比較幸運的是,同時有多個讀寫線程操作同一條緩存數據導致多次的重讀行為,其實并不是總是發生,也就是說,我們系統的大數據并發,主要在多個進程線程同時讀不同條的數據這一業務需求上,這也很容易理解,每個用戶登陸,都是讀他們各自的friend list(不同條數據,且在不同的slave節點上),只不過,這些請求是并發的(如果不進行分布式處理會沖垮服務器或數據庫),但是并不總是會,許多用戶都要同時讀某一條friend list同時我還在更新該條friend list導致多次無效的重讀行為。
? ? ? ?我們繼續上面的friend list。現在,我的friend list已經在緩存中被修改和更新了。無論是采用方式1還是方式2進行,在這期間,如果恰好有其它線程來讀我的friend list,那么總之會受到影響,如果是方式1,該請求將等待寫完畢;而如果是方式2,該請求將讀2次(也可能更多,但實在不常見)。這樣的處理方式,應該不是最好的,但前面已經說過了,我們的系統,主要解決:大流量高并發地讀寫多條數據,而不是一條。接下來,該考慮和數據庫同步的事情了。
? ? ? ?恩,剛才說了那么多,你有沒有發現,經過我修改和更新friend list后,緩存中的數據和數據庫不一致了呢?顯然,數據庫中的數據,已經過期了,需要對其更新。現在,slave節點中的編號為9的服務進程,更新完了自己的緩存數據后(修改更新我的friend list),將“嘗試”向數據庫更新。注意,用詞“嘗試”表明該請求不一定會被馬上得到滿足。其實,服務進程對數據庫的更新,是批量進行的,可認為是一個TaskContainer(任務容器),每間隔一段時間,或得到一定的任務數量,則成批地向數據庫進行更新操作,而不是每過來一個請求,更新緩存后就更新一次數據庫(你現在知道了這樣做又節省了多少次數據庫操作!)。那么,為什么可以這樣做呢?因為,我們已經有了緩存,緩存就是我們的保障,在“單點模型”下,緩存更新后,任何讀緩存的操作,都只會讀到該緩存,不需要經過數據庫,參看上篇中提到過此問題。所以,數據庫的寫更新操作,可以“聚集”,可以一定延遲之后,再進行處理。你會發現,既然如此,我就可以對這些操作進行合并、優化,比如,兩個寫請求都是操作同一張表,那么可以合并成一條,沒錯,這其實已經涉及到SQL優化的領域了。當然,你也會發現,現在緩存中的新數據還沒有進行持久化,如果在這個時間點,slave節點機器down掉了,那么,這部分數據就丟失了!所以,這個延遲時間并不會太長,通常10秒已經足夠了。即,每10秒,整理一下我這個服務進程中已經更新緩存未更新DB的請求,然后統一處理,如果更杞人憂天(雖然考慮數據安全性決不能說是杞人憂天,但你要明白,其實任何實時服務器發生down行為總是會有數據丟失的,只是或多或少),則延遲間隔可以更短一些,則DB壓力更大一些,再次需要進行實際的考量和權衡。至此,我的friend list修改和更新請求,就全部完成了,雖然,可能在幾十秒之前,就已經在頁面上看到了變化(通過緩存返回的數據)。
? ? ? ?那么,讀和寫都已經講述了,還有其它問題嗎?問題還不少。剛才討論的,都是“單點模型”。
? ? ? ?即,每一條數據庫中的數據,都只有一份緩存數據與之對應。然而,實際上,“多點模型”是必須存在的,而且是更強大的處理方式,也帶來同步和一致性的更多難題,即每一條數據,可能有多份緩存與之對應。即多個slave節點上的服務進程中,都有一份對應DB中相同數據的緩存,這個時候,又將如何同步呢?我們解決的方式,叫做“最終一致性”原則,關于最終一致性模型,又可以google到一大堆,特別要提出的是GoogleFS的多點一致性同步,就是通過“最終一致性”來解決的,通俗的講,就是同一條數據,同一時刻,只能被一個節點修改。假設,我現在的業務,是“多點模型”,比如,我的friend list,是多點模型,有多份緩存(雖然實際并不是這樣的),那么,我對friend list的修改和更新,將只會修改我被分配到的slave節點服務進程中的緩存,其它服務進程或slave節點的緩存,以及數據庫,將必須被同步更新,這是如何做到的呢?這又要用到上篇曾提到的Notification(通知服務),這個模塊雖然沒有在架構圖中出現,卻是這個系統中最核心的一種服務(當然,它也是多份的,呵呵),即,當一條數據是多點模型時,當某一個服務進程對其進行修改和更新后,將通過向master節點提交Notificaion并通知其它服務進程或其它slave節點,告知他們的緩存已經過期,需要進行更新,這個更新,可能由所進行修改更新的服務進程,發送緩存數據給其它進程或節點,也由可能等待DB更新之后,由其它節點從DB進行更新,從而間接保證多點一致性。等等,剛才不是說,通常10秒才批量更新DB嗎?那是因為在單點模型下,這樣做是合理的,但在多點模型下,雖然也是批理對數據庫進行更新,但這樣的延遲通常非常小,可認為即時對數據庫進行批量更新,然后,通過Notification通知所有有這一條數據的節點,更新他們的緩存。由此可見,多點模型,所可能產生的問題是不少的。那么,為什么要用多點模型呢?假設我有這樣的業務:大數據高并發的讀某一條數據,非常非常多的讀,但寫很少,比如一張XX門的熱門圖片,有很多很多的請求來自不同的用戶都需要這個條數據的緩存,多點模型即是完美的選擇。我許多slave節點上都有它的緩存,而很少更新,則可最大限度的享用到多點模型帶來的性能提升。
? ? ? ?還有一些問題,不得不說一下。就是down機和定期緩存更新的問題。先說宕機,很顯然,緩存是slave節點中的服務進程的內存,一旦節點宕機,緩存就丟失了,這時就需要前面我提到過的“重建緩存”,這通常是由master節點發出的,master節點負責監控各個slave節點(當然也可以是其它master節點)的運行狀況,如果發現某個slave節點宕機(沒有了“心跳”,如果你了解一些Hadoop,你會發現它也是這樣工作的),則在slave節點重新運行之后(可能進行了重啟),master節點將通知該slave節點,重建其所負責的數據的緩存,從哪重建,當然是從數據庫了,這需要一定的時間(在我們擁有百萬用戶之后,重建一個slave節點所負責的數據的緩存通常需要幾分鐘),那么,從宕機到slave節點重建緩存完畢這一段時間,服務由誰提供呢?顯然備份節點就出馬了。其實在單點模型下,如果考慮了備份節點,則其實所有的請求都是多點模型。只不過備份節點并不是總是會更新它的緩存,而是定期,或收到Notification時,才會進行更新。master節點在發現某個slave節點宕機后,可以馬上指向含有同樣數據的備份節點,保證緩存服務不中斷。那么,備份節點的緩存數據是否是最新的呢?有可能不是。雖然,通常每次對數據庫完成批量更新后,都會通知備份節點,去更新這些緩存,但還是有可能存在不一致的情況。所以,備份節點的工作方式,是特別的,即對于每次請求的緩存都采用Pull(拉)方式,如何Pull?前面提到的版本管理系統再次出馬,即每次讀之前,先比較版本,再讀,寫也是一樣的。所以,備份節點的性能,并不會很高,而且,通常需要同時負責幾個slave節點的數據的備份,所以,存在被沖垮的可能性,還需要slave節點盡快恢復,然后把服務工作重新還給它。
? ? ? ?再說定期緩存更新的問題。通常,所有的slave節點,都會被部署在夜深人靜的某個時候(如02:00~06:00),用戶很少的時候,定期進行緩存更新,以盡可能保證數據的同步和一致性,且第二天上午,大量請求到達時,基本都能從緩存返回最新數據。而備份節點,則可能每30分鐘,就進行一次緩存更新。咦?前面你不是說,備份節點上每次讀都要Pull,比較版本并更新緩存,才會返回嗎?是的,那為什么還要定期更新呢?答案非常簡單,因為如果大部分緩存都是最新的數據,只比較版本而沒有實際的更新操作,所消耗的性能很小很小,所以定期更新,在發生slave節點宕機轉由備份節點工作的時候,有很大的幫助。
? ? ? ?最后,再說一下Push(推送)方式,即,每次有數據改動,都強制去更新所有緩存。這種方式很消耗性能,但更能保證實時性。而通常我們使用的,都是Pull(拉)方式,即無論是定期更新緩存,還是收到Notification(雖然收通知是被“推”了一把)后更新緩存,其實都是拉,把新的數據拉過來,就好了。在實際的系統中,兩種方式都有,還是那句話,看需求,再決定處理方式。
? ? ? ?好了,終于寫完了這篇總結,看到上篇發布后,得到了許許多多園友的鼓勵和支持,在此一并感謝!相信也有不少園友,已經看到了這個系統的許多不足和瓶頸,確實,它并不是一個完美的系統,還需要不斷進化。我寫出這篇文章,也是希望和大家多多交流,共同進步。馬上就是2013年了,希望自己能有更好的發展,也希望所有的朋友,都能更上一層樓!
? ? ? ? (全文完,Jone Zhang,張峻崇,2012.12.28)?
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的高性能分布式计算与存储系统设计概要——暨2012年工作3年半总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Anychat音视频开发】apache
- 下一篇: Android Virtual Devi