可擴展Web架構與分佈式系統

 

英文原文:Scalable Web Architecture and Distributed Systems,翻譯:oschinaphp

開放源代碼已經成爲一些大型網站的基本原則。而在這些網站成長的過程當中,一些優秀的實踐經驗和規則也出如今他們的結構中。本文旨在介紹一些在大型網站結構設計的過程當中須要注意的關鍵問題以及實現目標的基礎工做。html

本文側重於介紹網絡系統,儘管一些準則在其餘分佈式系統中也是適用的。前端

1.1. web分佈式系統的設計原則

搭建和運營一個可伸縮的web站點或者應用程序意味着什麼?在原始層面上這僅僅是用戶經過互聯網鏈接到遠程資源-使系統變得可伸縮的部分是將資源、或者訪問的資源,分佈於多個服務器上。node

像生活中大多數事情同樣,當構建一個web服務時花時間提早作好計劃從長遠看來仍是頗有幫助的;瞭解一些注意事項和大網站背後的權衡原則能夠在建立小型網站時作出更明智的決定。如下是一些影響大規模web系統設計的關鍵原則:mysql

  • 可用性:對於不少公司來講一個網站的正常運行時間是很是關鍵的聲譽和功能,像一些大型的在線零售系統,即便一分鐘的宕機都有可能致使數千或者數百萬美圓的損失,所以設計系統的時時可用性和彈性的錯誤處理機制既是一個基本業務也是一個技術要求。 高可用分佈式系統須要仔細考慮關鍵組件的冗餘,分系統失敗後能快速修復,而且當問題出現時優雅型降級。
  • 性能:網站的性能正在變成大多數站點考慮的一個重要的方面,網站的速度影響正常使用和用戶的滿意度,一樣影響搜索的排名,這也是影響網站收益和保留用戶的一個因素。所以,建立一個快速響應和低延遲的系統是很是關鍵的。
  • 可靠性:一個系統須要具有可靠性,好比同一個數據的請求始終返回一樣的數據響應 。若是數據改變或者被更新,那麼一樣的數據將返回一個新的數據。用戶須要知道一些東西被寫入系統或者被存儲到系統後,系統會保持不變而且能夠在之後恢復到合適的位置。
  • 可伸縮性:當談到任何大型的分佈式系統時,規模大小隻是考慮的其中一個方面,一樣重要的是加強處理較大規模的負載性能所作的努力,這一般稱爲系統的可伸縮性。可伸縮性能夠表明系統不少不一樣的參數:額外流量的處理量,添加存儲容量的便意性,甚至事務的處理量。
  • 可管理性: 設計一個系統能夠方便操做是另外一個重要的考慮方面,系統的可管理性等同於操做的可伸縮性:維護和升級。可管理性須要考慮的事情是當問題發生時方便診斷和了解問題,易於升級和修改,以及系統能簡單性的操做(即,例行的操做有沒有失敗和異常?)
  • 成本: 成本是一個重要的因素。很明顯這包含硬件和軟件成本,但一樣重要須要考慮的其餘方面是部署和維護系統的成本。開發者構建系統花費的大量時間,運維部署時間,甚至培訓時間都須要考慮,成本是整體成本。

以上每一個原則都爲設計分佈式web架構提供了基礎決策。然而,他們也能彼此互斥,例如要實現某個目標就要以另外的做爲代價。一個基本的例子:選擇經過單純增長更多的服務器(可擴展性)來增長地址容量,是以可管理性(你必須操做增長的服務器)和成本(服務器的價格)爲代價的。git

當設計任何的web應用程序時,考慮這些關鍵原則都是很重要的,即便得認可一個設計可能要犧牲它們之中的一個或者多個。github

1.2. 基礎

當設計一個系統架構時,有一些東西是要考慮的:正確的部分是什麼,怎樣讓這些部分很好地融合在一塊兒,以及好的折中方法是什麼。一般在系統架構須要以前就爲它的可擴展性投資不是一個聰明的商業抉擇;然而,在設計上的深謀遠慮能在將來節省大量的時間和資源。web

這部分關注點是幾乎全部大型web應用程序中心的一些核心因素:服務、冗餘、劃分和錯誤處理。每個因素都包含了選擇和妥協,特別是上部分提到的設計原則。爲了詳細的解析這些,最好是用一個例子來開始。redis

實例:圖片託管應用

有時候你可能會在線上傳一張圖片。對於那些託管並負責分發大量圖片的網站來講,要搭建一個既節省成本又高效還能具有較低的延遲性(你能快速的獲圖片)的網站架構確實是一種挑戰。算法

咱們來假設一個系統,用戶能夠上傳他們的圖片到中心服務器,這些圖片又可以讓一些web連接或者API獲取這些圖片,就如同如今的Flickr或者Picasa。爲了簡化的須要,咱們假設應用程序分爲兩個主要的部分:一個是上傳圖片到服務器的能力(一般說的寫操做),另外一個是查詢一個圖片的能力。然而,咱們固然想上傳功能很高效,可是咱們更關心的是可以快速分發能力,也就是說當某我的請求一個圖片的時候(好比,一個web頁面或者其它應用程序請求圖片)可以快速的知足。這種分發能力很像web服務器或者CDN鏈接服務器(CDN服務器通常用來在多個位置存儲內容一邊這些內容可以從地理位置或者物理上更靠近訪問它的用戶,已達到高效訪問的目的)氣的做用。

系統其餘重要方面:

  • 對圖片存儲的數量沒有限制,因此存儲須要可擴展,在圖像數量方面須要考慮。
  • 圖片的下載和請求不須要低延遲。
  • 若是用戶上傳一個圖片,圖片應該都在那裏(圖片數據的可靠性)。
  • 系統應該容易管理(可管理性)。
  • 因爲圖片主機不會有高利潤的空間,因此係統須要具備成本效益。

Figure 1.1是一個簡化的功能圖。

可擴展Web架構與分佈式系統

Figure 1.1: 圖片主機應用的簡化架構圖

在這個圖片主機的例子裏,可碰見系統必需快速,它的數據存儲要可靠以及這些全部的屬性都應該高度的可擴展。創建這個應用程序的一個小版本不是很重要並且很容易部署在單一的服務器上;然而,這不是這節裏的感興趣部分。假設下咱們想建一個會增加到和Flickr痛讓規模的東西。

服務

當要考慮設計一個可擴展的系統時,爲功能解耦和考慮下系統每部分的服務都定義一個清晰的接口都是頗有幫助的。在實際中,在這種方式下的系統設計被成爲面向服務架構(SOA)。對於這類型的系統,每一個服務有本身獨立的方法上下文,以及使用抽象接口與上下文的外部任何東西進行交互,典型的是別的服務的公共API。

把一個系統解構爲一些列互補的服務,可以爲這些部分從別的部分的操做解耦。這樣的抽象幫助在這些服務服、它的基礎環境和服務的消費者之間創建清晰的關係。創建這種清晰的輪廓能幫助隔離問題,但也容許各模塊相對其它部分獨立擴展。這類面向服務設計系統是很是相似面向對象設計編程的。

在咱們的例子中,上傳和檢索圖像的請求都是由同一個服務器處理的;然而,由於系統須要具備伸縮性,有理由要將這兩個功能分解爲各由本身的服務進行處理。

快速轉發(Fast-forward)假定服務處於大量使用中;在這種狀況下就很容易看到,讀取圖像所花的時間中有多少是因爲受到了寫入操做的影響(由於這兩個功能將競爭使用它們共享的資源)。取決於所採用的體系結構,這種影響多是巨大的。即便上傳和下載的速度徹底相同(在絕大多數IP網絡中都不是這樣的狀況,大部分下載速度和上傳速度之比都至少設計爲3:1),文件讀取操做通常都是從高速緩存中進行的,而寫操做卻不得不進行最終的磁盤操做(並且可能要寫幾回才能達成最後的一致狀態)。即便全部內容都已在內存中,或者從磁盤(好比SSD磁盤)中進行讀取,數據庫寫入操做幾乎每每都要慢於讀取操做。(Pole Position是一個開源的DB基準測試工具,http://polepos.org/,測試結果參見 http://polepos.sourceforge.net/results/PolePositionClientServer.pdf

這種設計另外一個潛在的問題出在web服務器上,像Apache或者lighttpd一般都有一個可以維持的併發鏈接數上限(默認狀況下在500左右,不過能夠更高)和最高流量數,它們會很快被寫操做消耗掉。由於讀操做能夠異步進行,或者採用其它一些像gizp壓縮的性能優化或者塊傳輸編碼方式,web服務器能夠經過在多個請求服務之間切換來知足比最大鏈接數更多的請求(一臺Apache的最大鏈接數設置爲500,它每秒鐘提供近千次讀請求服務也是正常的)。寫操做則不一樣,它須要在上傳過程當中保持鏈接,因此大多數家庭網絡環境下,上傳一個1MB的文件可能須要超過1秒的時間,因此web服務器只能處理500個這樣併發寫操做請求。

可擴展Web架構與分佈式系統

對於這種瓶頸,一個好的規劃案例是將讀取和寫入圖片分離爲兩個獨立的服務,如圖Figure 1.2.所示。這讓咱們能夠單獨的擴展其中任意一個(由於有可能咱們讀操做比寫操做要頻繁不少),同時也有助於咱們理清每一個節點在作什麼。最後,這也避免了將來的憂慮,這使得故障診斷和查找問題更簡單,像慢讀問題。

這種方法的優勢是咱們可以單獨的解決各個模塊的問題-咱們不用擔憂寫入和檢索新圖片在同一個上下文環境中。這兩種服務仍然使用全球資料庫的圖片,可是它們可經過適當的服務接口自由優化它們本身的性能(好比,請求隊列,或者緩存熱點圖片-在這之上的優化)。從維護和成本角度來看,每一個服務按需進行獨立規模的規劃,這點很是有用,試想若是它們都組合混雜在一塊兒,其中一個無心間影響到了性能,另外的也會受影響。

固然,上面的例子在你使用兩個不一樣端點時能夠很好的工做(事實上,這很是相似於雲存儲和內容分發網絡)。雖然有不少方式來解決這樣的瓶頸,但每一個都有各自的取捨。

好比,Flickr經過分配用戶訪問不一樣的分片解決這類讀/寫問題,每個分片只能夠處理必定數量的用戶,隨着用戶的增長更多的分片被添加到集羣上(參看「Flickr縮影」的描述http://mysqldba.blogspot.com/2008/04/mysql-uc-2007-presentation-file.html)。在第一個例子中,能夠根據實際用途更簡單的規劃硬件資源(在整個系統中讀和寫的比例),然而,Flickr規劃是根據用戶基數(假定每一個用戶擁有相同的資源空間)。在前者中一個故障或者問題會致使整個系統功能的降低(好比,所有不能寫入文件了),然而Flickr一個分片的故障只會影響到相關的那部分用戶。在第一個例子中,更容易操做整個數據集-好比,在全部的圖像元數據上更新寫入服務用來包含新的元數據或者檢索-然而在Flickr架構上每個分片都須要執行更新或者檢索(或者須要建立個索引服務來覈對元數據-找出哪個纔是實際結果)。

冗餘(Redundancy)

爲了優雅的處理故障,web架構必須冗餘它的服務和數據。例如,單服務器只擁有單文件的話,文件丟失就意味這永遠丟失了。丟失數據是個很糟糕的事情,常見的方法是建立多個或者冗餘備份。

一樣的原則也適用於服務。若是應用有一個核心功能,確保它同時運行多個備份或者版本能夠安全的應對單點故障。

在系統中建立冗餘能夠消除單點故障,能夠在緊急時刻提供備用功能。例如,若是在一個產品中同時運行服務的兩個實例,當其中一個發生故障或者降級(degrade),系統能夠轉移(failover)到好的那個備份上。故障轉移(Failover)能夠自動執行或者人工手動干預。

服務冗餘的另外一個關鍵部分是建立無共享(shared-nothing)架構。採用這種架構,每一個接點均可以獨立的運做,沒有中心」大腦」管理狀態或者協調活動。這能夠大大提升可伸縮性(scalability)由於新的接點能夠隨時加入而不須要特殊的條件或者知識。並且更重要的是,系統沒有單點故障。因此能夠更好的應對故障。

例如,在咱們的圖片服務應用,全部的圖片應該都冗餘備份在另外的一個硬件上(理想的狀況下,在不一樣的地理位置,以防數據中心發生大災難,例如地震,火災),並且訪問圖片的服務(見Figure 1.3.)-包括全部潛在的服務請求-也應該冗餘。(負載均衡器是個很好的方法冗餘服務,可是下面的方法不只僅是負載均衡)

可擴展Web架構與分佈式系統

Figure 1.3: 使用冗餘的圖片存儲

分區

咱們可能碰見單一服務器沒法存放的龐大數據集。也可能遇到一個須要過多計算資源的操做,致使性能降低,急需增添容量。這些狀況下,你都有兩種選擇:橫向或縱向擴展。

縱向擴展意味着對單一服務器增添更多資源。對於一個很是龐大的數據集,這可能意味着爲單一服務器增長更多(或更大)的硬盤以存放整個數據集。而對於計算操做,這可能意味着將操做移到一個擁有更快的 CPU 或 更大的內存的服務器中。不管哪一種狀況,縱向擴展都是爲了使單個服務器可以本身處理更多的方法。

另外一方面,對於橫向擴展,則是增長更多的節點。例如龐大的數據集,你能夠用第二個服務器來存放部分數據;而對於計算操做,你能夠切割計算,或是經過額外的節點加載。想要充分的利用橫向擴展的優點,你應該之內在的系統構架設計原則來實現,不然的話,實現的方法將會變成繁瑣的修改和切分操做。

說道橫向分區,更常見的技術是將你的服務分區,或分片。分區能夠經過對每一個功能邏輯集的分割分配而來;能夠經過地域劃分,也能夠經過相似付費 vs. 未付費用戶來區分。這種方式的優點是能夠經過增添容量來運行服務或實現數據存儲。

以咱們的圖像服務器爲例,將曾經儲存在單一的文件服務器的圖片從新保存到多個文件服務器中是能夠實現的,每一個文件服務器都有本身唯一的圖片集。(見圖表1.4。)這種構架容許系統將圖片保存到某個文件服務器中,在服務器都即將存滿時,像增長硬盤同樣增長額外的服務器。這種設計須要一種可以將文件名和存放服務器綁定的命名規則。一個圖像的名稱多是映射所有服務器的完整散列方案的形式。或者可選的,每一個圖像都被分配給一個遞增的 ID,當用戶請求圖像時,圖像檢索服務只須要保存映射到每一個服務器的 ID 範圍(相似索引)就能夠了。

可擴展Web架構與分佈式系統

圖 1.4: 使用冗餘和分區實現的圖片存儲服務

固然,爲多個服務器分配數據或功能是充滿挑戰的。一個關鍵的問題就是數據局部性;對於分佈式系統,計算或操做的數據越相近,系統的性能越佳。所以,一個潛在的問題就是數據的存放遍及多個服務器,當須要一個數據時,它們並不在一塊兒,迫使服務器不得不爲從網絡中獲取數據而付出昂貴的性能代價。

另外一個潛在的問題是不一致性。當多個不一樣的服務讀取和寫入同一共享資源時,有可能會遭遇競爭狀態——某些數據應當被更新,但讀取操做剛好發生在更新以前——這種情形下,數據就是不一致的。例如圖像託管方案中可能出現的競爭狀態,一個客戶端發送請求,將其某標題爲「狗」的圖像更名爲」小傢伙「。而同時另外一個客戶端發送讀取此圖像的請求。第二個客戶端中顯示的標題是「狗」仍是「小傢伙」是不能明確的。

固然,對於分區還有一些障礙存在,但分區容許將問題——數據、負載、使用模式等——切割成能夠管理的數據塊。這將極大的提升可擴展性和可管理性,但並不是沒有風險。有不少能夠下降風險,處理故障的方法;不過篇幅有限,再也不贅述。如有興趣,可見於此文,獲取更多容錯和檢測的信息。

1.3. 構建高效和可伸縮的數據訪問模塊

在設計分佈式系統時一些核心問題已經考慮到,如今讓咱們來討論下比較困難的一部分:可伸縮的數據訪問。

對於大多數簡單的web應用程序,好比LAMP系統,相似於圖 Figure 1.5.

可擴展Web架構與分佈式系統

Figure 1.5: 簡單web應用程序

隨着它們的成長,主要發生了兩方面的變化:應用服務器和數據庫的擴展。在一個高度可伸縮的應用程序中,應用服務器一般最小化而且通常是shared-nothing架構(譯註:shared nothing architecture是一 種分佈式計算架構,這種架構中不存在集中存儲的狀態,整個系統中沒有資源競爭,這種架構具備很是強的擴張性,在web應用中普遍使用)方式的體現,這使得系統的應用服務器層水平可伸縮。因爲這種設計,數據庫服務器能夠支持更多的負載和服務;在這一層真正的擴展和性能改變開始發揮做用了。

剩下的章節主要集中於經過一些更經常使用的策略和方法提供快速的數據訪問來使這些類型服務變得更加迅捷。

可擴展Web架構與分佈式系統

Figure 1.6: Oversimplified web application

大多數系統簡化爲如圖 Figure 1.6所示,這是一個良好的開始。若是你有大量的數據,你想快捷的訪問,就像一堆糖果擺放在你辦公室抽屜的最上方。雖然過於簡化,前面的聲明暗示了兩個困難的問題:存儲的可伸縮性和數據的快速訪問。

爲了這一節內容,咱們假設你有很大的數據存儲空間(TB),而且你想讓用戶隨機訪問一小部分數據(查看Figure 1.7)。這相似於在圖像應用的例子裏在文件服務器定位一個圖片文件。

可擴展Web架構與分佈式系統

Figure 1.7: Accessing specific data

這很是具備挑戰性,由於它須要把數TB的數據加載到內存中;而且直接轉化爲磁盤的IO。要知道從磁盤讀取比從內存讀取慢不少倍-內存的訪問速度如同敏捷的查克·諾里斯(譯註:空手道冠軍),而磁盤的訪問速度就像笨重的卡車同樣。這個速度差別在大數據集上會增長更多;在實數順序讀取上內存訪問速度至少是磁盤的6倍,隨機讀取速度比磁盤快100,000倍(參考「大數據之殤」http://queue.acm.org/detail.cfm?id=1563874)。另外,即便使用惟一的ID,解決獲取少許數據存放位置的問題也是個艱鉅的任務。這就如同不用眼睛看在你的糖果存放點取出最後一塊Jolly Rancher口味的糖果同樣。

謝天謝地,有不少方式你可讓這樣的操做更簡單些;其中四個比較重要的是緩存,代理,索引和負載均衡。本章的剩餘部分將討論下如何使用每個概念來使數據訪問加快。

緩存

緩存利用局部訪問原則:最近請求的數據可能會再次被請求。它們幾乎被用於計算機的每一層:硬件,操做系統,web瀏覽器,web應用程序等等。緩存就像短時間存儲的內存:它有空間的限制,可是一般訪問速度比源數據源快而且包含了大多數最近訪問的條目。緩存能夠在架構的各個層級存在,可是經常在前端比較常見,在這裏一般須要在沒有下游層級的負擔下快速返回數據。

在咱們的API例子中如何使用緩存來快速訪問數據?在這種狀況下,有兩個地方你能夠插入緩存。一個操做是在你的請求層節點添加一個緩存,如圖 Figure 1.8.

可擴展Web架構與分佈式系統

Figure 1.8: Inserting a cache on your request layer node

直接在一個請求層節點配置一個緩存能夠在本地存儲相應數據。每次發送一個請求到服務,若是數據存在節點會快速的返回本地緩存的數據。若是數據不在緩存中,請求節點將在磁盤查找數據。請求層節點緩存能夠存放在內存和節點本地磁盤中(比網絡存儲快些)。

可擴展Web架構與分佈式系統

Figure 1.9: Multiple caches

當你擴展這些節點後會發生什麼呢?如圖Figure 1.9所示,若是請求層擴展爲多個節點,每一個主機仍然可能有本身的緩存。然而,若是你的負載均衡器隨機分配請求到節點,一樣的請求將指向不一樣的節點,從而增長了緩存的命中缺失率。有兩種選擇能夠解決這個問題:全局緩存和分佈式緩存。

全局緩存

全局緩存顧名思義:全部的節點使用同一個緩存空間,這涉及到添加一個服務器,或者某種文件存儲系統,速度比訪問源存儲和經過全部節點訪問要快些。每一個請求節點以一樣的方式查詢本地的一個緩存,這種緩存方案可能有點複雜,由於在客戶端和請求數量增長時它很容易被壓倒,可是在有些架構裏它仍是頗有用的(尤爲是那些專門的硬件來使全局緩存變得很是快,或者是固定數據集須要被緩存的)。

在描述圖中有兩種常見形式的緩存。在圖Figure 1.10中,當一個緩存響應沒有在緩存中找到時,緩存自身從底層存儲中查找出數據。在 Figure 1.11中,當在緩存中招不到數據時,請求節點會向底層去檢索數據。

可擴展Web架構與分佈式系統

Figure 1.10: Global cache where cache is responsible for retrieval

可擴展Web架構與分佈式系統

Figure 1.11: Global cache where request nodes are responsible for retrieval

大多數使用全局緩存的應用程序趨向於第一類,這類緩存能夠管理數據的讀取,防止客戶端大量的請求一樣的數據。然而,一些狀況下,第二類實現方式彷佛更有意義。好比,若是一個緩存被用於很是大的文件,一個低命中比的緩存將會致使緩衝區來填滿未命中的緩存;在這種狀況下,將使緩存中有一個大比例的總數據集。另外一個例子是架構設計中文件在緩存中存儲是靜態的而且不會被排除。(這多是由於應用程序要求周圍數據的延遲-某些片斷的數據可能須要在大數據集中很是快-在有些地方應用程序邏輯理清排除策略或者熱點 比緩存方案好使些)

分佈式緩存

在分佈式緩存(圖1.12)中,每一個節點都會緩存一部分數據。若是把冰箱看做食雜店的緩存的話,那麼分佈式緩存就象是把你的食物分別放到多個地方 —— 你的冰箱、櫃櫥以及便當盒 ——放到這些便於隨時取用的地方就無需一趟趟跑去食雜店了。緩存通常使用一個具備一致性的哈希函數進行分割,如此即可在某請求節點尋找某數據時,可以迅速知道要到分佈式緩存中的哪一個地方去找它,以肯定改數據是否從緩存中可得。在這種狀況下,每一個節點都有一個小型緩存,在直接到原數據所做處找數據以前就能夠向別的節點發出尋找數據的請求。由此可得,分佈式緩存的一個優點就是,僅僅經過向請求池中添加新的節點即可以擁有更多的緩存空間。

分佈式緩存的一個缺點是修復缺失的節點。一些分佈式緩存系統經過在不一樣節點作多個備份繞過了這個問題;然而,你能夠想象這個邏輯迅速變複雜了,尤爲是當你在請求層添加或者刪除節點時。即使是一個節點消失和部分緩存數據丟失了,咱們還能夠在源數據存儲地址獲取-所以這不必定是災難性的!

可擴展Web架構與分佈式系統

Figure 1.12: Distributed cache

緩存的偉大之處在於它們使咱們的訪問速度更快了(固然前提是正確使用),你選擇的方法要在更多請求下更快才行。然而,全部這些緩存的代價是必須有額外的存儲空間,一般在放在昂貴的內存中;歷來沒有嗟來之食。緩存讓事情處理起來更快,並且在高負載狀況下提供系統功能,不然將會使服務器出現降級。

有一個很流行的開源緩存項目Memcached (http://memcached.org/)(它能夠當作一個本地緩存,也能夠用做分佈式緩存);固然,還有一些其餘操做的支持(包括語言包和框架的一些特有設置)。

Memcached 被用做不少大型的web站點,儘管他很強大,但也只是簡單的內存key-value存儲方式,它優化了任意數據存儲和快速檢索(o(1))。

Facebook使用了多種不一樣的緩存來提升他們站點的性能(查看」Facebook caching and performance」)。在語言層面上(使用PHP內置函數調用)他們使用$GLOBALSand APC緩存,這有助於使中間函數調用和結果返回更快(大多數語言都有這樣的類庫用來提升web頁面的性能)。Facebook使用的全局緩存分佈在多個服務器上(查看 」Scaling memcached at Facebook」),這樣一個訪問緩存的函數調用能夠使用不少並行的請求在不一樣的Memcached 服務器上獲取存儲的數據。這使得他們在爲用戶分配數據空間時有了更高的性能和吞吐量,同時有一箇中央服務器作更新(這很是重要,由於當你運行上千服務器時,緩存失效和一致性將是一個大挑戰)。

如今讓咱們討論下當數據不在緩存中時該如何處理···

代理

簡單來講,代理服務器是一種處於客戶端和服務器中間的硬件或軟件,它從客戶端接收請求,並將它們轉交給服務器。代理通常用於過濾請求、記錄日誌或對請求進行轉換(增長/刪除頭部、加密/解密、壓縮,等等)。

可擴展Web架構與分佈式系統

圖1.13: 代理服務器

當須要協調來自多個服務器的請求時,代理服務器也十分有用,它容許咱們從整個系統的角度出發、對請求流量執行優化。壓縮轉發(collapsed forwarding)是利用代理加快訪問的其中一種方法,將多個相同或類似的請求壓縮在同一個請求中,而後將單個結果發送給各個客戶端。

假設,有幾個節點都但願請求同一份數據,並且它並不在緩存中。在這些請求通過代理時,代理能夠經過壓縮轉發技術將它們合併成爲一個請求,這樣一來,數據只須要從磁盤上讀取一次便可(見圖1.14)。這種技術也有一些缺點,因爲每一個請求都會有一些時延,有些請求會因爲等待與其它請求合併而有所延遲。無論怎麼樣,這種技術在高負載環境中是能夠幫助提高性能的,特別是在同一份數據被反覆訪問的狀況下。壓縮轉發有點相似緩存技術,只不過它並不對數據進行存儲,而是充當客戶端的代理人,對它們的請求進行某種程度的優化。

在一個LAN代理服務器中,客戶端不須要經過本身的IP鏈接到Internet,而代理會將請求相同內容的請求合併起來。這裏比較容易搞混,由於許多代理同時也充當緩存(這裏也確實是一個很適合放緩存的地方),但緩存卻不必定能當代理。

可擴展Web架構與分佈式系統

圖1.14: 經過代理來合併請求

另外一個使用代理的方式不僅是合併相同數據的請求,同時也能夠用來合併靠近存儲源(通常是磁盤)的數據請求。採用這種策略可讓請求最大化使用本地數據,這樣能夠減小請求的數據延遲。好比,一羣節點請求B部分信息:partB1,partB2等,咱們能夠設置代理來識別各個請求的空間區域,而後把它們合併爲一個請求並返回一個bigB,大大減小了讀取的數據來源(查看圖Figure 1.15)。當你隨機訪問上TB數據時這個請求時間上的差別就很是明顯了!代理在高負載狀況下,或者限制使用緩存時特別有用,由於它基本上能夠批量的把多個請求合併爲一個。

可擴展Web架構與分佈式系統

Figure 1.15: Using a proxy to collapse requests for data that is spatially close together

值得注意的是,代理和緩存能夠放到一塊兒使用,但一般最好把緩存放到代理的前面,放到前面的緣由和在參加者衆多的馬拉松比賽中最好讓跑得較快的選手在隊首起跑同樣。由於緩存從內存中提取數據,速度飛快,它並不介意存在對同一結果的多個請求。可是若是緩存位於代理服務器的另外一邊,那麼在每一個請求到達cache以前都會增長一段額外的時延,這就會影響性能。

若是你正想在系統中添加代理,那你能夠考慮的選項有不少;SquidVarnish都通過了實踐檢驗,普遍用於不少實際的web站點中。這些代理解決方案針對大部分client-server通訊提供了大量的優化措施。將兩者之中的某一個安裝爲web服務器層的反向代理(reverse proxy,下面負載均衡器一節中解釋)能夠大大提升web服務器的性能,減小處理來自客戶端的請求所需的工做量。

索引

使用索引快速訪問數據是個優化數據訪問性能公認的策略;可能咱們大多數人都是從數據庫瞭解到的索引。索引用增加的存儲空間佔用和更慢的寫(由於你必須寫和更新索引)來換取更快的讀取。

你能夠把這個概念應用到大數據集中就像應用在傳統的關係數據存儲。索引要關注的技巧是你必須仔細考慮用戶會怎樣訪問你的數據。若是數據集有不少TBs,可是每一個數據包(payload)很小(可能只有1KB),這時就必須用索引來優化數據訪問。在這麼大的數據集找到小的數據包是個頗有挑戰性的工做由於你不可能在合理的時間內遍歷全部數據。甚至,更有可能的是這麼大的數據集分佈在幾個(甚至不少個)物理設備上-這意味着你要用些方法找到指望數據的正確物理位置。索引是最適合的方法作這種事情。

可擴展Web架構與分佈式系統

Figure 1.16: Indexes

索引能夠做爲內容的一個表格-表格的每一項指明你的數據存儲的位置。例如,若是你正在查找B的第二部分數據-你如何知道去哪裏找?若是你有個根據數據類型(數據A,B,C)排序的索引,索引會告訴你數據B的起點位置。而後你就能夠跳轉(seek)到那個位置,讀取你想要的數據B的第二部分。 (See Figure 1.16.)

這些索引經常存儲在內存中,或者存儲在對於客戶端請求來講很是快速的本地位置(somewhere very local)。Berkeley DBs (BDBs)和樹狀數據結構經常按順序存儲數據,很是理想用來存儲索引。

經常索引有不少層,看成數據地圖,把你從一個地方指向另一個地方,一直到你的獲得你想要的那塊數據。(See Figure 1.17.)

可擴展Web架構與分佈式系統

Figure 1.17: Many layers of indexes

索引也能夠用來建立一樣數據的多個不一樣視圖(views)。對於大數據集來講,這是個很棒的方法來定義不一樣的過濾器(filter)和類別(sort),而不用建立多個額外的數據拷貝。

例如,想象一下,圖片存儲系統開始實際上存儲的是書的每一頁的圖像,並且服務容許客戶查詢這些圖片中的文字,搜索每一個主題的全部書的內容,就像搜索引擎容許你搜索HTML內容同樣。在這種狀況下,全部的書的圖片佔用了不少不少服務器存儲,查找其中的一頁給用戶顯示有點難度。首先,用來查詢任意詞或者詞數組(tuples)的倒排索引(inverse indexes)須要很容易的訪問到;而後,導航到那本書的確切頁面和位置並獲取準確的圖片做爲返回結果,也有點挑戰性。因此,這種境況下,倒排索引應該映射到每一個位置(例如書B),而後B要包含一個索引每一個部分全部單詞,位置和出現次數的索引。

能夠表示上圖Index1的一個倒排索引,可能看起來像下面的樣子-每一個詞或者詞數組對應一個包含他們的書。

Word(s) Book(s)
being awesome Book B, Book C, Book D
always Book C, Book F
believe Book B

這個中間索引可能看起來像上面的樣子,可是可能只包含詞,位置和書B的信息。這種嵌套的索引架構要使每一個子索引佔用足夠小的空間,以防全部的這些信息必須保存在一個大的倒排索引中。

這是大型系統的關鍵點,由於即便壓縮,這些索引也太大,太昂貴(expensive)而難以存儲。在這個系統,若是咱們假設咱們世界上的不少書-100,000,000 (see Inside Google Books blog post)-每一個書只有10頁(只是爲了下面好計算),每頁有250個詞,那就是2500億(250 billion)個詞。若是咱們假設每一個詞有5個字符,每一個字符佔用8位(或者1個字節,即便某些字符要用2個字節),因此每一個詞佔用5個字節,那麼每一個詞即便只包含一次,這個索引也要佔用超過1000GB存儲空間。那麼,你能夠明白建立包含不少其餘信息-詞組,數據位置和出現次數-的索引,存儲空間增加多快了吧。

建立這些中間索引和用更小分段表示數據,使的大數據問題能夠獲得解決。數據能夠分散到多個服務器,訪問仍然很快。索引是信息檢索(information retrieval)的奠定石,是現代搜索引擎的基礎。固然,咱們這段只是淺顯的介紹,還有其餘不少深刻研究沒有涉及-例如如何使索引更快,更小,包含更多信息(例如關聯(relevancy)),和無縫的更新(在競爭條件下(race conditions),有一些管理性難題;在海量添加或者修改數據的更新中,尤爲還涉及到關聯(relevancy)和得分(scoring),也有一些難題)。

快速簡便的查找到數據是很重要的;索引是能夠達到這個目的有效簡單工具。

負載均衡器

最後還要講講全部分佈式系統中另外一個比較關鍵的部分,負載均衡器。負載均衡器是各類體系結構中一個不可或缺的部分,由於它們擔負着將負載在處理服務請求的一組節點中進行分配的任務。這樣就可讓系統中的多個節點透明地服務於同一個功能(參見圖1.18)。它的主要目的就是要處理大量併發的鏈接並將這些鏈接分配給某個請求處理節點,從而可以使系統具備伸縮性,僅僅經過添加新節點便能處理更多的請求。

可擴展Web架構與分佈式系統

圖1.18: 負載均衡器

用於處理這些請求的算法有不少種,包括隨機選取節點、循環式選取,甚至能夠按照內存或CPU的利用率等等這樣特定的條件進行節點選取。負載均衡器能夠用軟件或硬件設備來實現。近來獲得普遍應用的一個開源的軟件負載均衡器叫作 HAProxy)。

在分佈式系統中,負載均衡器每每處於系統的最前端,這樣全部發來的請求才能進行相應的分發。在一些比較複雜的分佈式系統中,將一個請求分發給多個負載均衡器也是常事,如圖1.19所示。

可擴展Web架構與分佈式系統

圖1.19: 多重負載均衡器

和代理相似,有些負載均衡器還能夠基於請求的類型對不一樣的請求進行不一樣的處理(技術上講,這樣的叫作反向代理)。

負載均衡器面臨的一個難題是怎麼管理同用戶的session相關的數據。在電子商務網站中,若是你只有一個客戶端,那麼很容易就能夠把用戶放入購物車裏的東西保存起來,等他下次訪問訪問時購物車裏仍能看到那些東西(這很重要,由於當用戶回來發現仍然呆在購物車裏的產品時頗有可能就會買它)。然而,若是在一個session中將用戶分發到了某個節點,但該用戶下次訪問時卻分發到了另一個節點,這裏就有可能產生不一致性,由於新的節點可能就沒有保留下用戶購物車裏的東西。(要是你把6盒子子農夫山泉放到購物車裏了,可下次回來一看購物車空了,難道你不會發火嗎?) 解決該問題的一個方法是能夠使session具備保持性,讓同一用戶老是分發到同一個節點之上,但這樣一來就很難利用相似failover這樣的可靠性措施了。若是這樣的話,用戶的購物車裏的東西不會丟,但若是用戶保持的那個節點失效,就會出現一種特殊的狀況,購物車裏的東西不會丟這個假設不再成立了(雖然希望不要把這個假設寫到程序裏)。固然,這個問題還能夠用本章中講到的其它策略和工具來解決,好比服務以及許多並無講到的方法(象服務器緩存、cookie以及URL重寫)。

若是系統中只有不太多的節點,循環式(round robin)DNS系統這樣的方案也許更有意義,由於負載均衡器可能比較貴,並且還額外增長了一層不必的複雜性。固然,在比較大的系統中會有各類各樣的調度以及負載均衡算法,簡單點的有隨機選取或循環式選取,複雜點的能夠考慮上利用率以及處理能力這些因素。全部這些算法都是對瀏覽和請求進行分發,並能提供頗有用的可靠性工具,好比自動failover或者自動提出失效節點(好比節點失去響應)。然而,這些高級特性會讓問題診斷難以進行。例如,當系統載荷較大時,負載均衡器可能會移除慢速或者超時的節點(因爲節點要處理大量請求),但對其它節點而言,這麼作其實是加重了狀況的惡化程度。在這時進行大量的監測很是重要,由於系統整體流量和吞吐率可能看上去是在降低(由於節點處理的請求變少了),但個別節點卻愈來愈忙得不可開交。

負載均衡器是一種能讓你擴展系統能力的簡單易行的方式,和本文中所講的其它技術同樣,它在分佈式系統架構中起着基礎性的做用。負載均衡器還要提供一個比較關鍵的功能,它必需可以探測出節點的運行情況,好比,若是一個節點失去響應或處於過載狀態,負載均衡器能夠將其總處理請求的節點池中移除出去,還接着使用系統中冗餘的其它不一樣節點。

隊列

目前爲止咱們已經介紹了許多更快讀取數據的方法,但另外一個使數據層具伸縮性的重要部分是對寫的有效管理。當系統簡單的時候,只有最小的處理負載和很小的數據庫,寫的有多快能夠預知;然而,在更復雜的系統,寫可能須要幾乎沒法決定的長久時間。例如,數據可能必須寫到不一樣數據庫或索引中的幾個地方,或者系統可能正好處於高負載。這些狀況下,寫或者任何那一類任務,有可能須要很長的時間,追求性能和可用性須要在系統中建立異步;一個一般的作到那一點的辦法是經過隊列。

可擴展Web架構與分佈式系統

Figure 1.20: Synchronous request

設想一個系統,每一個客戶端都在發起一個遠程服務的任務請求。每個客戶端都向服務器發送它們的請求,服務器儘量快的完成這些任務,並分別返回結果給各個客戶端。在一個小型系統,一個服務器(或邏輯服務)能夠給傳入的客戶端請求提供迅速服務,就像它們來的同樣快,這種情形應該工做的很好。然而,當服務器收到了超過它所能處理數量的請求時,每一個客戶端在產生一個響應前,將被迫等待其餘客戶端的請求結束。這是一個同步請求的例子,示意在圖1.20。

這種同步的行爲會嚴重的下降客戶端性能;客戶端被迫等待,有效的執行零工做,直到它的請求被應答。添加額外的服務器承擔系統負載也不會解決這個問題;即便是有效的負載均衡,爲了最大化客戶端性能,保證平等的公平的分發工做也是極其困難的。並且,若是服務器處理請求不可及,或者失敗了,客戶端上行也會失敗。有效解決這個問題在於,須要在客戶端請求與實際的提供服務的被執行工做之間創建抽象。

可擴展Web架構與分佈式系統

圖 1.21:用隊列管理請求

進入隊列。一個隊列就像它聽起來那麼簡單:一個任務進入,被加入隊列而後工人們只要有能力去處理就會拿起下一個任務。(看圖1.21)這些任務多是表明了簡單的寫數據庫,或者一些複雜的事情,像爲一個文檔生成一個縮略預覽圖一類的。當一個客戶端提交一個任務請求到一個隊列,它們不再會被迫等待結果;它們只須要確認請求被正確的接收了。這個確認以後可能在客戶端請求的時候,做爲一個工做結果的參考。

隊列使客戶端能以異步的方式工做,提供了一個客戶端請求與其響應的戰略抽象。換句話說,在一個同步系統,沒有請求與響應的區別,所以它們不能被單獨的管理。在一個異步的系統,客戶端請求一個任務,服務端響應一個任務已收到的確認,而後客戶端能夠週期性的檢查任務的狀態,一旦它結束就請求結果。當客戶端等待一個異步的請求完成,它能夠自由執行其它工做,甚至異步請求其它的服務。後者是隊列與消息在分佈式系統如何成爲槓桿的例子。

隊列也對服務中斷和失敗提供了防禦。例如,建立一個高度強健的隊列,這個隊列可以從新嘗試因爲瞬間服務器故障而失敗的服務請求,是很是容易的事。相比直接暴露客戶端於間歇性服務中斷,須要複雜的並且常常矛盾的客戶端錯誤處理程序,用一個隊列去增強服務質量的擔保更爲可取。

隊列對管理任何大規模分佈式系統不一樣部分之間的分佈式通訊,是一個基礎,並且實現它們有許多的方法。有很多開源的隊列如 RabbitMQActiveMQBeanstalkD,可是有些也用像 Zookeeper的服務,或者甚至像Redis的數據存儲。

1.4. 結論

設計有效的系統來進行快速的大數據訪問是有趣的,同時有大量的好工具來幫助各類各樣的應用程序進行設計。 這文章只覆蓋了一些例子,僅僅是一些表面的東西,但將會愈來愈多–同時在這個領域裏必定會繼續有更多創新東西。

 

http://blog.jobbole.com/36575/

相關文章
相關標籤/搜索