可伸縮Web架構與分布式系統(1)

jopen 10年前發布 | 22K 次閱讀 架構 分布式/云計算/大數據

開源軟件近年來已變為構建一些大型網站的基礎組件。并且伴隨著網站的成長,圍繞著它們架構的最佳實踐和指導準則已經顯露。這篇文章旨在涉及一些在設 計大型網站時需要考慮的關鍵問題和一些為達到這些目標所使用的組件。本文主要關注于Web系統,然而其中的一些內容同樣適用于其他分布式系統。

1、Web分布式系統設計準則

構建和運行一個可伸縮的網站或應用來說究竟意味著什么?從一個基本的層面來看,就是將用戶和遠程資源通過互聯網連接起來——將其變得可伸縮的部分, 是指這些資源或者訪問這些資源是分布式的、貫穿于多個服務器。像很多生活中的事情一樣,在構建一個web服務時花費時間提前做好計劃將會幫助服務能夠長久 運行;理解一些大型網站背后的考量和權衡會在創建小型網站時幫助做出更明智的決定。下面是一些會影響到設計大型可伸縮性Web系統的關鍵準則:

可用性

一個網站的正常運行時間,對于很多公司的信譽和功能來說都是至關重要的。對于一些大型在線零售網站,甚至是幾分鐘的不可用都會導致數以千萬計美元的 收入損失,所以將他們的系統設計成不間斷可用和能夠彈性恢復既是一項基礎業務也是一個技術需求。分布式系統的高可用性需要仔細考慮關鍵組件冗余,快速恢復 部分有問題的系統,并且當出現問題時能夠做到優雅降級。

性能

性能已成為大多數網站一個很重要的考量指標。一個網站的速度會影響到使用和用戶滿意度,同樣也會影響到搜索引擎排名,將直接關系到網站收入和用戶保持力(黏性)。因此,關鍵之處就在于創建一個為快速響應、低延遲的系統。

可靠性

一個系統需要做到可靠,這樣才能使得對于(固定)數據的請求始終會返回同樣的數據。如果數據發生變化或者更新,那相同的請求應該返回新的數據。用戶需要知道,一旦一些數據被寫入、存儲到系統中,那么系統就會持久化(這些數據)并且能夠讓人信賴隨時能夠檢索。

可伸縮性

對于任何大型分布式系統,系統規模只是可伸縮性需要考慮的一個方面。同樣重要的是,增加容量能夠處理更大量的負載所需的工作,通常在系統可伸縮性方 面被提及到。可伸縮性會涉及到系統的很多不同的因素:系統額外還能夠處理多少流量,是否能夠輕易增加存儲容量,還能多處理多少事務。

可管理性

設計一個易于運維的系統是另一個重要考量點。系統的可管理型等同于操作的可伸縮性:維護和變更。可管理性需要考慮的有:當問題發生時能夠便于診斷和理解,便于進行變更和修改,并且系統易于操作。(比如系統是否能夠進行例行操作而不帶來失敗或者異常?)

成本

成本是一個重要因素。這明顯會包括硬件和軟件成本,但同樣還要考慮到一些其他方面來部署、運維系統,比如構建系統所需的開發時間,運行系統所需的運維工作量,甚至所需的培訓都要被考慮在內。成本是指擁有系統的總成本(原句是Cost is the total cost of ownership.)。

這些準則中的每一條都提供了在設計一個分布式web系統架構時作決定的基本原則。但是,他們也可能互相矛盾,比如達到某一目標是以犧牲另一個為代價 的。一個典型的例子:專注于系統容量時,選擇通過簡單增加更多機器(可伸縮性)的代價是(增加了)可管理性(你需要運維更多的服務器)和成本(更多服務器 價格)。當設計任何web應用時,這些關鍵準則都是需要考量的,即使不得不承認,一個設計可能會犧牲它們中的一個或更多。

2、基本原理

對于系統架構來說,有一些事情需要考慮:什么是正確的組件,這些組件如何協作,需要做哪些正確的權衡。在真正需要可伸縮性之前的投資通常并不是一個明智的商業提議,然而,一些設計中的遠見卓識將會在未來節省真正的時間和資源。

本節著眼于一些對于幾乎所有大型Web應用都非常核心的因素:服務,冗余,分期和失敗處理。每個因素均包含有選擇和妥協,特別是在上一節提到的準則上下文中。為了詳細解釋這些,最好的方式就是從一個例子開始。

舉例:圖片托管應用

你很可能已經在網上上傳過一張圖片。對于托管和傳送大量圖片的大型網站來說,將會在架構建設上遇到很多挑戰,比如成本效益、高可用性和低延遲性(能夠快速檢索)。

設想一個這樣的系統:用戶可以將他們的圖片上傳到一個中央服務器,并且圖片可以通過一個web鏈接或者API(應用程序接口)進行請求,就像 Flickr或者Picasa一樣。為了簡單起見,我們假定這個應用有兩個關鍵部分:能夠上傳(寫入)一張圖片到服務器,能夠查詢一張圖片。雖然我們希望 上傳能夠更快速,但我們最關心的是系統能夠快速分發用戶請求的圖片(比如圖片可以被請求用于一張網頁或是其他應用)。這些跟一個web服務器或者 CDN(內容分發網絡) edge server(CDN所使用的服務器,用于在很多位置存放內容,這樣內容在地理/物理上更接近用戶,起到更高性能的作用)所提供的功能非常類似。

系統其他重要的方面

  • 對于存儲的圖片數量沒有設限,所以就圖片數量而言,需要考慮存儲的可伸縮性。
  • 對于圖片的下載/請求需要做到低延遲。
  • 如果一個用戶上傳了一張圖片,那該圖片應該總是存在的。(圖片的數據可靠性)
  • 系統需要易于管理(可管理型)。
  • 由于圖片托管不會帶來很高的利潤,所以系統需要做到有成本效益的。

可伸縮Web架構與分布式系統

圖片1.1:圖片托管應用的簡化架構圖

在這個圖片托管例子中,系統必須做到(讓用戶)可感知到快速,存儲數據的可靠性和那些所有高可伸縮的特征。構建一個小型的托管于單臺服務器上的應用過于簡單,也沒有意義,對于本章來說也沒有樂趣所在。來假設下,我們想要構建出可以成長為像Flickr一樣的龐然大物。

服務

當考慮可伸縮系統的設計時,(服務)有助于各功能去耦并且通過一個清晰定義的接口思考系統的每個部分。在實踐中,這種方式的系統設計表明其擁有一個 面向服務的架構(SOA)。對于這些類型的系統,每個服務都有它們各自確切的功能上下文,并且和該上下文以外的任何交互均是與一個抽象的接口進行的,特別 是另一個服務的公有接口。

將一個系統拆解為一個互補的服務集合解耦了那些相互間的操作。這種抽象有助于建立服務間明確的關系、潛在的(運行)環境、服務的消費者。通過這些清晰的描繪有助于隔離問題,并且允許每個部分能夠相互獨立地進行擴展。這種面向服務的系統設計有點類似與面向對象編程。

在我們的例子中,所有上傳和獲取圖片的請求都是在同一服務器上處理,但是,如果系統想要達到可伸縮,那么將這兩個功能拆分成各自的服務是非常明智的。

快進下,假設這些服務被大量使用;這樣的場景將非常易于看到更久(原文此處是how longer writes will impact the time it takes to read the images)的寫操作會如何影響(系統)讀取圖片的時間(因為這兩個功能會競爭共享資源)。在這樣的架構下,這種影響是真實存在的。即使上傳和下載速度 是一樣的(對于大多數IP網絡來說不一定是,因為大多數都是設計成下載速度與上傳速度3:1的比例),文件通常直接從緩存中讀取,而寫入則最終必須到達磁 盤(在最終一致的場景中可能會被寫入多次)。即使所有東西都是從內存或者磁盤(比如SSD固態硬盤)讀取,數據庫的寫入操作總還是比讀取要慢(Pole Position, 一個開源的數據庫評測工具
http://polepos.org/ http://polepos.sourceforge.net/results/PolePositionClientServer.pdf)。

另一個潛在的設計問題是,一個像Apache或者lighttpd的web服務器,通常有一個它可以維持并發連接數的上線(默認大約在500左右, 但可以調得更高),并且在高流量下,寫操作將很快消耗完所有(連接資源)。由于讀操作可以異步進行,或者利用其它性能調優如gzip壓縮或者 chunked transfer encoding,web服務器可以轉換為更快服務讀操作、更快切換客戶端,從而比最大連接數每秒服務更多的請求(Apache最大連接數設置為500, 但一般都能每秒服務數千個請求)。寫操作,在另一方面,傾向于在上傳過程中維護一個打開狀態的連接,所有上傳一個1M大小的文件在大多數家庭網絡上將花費 超過1秒的視角,所以web服務器只能同時處理500個寫操作。

可伸縮Web架構與分布式系統

圖 1.2: 讀寫分離

將圖片的讀、寫操作拆分成各自的服務是一個應對這種瓶頸很好的解決方案,如圖1.2。這樣允許我們能夠獨立的擴展它們(我們通常會讀大于寫),而且有助于將每一點的進展情況看得更加清晰。最后,這樣可以分離未來的擔心,可以更簡單地解決像讀操作緩慢的問題,并做到可伸縮。

這種方法的好處在于我們能夠獨立(不影響其他)解決問題——我們不用擔心在同一上下文中寫入、讀取新的圖片。這兩者(服務)仍然影響著全部的圖片, 但均能通過service-appropriate方法優化它們的性能(比如讓請求排隊,或者緩存受歡迎的圖片——更多種方式請見下文)。從一個維護和成 本的視角出發,每個服務均能獨立、按需伸縮是非常好的,因為如果它們被組合、混合在一起,在上面討論的場景下,可能某一(服務)不經意間就會影響到其他 (服務)的性能。

當然,當你考慮著兩個不同點時,上面的例子能夠工作得很好(事實上,這跟一些云存儲提供商的實現方案和CDN很類似)。盡管還有很多方法來處理這些類型的瓶頸,但每個都有不同方面的權衡。

例如,Flickr通過將用戶分布在不同區域的方法來解決讀/寫問題,比如每個分區只處理一定數量的用戶,隨著用戶的增加,集群會更多的分區(參考 Flickr可伸縮報告,, http://mysqldba.blogspot.com/2008/04/mysql-uc-2007-presentation- file.html)。在第一個例子中,基于實際使用(整個系統的讀寫操作數量)可以更容易地伸縮硬件,然而Flickr是基于它的用戶(但強制假設用戶 的使用率均等,所以仍有額外的容量)。對于前者來說,停電或者一個服務的問題就會降低整個系統的功能性(比如沒人可以寫入文件),然而Flickr的一個 分區停電僅會影響到這個分區相應的用戶。第一個例子易于操作整個數據集,比如升級寫入服務來包含新的元數據或者搜索所有的圖片元數據,然而在Flickr 的架構下,每個分區均需要被更新或搜索(或者一個搜索服務需要能夠整理相關元數據——事實上他們確實這么做)。

對于這些系統來說沒有孰對孰錯,而是幫助我們回到本章開頭所說的準則,判斷系統需求(讀多還是寫多還是兩者都多,并發程度,跨數據集查詢,搜索,排序等),檢測不同的取舍,理解系統為什么會失敗并且有可靠的計劃來應對失敗的發生。

冗余

為了能夠優雅地處理失敗問題,Web架構必須做到服務和數據的冗余。比如,如果在單臺服務器上僅有一份文件,那么失去那臺服務器就意味著丟失那份文件。丟失數據很少是件好事情,而通常的解決方案是創建多個、冗余的備份。
該準則同樣適用于服務。如果應用有一個核心功能,那么通過確保多個拷貝(多個同類服務實例)或者版本同時運行能夠免于單點失敗的情況。

在一個系統中創建冗余能夠去除單點失敗,并提供一個備份或在必要的緊急時刻替換功能。例如,如果在生產環境有同一服務的兩個實例在運行,其中一個失敗或者降級了,系統可以(啟動)failover到那個健康狀態的服務。Failover可以自動發生或者需要人工干預。

服務冗余的另一個關鍵點在于創建一個非共享的架構(譯者理解為無狀態架構)。 通過這種架構,每個節點都能夠獨立操作,并且沒有中央“大腦”來管理狀態或者協調其他節點的活動。這對于可伸縮性非常有幫助,因為新的節點不需要特殊的條 件或知識就能加入(到集群)。但是,最重要的是在這些系統中不會存在單點失敗問題,所以它們能夠更加彈性地面對失敗。

例如,在我們的圖片服務應用,所有的圖片會在另一個地方的硬件中有冗余的備份(理想情況是在一個不同的地理位置,以防地震或者數據中心火災這類的災 難發生),而訪問圖片的服務同樣是冗余的,所有(服務)都可能會服務請求。(見圖1.3)(負載均衡器可以將其變為現實,詳情請見下文。)

可伸縮Web架構與分布式系統

圖1.3:圖片托管應用,帶有冗余特性

分區

單臺服務器可能沒法放下海量數據集。也可能是一個操作需要太多計算資源,消耗性能,使得有必要增加(系統)容量。無論是哪種情況,你都有兩種選擇:垂直擴展(scale vertically)或者水平擴展(scale horizontally)。

垂直擴展意味著在單臺服務器上增加更多的資源。所以對于大數據來說,這意味著增加更多(更大容量)的硬盤以便讓單臺服務器能夠容納整個數據集。對于 計算操作的場景,這意味著將計算任務交給一臺擁有更快CPU或者更多內存的大型服務器。對于每種場景,垂直擴展是通過自身(個體)能夠處理更多的方式來達 到目標的。

另一方面,對于水平擴展來說,就是增加更多的節點。對于大數據集,可能是用另一臺服務器來存儲部分數據集;而對于計算資源來說,則意味著將操作進行分解或者加載在一些額外的節點上。為了充分利用水平擴展的優勢,這(譯者認為此處指代的是系統支持水平擴展。垂直擴展對于應用來說無需修改,通常升級機器即可達到目的。而水平擴展就要求應用架構能夠支持這種方式的擴展,因為數據、服務都是分布式的,需要從軟件層面來支持這一特性,從而做到數據、服務的水平可擴展。)應該被天然地包含在系統架構設計準則里,否則想要通過修改、隔離上下文來達到這一點將會相當麻煩。

對于水平擴展來說,通常方法之一就是將你的服務打散、分區。分區可以是分布式的,這樣每個邏輯功能集都是分離的;分區可通過地理邊界來劃分,或者其他標準如付費/未付費用戶。這些設計的好處在于它們能夠使得服務或數據存儲易于增加容量。

在我們的圖片服務器例子中,可以將單臺存儲圖片的服務器替換為多臺文件服務器,每臺保存各自單獨的圖片集。(見圖1.4)這樣的架構使得系統能夠往 各臺文件服務器中存入圖片,當磁盤快滿時再增加額外的服務器。這種設計將需要一種命名機制,將圖片的文件名與所在服務器關聯起來。一個圖片的名字可以通過 服務器間一致性Hash機制來生成。或者另一種選擇是,可以分配給每張圖片一個增量ID,當一個客戶端請求一張圖片時,圖片檢索服務只需要維護每臺服務器 對應的ID區間即可(類似索引)。

可伸縮Web架構與分布式系統

圖1.4:圖片托管應用,加入冗余和分區特性

當然,將數據或功能分布在多臺服務器上會帶來很多挑戰。關鍵問題之一是數據局部性(data locality);在分布式系統里,數據離操作或者計算點越近,系統性能就越高。因此將數據分布在多臺服務器可能是有問題的,任何需要(數據)的時候都 可能不在本地,使得服務器必須通過網絡來獲取所需的信息。

另一個潛在問題是不一致性。當不同的服務在對同一塊共享資源進行讀、寫時,可能是另一個服務或者數據,就會存在競爭條件的機會——當一些數據將被更 新,但讀操作先于更新發生——這類場景下數據就會發生不一致。例如,在圖片托管這個場景下,競爭條件會發生在一個客戶端發出將小狗圖片標題由“Dog”更 新為“Gizmo”的請求,但同時另一個客戶端正在讀取該圖片這樣的情況下。在這樣的情況下,第二個客戶端就不清楚接收到的標題會是“Dog”還是 “Gizmo”。(此端譯者并不理解作者原意,因為無論是在分布式還是在單機環境下,都可能出現同時讀、寫的操作,返回結果取決于底層存儲對同時接收到的請求處理的調度,可能讀先于寫,反之亦然,故此處的例子用來解釋不一致是否并不恰當?

誠然,關于數據分區還存在很多阻礙,但分區通過數據、負載、用戶使用模式等使得每個問題分解成易處理的部分。這樣有助于可伸縮性和可管理型,但也不 是沒有風險的。有很多方法能夠用來降低風險、處理失敗問題;但為了簡化篇幅,本章就不覆蓋(這些方法)了。如果你有興趣想了解更多,可以看下我博客上發表 的關于容錯性和監控的相關文章。

原文鏈接: Aosabook   翻譯: 伯樂在線 - narutoying
譯文鏈接: http://blog.jobbole.com/58523/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!