可擴(kuò)展的Web架構(gòu)和分布式系統(tǒng)

woman-1807533.jpg

本文翻譯自The Architecture Of Open Source Applications第二卷第一章。

原文:Scalable Web Architecture and Distributed Systems

翻譯:nettee


開源軟件已經(jīng)成為構(gòu)建一些大型網(wǎng)站重要的基礎(chǔ)材料。在這些網(wǎng)站不斷發(fā)展的同時(shí),和架構(gòu)相關(guān)的最佳實(shí)踐與指導(dǎo)原則也隨之出現(xiàn)。這一章致力于考察設(shè)計(jì)大型網(wǎng)站時(shí)的一些關(guān)鍵問題,以及用來達(dá)成這些目標(biāo)的一些基礎(chǔ)材料。

這一章主要關(guān)注Web系統(tǒng),其中一些內(nèi)容同樣適用于其他的分布式系統(tǒng)。

1.1 Web分布式系統(tǒng)設(shè)計(jì)的原則

構(gòu)建和運(yùn)行一個(gè)可擴(kuò)展的網(wǎng)站或應(yīng)用究竟意味著什么?最原始的網(wǎng)站只是讓用戶能通過因特網(wǎng)連接到遠(yuǎn)程的資源——將資源(或?qū)Y源的訪問)分布在多個(gè)服務(wù)器上,便能使網(wǎng)站變得可擴(kuò)展。

和生活中的很多事情一樣,在構(gòu)建一個(gè)Web服務(wù)的時(shí)候花些時(shí)間提前計(jì)劃,會對長期有幫助。理解大型網(wǎng)站背后的一些考慮和權(quán)衡,會使你在建立小型網(wǎng)站時(shí)做出聰明的決定。下面是一些影響大型可擴(kuò)展Web系統(tǒng)設(shè)計(jì)的主要原則:

  • 可用性(Availability):網(wǎng)站的響應(yīng)時(shí)間對許多公司的名聲和正常運(yùn)作都非常的關(guān)鍵。對一些大型在線零售站點(diǎn)來說,即使是幾分鐘的不可訪問都會導(dǎo)致上萬元的收入損失,因此將系統(tǒng)設(shè)計(jì)得持續(xù)可訪問,并在失效后可恢復(fù),既是一個(gè)業(yè)務(wù)需求,也是一個(gè)技術(shù)需求。分布式系統(tǒng)的高可用性要求仔細(xì)考慮關(guān)鍵組件的冗余,部分系統(tǒng)失效后的恢復(fù),以及問題發(fā)生后降低影響。

  • 性能(Performance):網(wǎng)站性能是絕大多數(shù)網(wǎng)站重點(diǎn)考慮的。網(wǎng)站的速度不僅影響著用戶使用的滿意度,還影響著搜索引擎排名,與收入和顧客保留直接相關(guān)。因此,創(chuàng)建一個(gè)響應(yīng)迅速、延遲低的優(yōu)化系統(tǒng)非常重要。

  • 可靠性(Reliability):一個(gè)系統(tǒng)需要是可靠的,例如請求的數(shù)據(jù)會一直返回相同的結(jié)果,而在數(shù)據(jù)改變或更新后,同樣的請求應(yīng)當(dāng)返回新的數(shù)據(jù)。需要讓用戶知道,如果一些東西寫入或存入了系統(tǒng),會一直存留,在未來取回時(shí)仍然存在。

  • 可擴(kuò)展性(Scalability):對于大型分布式系統(tǒng)而言,系統(tǒng)大小只是需要考慮可擴(kuò)展性的一個(gè)方面。系統(tǒng)的可擴(kuò)展性通常是指應(yīng)對大量負(fù)載的能力,提高這一能力同樣重要??蓴U(kuò)展性還可以用來指代系統(tǒng)的多個(gè)不同參數(shù):可以處理多少額外的流量;存儲容量是否容易添加;甚至是可以多處理多少事務(wù)。

  • 可管理性(Manageablity):設(shè)計(jì)容易管理的系統(tǒng)是另一個(gè)重要的考慮。系統(tǒng)的可管理性即操作(維護(hù)與升級)的可擴(kuò)展性??晒芾硇孕枰紤]的事情包括診斷與理解發(fā)生的問題的容易程度,更新和修改的容易程度,以及系統(tǒng)運(yùn)行的簡單程度。(即,系統(tǒng)是否沒有錯(cuò)誤和異常地持續(xù)運(yùn)行?)

  • 成本(Cost):成本是一個(gè)重要因素。顯然,成本包括硬件和軟件的花費(fèi),但考慮系統(tǒng)部署和維護(hù)的花費(fèi)也很重要。系統(tǒng)開發(fā)時(shí)間、運(yùn)行消耗甚至是培訓(xùn)成本都應(yīng)該被考慮進(jìn)來。成本應(yīng)當(dāng)包括所有者的全部花費(fèi)。

這些原則中的每一條都提供了設(shè)計(jì)分布式Web架構(gòu)時(shí)需要考慮的基礎(chǔ)。Each of these principles provides the basis for decisions in designing a distributed web architecture.

然而,這些原則之間也可能產(chǎn)生沖突,實(shí)現(xiàn)一個(gè)目標(biāo)需要以另一個(gè)目標(biāo)為代價(jià)。一個(gè)基本的例子:選擇簡單地增加更多的服務(wù)器來提高容量(可擴(kuò)展性)可能影響可管理性(需要管理額外的服務(wù)器)和成本(服務(wù)器的價(jià)格)。

設(shè)計(jì)任何Web應(yīng)用的時(shí)候都要考慮這些原則,即使需要注意到,在一般情況下,一個(gè)設(shè)計(jì)可能會犧牲其中的一個(gè)或幾個(gè)原則。

1.2 基礎(chǔ)

對于系統(tǒng)架構(gòu),有不少的事情需要考慮:什么是的部分,這些部分怎么安排在一起,以及什么是正確的取舍。雖然在真正有需要之前就投資進(jìn)行擴(kuò)展通常不是一個(gè)明智的商業(yè)提議,但在設(shè)計(jì)上的一些遠(yuǎn)見可以在未來節(jié)省大量的時(shí)間和資源。

這一節(jié)主要討論對幾乎所有的大型Web應(yīng)用都很重要的一些核心因素:服務(wù)(service)、冗余(redundancy)、分區(qū)(partition)、故障處理(handling failure)。
每一個(gè)因素都涉及到取舍和妥協(xié),特別是在前一節(jié)提到的幾個(gè)原則的背景下。我們最好從一個(gè)例子開始進(jìn)行詳細(xì)解釋。

例子:圖片托管應(yīng)用")例子:圖片托管應(yīng)用

在某一時(shí)刻你可能在網(wǎng)上上傳了一張圖片。對于托管和傳送大量圖片的大型站點(diǎn),
構(gòu)建一個(gè)性價(jià)比好、可用性高、延遲低(快速檢索)的架構(gòu)很有挑戰(zhàn)性。
(there are challenges in building an architecture that is cost-effective, highly available, and has low latency (fast retrieval).)

想象一個(gè)用戶可以上傳圖片到中心服務(wù)器上,并可以通過一個(gè)Web鏈接或API請求圖片的系統(tǒng),就像Flickr或Picasa一樣。
簡單起見,我們假設(shè)這個(gè)應(yīng)用有兩個(gè)主要的部分:

上傳(寫)圖片到服務(wù)器的能力,以及請求一個(gè)圖片的能力。

我們當(dāng)然希望上傳是高效的,但我們更關(guān)注的是當(dāng)請求一個(gè)圖片時(shí)(如為一個(gè)網(wǎng)頁請求圖片),圖片能快速地傳送。這和一個(gè)Web服務(wù)器或一個(gè)CDN(Content Delivery Network,內(nèi)容傳送網(wǎng)絡(luò))邊緣服務(wù)器(edge server,CDN使用的服務(wù)器,將內(nèi)容存儲在很多地方,所以內(nèi)容在地理上/物理上接近用戶,從而提高性能)提供的功能很相似。

系統(tǒng)的其他重要方面有:

  • 可以存儲的圖片數(shù)量沒有限制,因此在圖片數(shù)量上的可擴(kuò)展性需要考慮

  • 圖片的下載/請求需要低延遲

  • 如果用戶上傳了一張圖片,這個(gè)圖片要一直存在(即數(shù)據(jù)可靠性)

  • 系統(tǒng)應(yīng)當(dāng)易于維護(hù)(可管理性)

  • 由于圖片托管利潤率不高,系統(tǒng)需要較高的性價(jià)比

圖1.1是系統(tǒng)功能的簡單圖示。

[圖片上傳失敗...(image-1d6cd4-1558156489675)] is a simplified diagram of the functionality.
](http://nettee.github.io/posts/2016/Scalable-Web-Architecture-and-Distributed-Systems/imageHosting1.jpg)

圖1.1:簡化后的圖片托管架構(gòu)圖

在這個(gè)圖片托管的示例中,系統(tǒng)必須要足夠快,數(shù)據(jù)存儲可靠,所有的屬性高度可擴(kuò)展。構(gòu)建這個(gè)應(yīng)用的小型版本很簡單,只要托管在一個(gè)服務(wù)器上即可,但這樣我們就沒有討論的必要了。我們假設(shè)想要構(gòu)建一個(gè)可以增長到和Flickr一樣大的家伙。

服務(wù)

當(dāng)考慮可擴(kuò)展性系統(tǒng)設(shè)計(jì)時(shí),對功能進(jìn)行解耦(decouple ),并將系統(tǒng)的每一個(gè)部分各自考慮成具有清晰定義的接口的服務(wù),會很有幫助。

  • 面向服務(wù)的架構(gòu)(Service-Oriented Architecture, SOA)
    實(shí)際上,我們稱這樣的系統(tǒng)擁有面向服務(wù)的架構(gòu)(Service-Oriented Architecture, SOA)。對這些類型的系統(tǒng),每一個(gè)服務(wù)都有它自己清晰的功能環(huán)境,和這個(gè)環(huán)境之外的任何事物的交互都通過抽象的接口(特別是其他服務(wù)的公開API)進(jìn)行。

將系統(tǒng)拆解為一些互補(bǔ)的服務(wù)的集合,可以將對這些部分的管理分離開。這種抽象幫助建立服務(wù)、服務(wù)之下的環(huán)境、服務(wù)的顧客之間清晰的關(guān)系。描述這些清晰的輪廓可以將問題隔離開,也使得每一部分可以獨(dú)立地?cái)U(kuò)展。這種面向服務(wù)的系統(tǒng)設(shè)計(jì)和編程中的面向?qū)ο笤O(shè)計(jì)非常相似。

在我們的例子中,所有上傳和檢索圖片的請求都由同一個(gè)服務(wù)器進(jìn)行處理。然而,由于系統(tǒng)需要擴(kuò)展,將這兩個(gè)功能(上傳和檢索)分解成兩個(gè)服務(wù)是有意義的。

讓我們快速前進(jìn),假設(shè)服務(wù)是重度使用的。這樣一個(gè)場景使我們?nèi)菀卓闯鰧懭氲臅r(shí)間對讀出時(shí)間的重大影響(因?yàn)檫@兩個(gè)功能會競爭共享資源)。在一些架構(gòu)上,這個(gè)影響可能是很大的。即使上傳和下載的速度相同(對大多數(shù)的IP網(wǎng)絡(luò),這是不對的,大部分的網(wǎng)絡(luò)都設(shè)計(jì)為至少3:1的下載/上傳比),讀文件基本上是從高速緩存(cache)讀的,而寫文件需要最終寫到磁盤上(在一些情況里還可能會寫多次)。即使所有的東西都在內(nèi)存里,或讀文件是從磁盤(如SSD)里讀,數(shù)據(jù)庫的寫入也總是會比讀取慢。(Pole Position,一個(gè)開源的數(shù)據(jù)庫衡量工具,http://polepos.org/ 和結(jié)果http://polepos.sourceforge.net/results/PolePositionClientServer.pdf )。

  • 這個(gè)設(shè)計(jì)的另一個(gè)潛在問題是

像Apache和lighttpd這樣的Web服務(wù)器可維護(hù)的同時(shí)連接數(shù)有上限(默認(rèn)值在500左右,但還可以更高)

在流量很大的情況下,寫操作會很快占用所有的連接。因?yàn)樽x操作是可以異步進(jìn)行的,或是受益于其他的性能優(yōu)化技術(shù)(如gzip壓縮或分塊傳輸編碼),Web服務(wù)器可以快速在客戶端間切換,從而支持比最大連接數(shù)多得多的服務(wù)請求(在最大連接數(shù)設(shè)置為500的Apache服務(wù)器上,每秒處理上千個(gè)讀請求并不稀奇)。

另一方面,寫操作就需要為上傳操作維護(hù)一個(gè)持續(xù)打開一段時(shí)間的連接。因此使用家庭網(wǎng)絡(luò)(速度1MB/s——譯注)上傳1MB的文件用時(shí)通常會多于1秒鐘,Web服務(wù)器可能只能同時(shí)處理500個(gè)進(jìn)行的寫操作。


圖1.2: 讀寫分離

為這種瓶頸做計(jì)劃的話,將讀圖片和寫圖片分離成兩個(gè)服務(wù)是很好的做法,如圖1.2所示。這不僅使我們可以獨(dú)立地對它們進(jìn)行擴(kuò)展(因?yàn)樽x可能總是比寫多),還讓每一點(diǎn)發(fā)生了什么變得很清晰。

另外,這樣還使未來的關(guān)注點(diǎn)分離開,使排解故障變得更容易,也有利于對讀得慢的情況進(jìn)行擴(kuò)展。

這個(gè)方法的好處在于我們可以單獨(dú)解決每個(gè)問題,而不需要在同一個(gè)語境中討論寫入和取回圖片。這兩個(gè)服務(wù)仍然都影響著全局圖片庫,但它們可以各自用合適的方法進(jìn)行優(yōu)化,提升性能(例如,將請求放入隊(duì)列,或?qū)⒔?jīng)常使用的圖片緩存——下文會詳細(xì)敘述)。每個(gè)服務(wù)可以獨(dú)自擴(kuò)展,從維護(hù)和成本的角度來看是非常好的,因?yàn)槿绻麅烧呓豢椈祀s在一起,一個(gè)服務(wù)可能在偶然間對另一個(gè)服務(wù)的性能有很大影響,正如在上面討論過的情景中那樣。

當(dāng)然,在上面的例子中,如果有兩個(gè)終端,效果也會很好(實(shí)際上這和很多云存儲提供方的實(shí)現(xiàn)以及CDN非常相似)。解決這類瓶頸的方法很多,每一個(gè)都有不同的權(quán)衡。

  • 讀寫分離后 對用戶繼續(xù)拆分

例如,F(xiàn)lickr解決這個(gè)讀/寫問題的方法是將將用戶分布到不同的shard上。每個(gè)shard只能處理一小部分的用戶,在用戶數(shù)量增長時(shí),將更多的shard加入集群中(參加Flickr的擴(kuò)展的presentation, http://mysqldba.blogspot.com/2008/04/mysql-uc-2007-presentation-file.html )。在第一個(gè)例子中,基于實(shí)際使用(整個(gè)系統(tǒng)的讀/寫數(shù))很容易擴(kuò)展硬件,然而Flickr是基于用戶基數(shù)擴(kuò)展(但強(qiáng)制假設(shè)用戶使用量均等,這樣會有額外的容量)。

在第一個(gè)例子中,斷電或服務(wù)故障會使整個(gè)系統(tǒng)的功能失效(例如,沒有人可以寫文件),而Flickr的一個(gè)shard的斷電卻只會影響它關(guān)聯(lián)的那些用戶。

在第一個(gè)例子中,在全部的數(shù)據(jù)集上進(jìn)行操作很容易——比如更新寫服務(wù),使之包含新的元數(shù)據(jù)或在所有的元數(shù)據(jù)中搜索,而在Flickr的架構(gòu)上每個(gè)shard都需要更新或搜索(或者,需要?jiǎng)?chuàng)建一個(gè)搜索服務(wù),整理元數(shù)據(jù)——他們實(shí)際上就是這么干的)。

對于這些系統(tǒng)架構(gòu),沒有一個(gè)最終正確的答案。但我們應(yīng)當(dāng)回到本章開頭的那些原則,判斷系統(tǒng)的需要(重度讀還是重度寫,并發(fā)層級,數(shù)據(jù)集上的請求,范圍,排序,等等 heavy reads or writes or both, level of concurrency, queries across the data set, ranges, sorts, etc.),

衡量不同的替代品,理解系統(tǒng)是如何失效的,并對失效發(fā)生有一個(gè)清晰的應(yīng)對計(jì)劃。這些都對建立一個(gè)好的系統(tǒng)架構(gòu)很有幫助。

冗余 Redundancy

為了優(yōu)雅地處理失效問題,一個(gè)Web架構(gòu)必須有服務(wù)和數(shù)據(jù)的冗余(services and data)。
例如,如果文件只有一個(gè)拷貝,存儲在單個(gè)服務(wù)器上,那么服務(wù)器的損壞就意味著文件的丟失。丟失數(shù)據(jù)從來不是一件好事,通常的應(yīng)對方法是創(chuàng)建多個(gè)冗余的拷貝。

同樣的原則也適用于服務(wù)。如果有一個(gè)應(yīng)用的核心功能,保證有多個(gè)拷貝或多個(gè)版本在同時(shí)運(yùn)行,可以免于單個(gè)服務(wù)結(jié)點(diǎn)失效的危害。

  • 一句話描述:避免單點(diǎn)故障

在系統(tǒng)中創(chuàng)建冗余可以消除單點(diǎn)失效問題,并在發(fā)生危機(jī)時(shí)提供備份或多余的功能。例如,如果在產(chǎn)品中有相同服務(wù)的兩個(gè)實(shí)例在運(yùn)行,其中一個(gè)失效或退化,系統(tǒng)可以故障轉(zhuǎn)移(failover)到另一個(gè)健康的拷貝上。
故障轉(zhuǎn)移可以是自動(dòng)進(jìn)行,或是需要人工干預(yù)。

  • 一句話描述:去中心化,沒有單點(diǎn)故障問題

服務(wù)冗余(service redundancy)的另一個(gè)關(guān)鍵部分是創(chuàng)建一個(gè)無共享架構(gòu)(shared-nothing architecture)。
在這個(gè)架構(gòu)中,每個(gè)結(jié)點(diǎn)都可以獨(dú)立(independently)于其他結(jié)點(diǎn)運(yùn)行,
沒有中央的管理狀態(tài)的“大腦”,也也沒有和其他結(jié)點(diǎn)協(xié)調(diào)(coordinating )進(jìn)行的活動(dòng)。

在對可擴(kuò)展性很有好處,因?yàn)樾陆Y(jié)點(diǎn)的添加不需要特殊條件和知識。
然而最重要的是,這些系統(tǒng)中的沒有單點(diǎn)故障,
對故障更有彈性(resilient )

例如,
數(shù)據(jù):
在我們的圖片服務(wù)應(yīng)用中,所有的圖片都在別處的一個(gè)硬件上有一份冗余拷貝(最理想的是在不同的地理位置上,以防地震、火災(zāi)等的發(fā)生)。
服務(wù):
訪問圖片的服務(wù)也是冗余的,都有請求處理的可能。(見圖1.3)(負(fù)載均衡器 Load balancers 是實(shí)現(xiàn)這一點(diǎn)的好辦法,下文會詳細(xì)敘述)。


圖1.3: 帶有冗余的圖片托管應(yīng)用 服務(wù)和數(shù)據(jù)都是有備份的

分區(qū) Partitions

數(shù)據(jù)集可能會有很大,導(dǎo)致單個(gè)服務(wù)器裝不下;
也可能是一個(gè)操作需要太多的計(jì)算資源,降低了性能,導(dǎo)致必須增加容量。

在這樣的情況下你有兩種選擇:垂直分區(qū)或是水平分區(qū)(scale vertically or horizontally)。

  • 一句話描述:升級機(jī)器硬件,不用改造程序
    垂直擴(kuò)展意味著在一個(gè)服務(wù)器上增加更多的資源
    因此對于很大的數(shù)據(jù)集,這可能意味著增加更多(或更大的)硬盤驅(qū)動(dòng)器,使一個(gè)服務(wù)器可以包含整個(gè)數(shù)據(jù)集。
    對于計(jì)算操作,這可能意味著將計(jì)算移動(dòng)到有更快的CPU、更大的內(nèi)存的服務(wù)器上。對于每種情況,垂直擴(kuò)展總是通過使個(gè)體的資源的能力更強(qiáng)來實(shí)現(xiàn)。

  • 一句話描述:增加更多機(jī)器,需要合理的架構(gòu)
    另一方面,水平擴(kuò)展(scale horizontally)則是增加更多的結(jié)點(diǎn)。
    對于很大的數(shù)據(jù)集,這可能是有第二個(gè)服務(wù)器來存儲部分?jǐn)?shù)據(jù)。對于計(jì)算操作,這可能意味著將操作或負(fù)載分離到某個(gè)額外的結(jié)點(diǎn)上。
    為了充分利用水平擴(kuò)展,它需要作為系統(tǒng)架構(gòu)的固有設(shè)計(jì)原則引入,否則為它修改和分離出環(huán)境會相當(dāng)麻煩。

  • 一句話描述:付費(fèi)用戶通過分區(qū)來擴(kuò)展
    當(dāng)談到水平擴(kuò)展時(shí),最常見的技術(shù)之一就是將你的服務(wù)分解為多個(gè)分區(qū)(partition),或是多個(gè)shard(break up your services into partitions, or shards)
    分區(qū)是可以分開的,于是每個(gè)邏輯功能集都分開了。
    可以按地理的邊界(geographic boundaries)劃分分區(qū),
    或是用其他標(biāo)準(zhǔn),如免費(fèi)用戶和付費(fèi)用戶。

這種計(jì)劃(schemes )的好處是為服務(wù)或數(shù)據(jù)提供了增加的容量。
The advantage of these schemes is that they provide a service or data store with added capacity

  • 一句話描述:客戶端如何訪問 分區(qū)

在我們的圖片服務(wù)器的例子中,將用來存儲圖片的單個(gè)文件服務(wù)器替換為多個(gè)文件服務(wù)器是可行的,每個(gè)服務(wù)器包含它自己獨(dú)一無二的圖片集。(見圖1.4)。這樣的架構(gòu)讓系統(tǒng)可以在每個(gè)文件服務(wù)器上填充圖片,在磁盤滿的時(shí)候增加額外的服務(wù)器。這種設(shè)計(jì)需要

一個(gè)命名規(guī)則,將圖片的文件名關(guān)聯(lián)到它所在的服務(wù)器。
一個(gè)圖片的名字可以由一個(gè)在所有服務(wù)器相同的散列規(guī)則來生成?;蛘哂昧硪环N方法,每個(gè)圖片都分配一個(gè)自動(dòng)增加的ID,當(dāng)客戶端請求圖片時(shí),圖片取回服務(wù)只需要維護(hù)每個(gè)服務(wù)器映射到的ID的范圍(類似于一個(gè)索引)即可。


圖1.4:帶有冗余和分區(qū)的圖片托管應(yīng)用

  • 分區(qū)之后遇到數(shù)局部性和不一致的問題

將數(shù)據(jù)或功能分布在多個(gè)服務(wù)器上當(dāng)然是有挑戰(zhàn)的。一個(gè)主要的問題就是數(shù)據(jù)局部性(data locality)。在分布式系統(tǒng)中,數(shù)據(jù)距離操作或計(jì)算點(diǎn)越近,系統(tǒng)的性能就越好。因此將數(shù)據(jù)散播在多個(gè)服務(wù)器上可能會有問題,因?yàn)楫?dāng)需要的數(shù)據(jù)不在本地的時(shí)候,就迫使服務(wù)器進(jìn)行在網(wǎng)絡(luò)上請求信息的昂貴操作。

另一個(gè)潛在的問題是不一致(inconsistency)。當(dāng)有多個(gè)服務(wù)從一個(gè)共享的資源(可能是另一個(gè)服務(wù)或數(shù)據(jù)存儲)讀寫時(shí),有可能產(chǎn)生競爭條件(race condition)——當(dāng)數(shù)據(jù)應(yīng)當(dāng)更新時(shí),讀操作卻在更新之前進(jìn)行——在這些情況下數(shù)據(jù)會不一致。例如,在圖片托管的場景中,競爭條件可能在這種情況下出現(xiàn):一個(gè)客戶端發(fā)送請求,將狗的圖片的標(biāo)題從”Dog”更新為”Gizmo”,但同時(shí)另一個(gè)客戶端正在讀圖片。我們不清楚第二個(gè)客戶端應(yīng)該收到標(biāo)題”Dog”還是”Gizmo”。

將數(shù)據(jù)進(jìn)行分區(qū)一定會伴隨著一些障礙,但分區(qū)使每個(gè)問題可以根據(jù)數(shù)據(jù)、負(fù)載、使用模式等分離為可管理的塊。這對可擴(kuò)展性和可管理性有幫助,但也不是沒有危險(xiǎn)。有很多方法可以減輕風(fēng)險(xiǎn)和處理失效,然而,為了簡潔,本章中沒有涉及這些內(nèi)容。如果你想了解更多,可以查看我的博文中關(guān)于容錯(cuò)的內(nèi)容。
http://katemats.com/distributed-systems-basics-handling-failure-fault-tolerance-and-monitoring/](http://katemats.com/distributed-systems-basics-handling-failure-fault-tolerance-and-monitoring

1.3 快速可擴(kuò)展數(shù)據(jù)訪問構(gòu)件

講完設(shè)計(jì)分布式系統(tǒng)的一些核心考慮后,我們來談?wù)勔粋€(gè)比較難的部分:擴(kuò)展數(shù)據(jù)訪問。(scaling access to the data)

大多數(shù)簡單的Web應(yīng)用,例如LAMP stack應(yīng)用,看起來像是圖1.5中這樣。

圖1.5:簡單的Web應(yīng)用

當(dāng)這個(gè)應(yīng)用進(jìn)行擴(kuò)展時(shí),主要有兩個(gè)挑戰(zhàn):擴(kuò)展對應(yīng)用服務(wù)器的訪問,和對數(shù)據(jù)庫的訪問。在高度可擴(kuò)展的應(yīng)用設(shè)計(jì)中,一個(gè)應(yīng)用服務(wù)器(或Web服務(wù)器)通常是最小化的,包含一個(gè)無共享架構(gòu)。這使系統(tǒng)中的應(yīng)用服務(wù)器層水平可擴(kuò)展。這種設(shè)計(jì)導(dǎo)致負(fù)擔(dān)傳遞到棧下層的數(shù)據(jù)庫服務(wù)器和配套服務(wù)上,在這一層才是擴(kuò)展和性能的挑戰(zhàn)真正起作用的地方。

本章的余下部分將著重講述一些常見的策略和方法,通過提供快速數(shù)據(jù)訪問,使這些服務(wù)快速而可擴(kuò)展。

圖1.6:過于簡單化的Web應(yīng)用

大多數(shù)系統(tǒng)可以簡化到圖1.6。從這里講起很合適。如果你有很多的數(shù)據(jù),想要快速而容易地訪問,類似在你書桌最上面的抽屜里儲存一堆糖果。雖然過分簡化了,上面的句子還是暗示出了兩個(gè)難題:存儲可擴(kuò)展和數(shù)據(jù)可快速訪問。

  • 舉例
    在本節(jié)中,假設(shè)你有很多TB的數(shù)據(jù),你想讓用戶可以隨機(jī)訪問一部分?jǐn)?shù)據(jù)。(見圖1.7)。這和上面圖片應(yīng)用的例子中,在文件服務(wù)器上定位一個(gè)圖片很相似。

圖1.7:訪問特定的數(shù)據(jù)

  • 用戶訪問部分?jǐn)?shù)據(jù) 四個(gè)常見的策略是緩存、代理、索引和負(fù)載均衡器

這件事很有挑戰(zhàn)性,因?yàn)閷B級的數(shù)據(jù)裝進(jìn)內(nèi)存的開銷非常大,會直接帶來很多的磁盤IO。
從磁盤讀比從內(nèi)存讀慢很多——內(nèi)存訪問和查克-諾里斯一樣快(Chuck Norris,美國動(dòng)作片演員——譯注),
然而磁盤訪問卻比美國車輛管理局的隊(duì)伍還慢。這個(gè)速度上的差異對于大數(shù)據(jù)集會更明顯。

對于浮點(diǎn)數(shù),內(nèi)存在順序讀的情況下比磁盤快6倍,在隨機(jī)讀的情況下比磁盤快100000倍(見“大數(shù)據(jù)病理學(xué)”,http://queue.acm.org/detail.cfm?id=1563874 )。此外,即使對于獨(dú)一無二的ID,找到那很少量的數(shù)據(jù)都是一項(xiàng)很費(fèi)力的任務(wù),就像不看一眼就找到你的糖果中的暴風(fēng)果橡皮糖一樣。

幸運(yùn)的是你有很多選擇使這項(xiàng)工作變得容易。

四個(gè)常見的策略是緩存、代理、索引和負(fù)載均衡器。本節(jié)余下的部分會討論這些概念如何使數(shù)據(jù)訪問變得更快。

緩存

緩存利用的是引用的局部性原理:最近請求過的數(shù)據(jù)很可能會被再次請求。(recently requested data is likely to be requested again)
緩存被用在計(jì)算機(jī)的幾乎所有層次:硬件,操作系統(tǒng),瀏覽器,Web應(yīng)用,等等。緩存像一個(gè)短期的內(nèi)存:它的空間有限,但通常速度比數(shù)據(jù)源快,含有最近最常訪問的數(shù)據(jù)。

緩存可以在架構(gòu)中的任何一個(gè)層次存在,但經(jīng)常是出現(xiàn)在最靠近前端的層次,實(shí)現(xiàn)為快速返回?cái)?shù)據(jù),不增加下層的負(fù)擔(dān)。

在我們的API例子中,緩存是如何能夠使數(shù)據(jù)被快速訪問的呢?在這個(gè)例子中,你可以在好幾個(gè)地方插入緩存。一個(gè)選擇是將緩存插入請求層結(jié)點(diǎn),如圖1.8所示。

圖1.8:在請求層結(jié)點(diǎn)插入緩存

將緩存直接放入請求層結(jié)點(diǎn)使該結(jié)點(diǎn)可以在局部存儲響應(yīng)數(shù)據(jù)。每次一個(gè)請求發(fā)往這個(gè)服務(wù),結(jié)點(diǎn)如果有緩存著的數(shù)據(jù),都會快速返回。如果數(shù)據(jù)不在緩存中,請求結(jié)點(diǎn)會從磁盤請求數(shù)據(jù)。一個(gè)請求層結(jié)點(diǎn)的緩存既可以是放在內(nèi)存中(速度非??欤部梢苑旁诮Y(jié)點(diǎn)的磁盤中(比通過網(wǎng)絡(luò)獲得要快)。

圖1.9:多個(gè)緩存

  • What happens when you expand this to many nodes?

如果將緩存擴(kuò)展到多個(gè)結(jié)點(diǎn)上會怎么樣呢?如圖1.9所示,如果請求層擴(kuò)展到多個(gè)結(jié)點(diǎn),每個(gè)結(jié)點(diǎn)仍很有可能擁有自己的緩存。然而,如果你的負(fù)載均衡器隨即地在結(jié)點(diǎn)間分發(fā)請求,同一個(gè)請求可能發(fā)往不同的結(jié)點(diǎn),這會增加緩存不命中的次數(shù)??朔@個(gè)障礙的兩種選擇是:全局緩存和分布式緩存。
Two choices for overcoming this hurdle are global caches and distributed caches.

全局緩存

一個(gè)全局緩存的表現(xiàn)和它的名字一樣:所有的結(jié)點(diǎn)使用相同的單一緩存空間。這需要增加一個(gè)服務(wù)器或某種文件存儲,比原先的存儲要快,而且可以被所有的請求層結(jié)點(diǎn)訪問。

每個(gè)請求層結(jié)點(diǎn)使用和查詢局部緩存一樣的方法查詢?nèi)志彺?。這種緩存策略會有點(diǎn)復(fù)雜,因?yàn)樗诳蛻舳撕驼埱蟮臄?shù)量增加時(shí),很容易就壓垮了單個(gè)的緩存。但在一些架構(gòu)中,這種方法很有效,尤其是一些有特殊硬件的架構(gòu),硬件可以實(shí)現(xiàn)很快速的全局緩存,或架構(gòu)中需要緩存的數(shù)據(jù)集大小確定。

全局緩存有兩種描述形式。在圖1.10中,當(dāng)在緩存中沒有發(fā)現(xiàn)已緩存請求時(shí),緩存自己負(fù)責(zé)從底層存儲獲取缺失的數(shù)據(jù)。在圖1.11中,是由請求層結(jié)點(diǎn)負(fù)責(zé)獲取緩存中沒有的數(shù)據(jù)。

圖1.10:緩存負(fù)責(zé)獲取數(shù)據(jù)的全局緩存

圖1.11:請求結(jié)點(diǎn)負(fù)責(zé)獲取數(shù)據(jù)的全局緩存

使用全局緩存的應(yīng)用大部分傾向于使用第一種,因?yàn)榫彺孀约汗芾砹藬?shù)據(jù)的回收和獲取,防止了從客戶端大量請求相同的數(shù)據(jù)。

  • 全局緩存無法解決的問題

然而,在一些情況下第二個(gè)實(shí)現(xiàn)更有意義。例如,如果緩存正在一個(gè)很大的文件上工作,很低的緩存命中率會使緩存區(qū)不堪重負(fù)。在這種情況下就需要將全部數(shù)據(jù)集(或熱點(diǎn)數(shù)據(jù)集)的大部分放在緩存中。另一個(gè)例子是緩存中存儲的文件都是靜態(tài)的,不應(yīng)被回收。(這可能是因?yàn)閼?yīng)用對于數(shù)據(jù)延遲的要求——特定的數(shù)據(jù)需要在大數(shù)據(jù)集中能很快訪問到——應(yīng)用邏輯比緩存更理解回收策略和熱點(diǎn)。)

分布式緩存 Distributed Cache

  • 概念
    在分布式緩存中(圖1.12),每個(gè)結(jié)點(diǎn)都擁有一部分緩存數(shù)據(jù)。
    那么,如果把緩存比作雜貨店里的冰箱的話,分布式緩存就是將食物放在好幾個(gè)地方——冰箱、櫥柜飯盒——不需要去商店就能拿到零食。

通常,緩存是用一個(gè)全局唯一的散列函數(shù)分割的,一個(gè)請求結(jié)點(diǎn)尋找特定數(shù)據(jù)時(shí),可以快速知道去哪個(gè)緩存上尋找,判斷數(shù)據(jù)是否可獲取。在這種情況下,每個(gè)結(jié)點(diǎn)都有少量的緩存,在將數(shù)據(jù)送回原處之前會想其他結(jié)點(diǎn)發(fā)送數(shù)據(jù)的請求。因此,分布式緩存的一個(gè)好處就是增加了緩存空間,且只需要為請求池增加結(jié)點(diǎn)就可以實(shí)現(xiàn)。

  • 分布式緩存的一個(gè)缺點(diǎn)是補(bǔ)救缺失結(jié)點(diǎn)。
    一些分布式緩存通過在不同的結(jié)點(diǎn)上存儲數(shù)據(jù)的多個(gè)拷貝來對付這種情況。然而,你可以想象很快邏輯會變得多么復(fù)雜,特別是當(dāng)你從請求層增加或刪除了結(jié)點(diǎn)時(shí)。即使一個(gè)結(jié)點(diǎn)消失了,部分的緩存丟失,請求還會將它們從原處拉過來——這是一個(gè)不必要的災(zāi)難!

緩存的一個(gè)很大的好處是它們通常會使一切更快(當(dāng)然,是在實(shí)現(xiàn)正確的情況下)。你選擇的方法允許你即使在更多請求的情況下也能使它更快。然而,所有這些緩存都以要有維護(hù)額外存儲空間的代價(jià),通常是昂貴的內(nèi)存;沒有什么是免費(fèi)的。緩存在加速上面做得很好,此外還使系統(tǒng)在高負(fù)載的情況下運(yùn)行良好。沒有緩存,服務(wù)可能會整個(gè)退化。

  • 一句話描述: Memcached可以分布式緩存

一個(gè)流行的開源緩存的例子是Memcached(http://memcached.org/ )(既可以作為局部緩存也可以作為分布式緩存)。其他的選擇還有很多,包括很多特定語言、特定框架下的選擇。

Memcached被很多大型的網(wǎng)站使用。雖然它可以非常強(qiáng)大,但它只是一個(gè)簡單的內(nèi)存中的鍵-值存儲,為任意的數(shù)據(jù)存儲和快速查找(O(1))優(yōu)化過。

Facebook使用多不同類型的緩存維持網(wǎng)站的性能(見“Facebook緩存與性能”)。他們使用$GLOBALS和語言級的APC緩存(在PHP中以一個(gè)函數(shù)調(diào)用為代價(jià)提供),使中間層函數(shù)調(diào)用和返回更加快速。(大多數(shù)語言都有這種類型的庫,用于提升網(wǎng)頁性能,應(yīng)該一直使用它們。)Facebook還使用了一個(gè)分布在很多服務(wù)器上的全局緩存(見“擴(kuò)展Facebook的memcached”),這樣一個(gè)訪問緩存的函數(shù)調(diào)用就可以得到多個(gè)請求,并行請求不同的Memcached服務(wù)器上存儲的數(shù)據(jù)。這使他們對用戶配置數(shù)據(jù)有更高的性能和吞吐量,并且有一個(gè)更新數(shù)據(jù)的中心位置(這很重要,因?yàn)榫彺媸Ш途S護(hù)一致性在運(yùn)行上千個(gè)服務(wù)器時(shí)是很有挑戰(zhàn)性的)。

  • 用緩存的會出現(xiàn)的問題
    現(xiàn)在讓我們談?wù)劗?dāng)數(shù)據(jù)不在緩存中時(shí)該怎么辦……
    Now let's talk about what to do when the data isn't in the cache…

代理 Proxies

在基礎(chǔ)層面上,代理服務(wù)器是一個(gè)硬件/軟件的中間片,從客戶端接收請求,并轉(zhuǎn)播到后端服務(wù)器。代理通常用來過濾請求和記錄請求,有時(shí)候也用來變換請求(通過增加/刪除頭部,加密/解密,或是壓縮)。

  • 代理的定義
    代理在將來自多個(gè)服務(wù)器的請求整合在一起的時(shí)候也非常有用,提供了從整個(gè)系統(tǒng)角度優(yōu)化請求量的機(jī)會。使用代理加速數(shù)據(jù)訪問的一種方法是將相同或相似的請求壓縮為一個(gè),然后為發(fā)送請求的客戶端返回同一個(gè)結(jié)果。這就是壓縮轉(zhuǎn)發(fā)(collapsed forwarding)。

想象一下在多個(gè)結(jié)點(diǎn)上有對同一個(gè)數(shù)據(jù)(叫它littleB)的請求,且這個(gè)數(shù)據(jù)不在緩存中。如果請求是通過代理發(fā)送,那么所有的請求就會被壓縮成一個(gè),這意味著我們只需要從磁盤上讀一次littleB。(見圖1.14)。這個(gè)設(shè)計(jì)會帶來一些開銷,因?yàn)槊總€(gè)請求的延遲會稍高,并且一些請求可能會因?yàn)橐拖嗨频恼埱蠛喜⒍休p微的延期。但這種做法在高負(fù)載,尤其是一些數(shù)據(jù)被一次又一次地請求的情況下,會大幅提升性能。這和緩存很像,但與緩存將數(shù)據(jù)/文檔存儲起來不同,代理是優(yōu)化對文檔的請求或調(diào)用,充當(dāng)這些客戶的代理人。

例如,在LAN代理中,客戶無需知道自己的IP地址,就可以連接Internet,并且LAN會將用戶對相同內(nèi)容的請求壓縮為一個(gè)。這里很容易搞混,因?yàn)楹芏啻硗瑫r(shí)就是緩存(因?yàn)榇硎呛苓m合放置緩存的地方),但不是所有的緩存都像代理一樣工作。

另一個(gè)使用請求的很好方式是不僅僅壓縮對同一個(gè)數(shù)據(jù)的請求,還壓縮對原存儲中相距很近(在磁盤上連續(xù))的數(shù)據(jù)的請求。使用這個(gè)策略最大化了請求的數(shù)據(jù)局部性,可以降低請求延遲。例如,我們有一堆結(jié)點(diǎn)請求B的一部分:B1,B2,等。我們可以將設(shè)定代理識別空間局部性,將它們壓縮為同一個(gè)請求,只返回bigB,這樣就極大地減小了從數(shù)據(jù)源讀的次數(shù)。(見圖1.15)。這可以在你從TB級的數(shù)據(jù)中隨機(jī)訪問時(shí)減少很多請求時(shí)間!代理還在高負(fù)載或是緩存空間有限時(shí)非常有用,因?yàn)樗鼈兡芤淮翁幚硪慌鷶?shù)據(jù)。

需要注意的是,你可以同時(shí)使用代理和緩存,但通常最好將緩存放在代理前面,這和馬拉松比賽中讓跑的最快的人最先跑是一個(gè)道理。因?yàn)榫彺媸菑膬?nèi)存中提供數(shù)據(jù),它很快,而且不在意對同一個(gè)結(jié)果的多個(gè)請求。但如果緩存被放在代理的后面,每個(gè)在緩存前面的請求就會有額外的冗余,這也會拖累性能。

如果你計(jì)劃在系統(tǒng)中增加代理,有很多中方法可以考慮。SquidVarnish都已經(jīng)經(jīng)過實(shí)際試驗(yàn),在很多網(wǎng)站中使用。這些代理的實(shí)現(xiàn)提供了很多優(yōu)化,充分利用客戶-服務(wù)器的通信。在Web服務(wù)器層安裝一個(gè)作為反向代理(在下面負(fù)載均衡器一節(jié)會解釋)會可觀地提升Web服務(wù)器的性能,減少處理進(jìn)入的客戶所需的工作量。

索引

使用索引來快速訪問數(shù)據(jù)是一個(gè)廣為人知的優(yōu)化數(shù)據(jù)訪問性能的方法,在數(shù)據(jù)庫方面可能是最知名的。索引通過增加存儲開銷和寫操作時(shí)間(因?yàn)槟惚仨毻瑫r(shí)寫數(shù)據(jù)和更新索引)來換取更快的讀。

正如傳統(tǒng)的關(guān)系型數(shù)據(jù)存儲一樣,你也可以將這個(gè)概念應(yīng)用到更大的數(shù)據(jù)集上。索引的技巧是你需要仔細(xì)考慮用戶會如何訪問你的數(shù)據(jù)。在數(shù)據(jù)集是TB大小,但負(fù)載非常小(如1KB)的情況下,索引是數(shù)據(jù)訪問優(yōu)化中必須的。在如此大的數(shù)據(jù)集上尋找一個(gè)小負(fù)載會是一項(xiàng)很大的挑戰(zhàn),因?yàn)槟悴豢赡茉诤侠淼臅r(shí)間內(nèi)遍歷這么多數(shù)據(jù)。幸運(yùn)的是,如此大的數(shù)據(jù)集很可能是分布在多個(gè)物理設(shè)備上——這意味著你需要用某種方法找到你想要的數(shù)據(jù)的物理位置。使用索引是最好的方法。

圖1.16:索引

索引用起來像是指引你數(shù)據(jù)所在位置的目錄。例如,我們假設(shè)你在尋找一份數(shù)據(jù),B的第2部分——你如何知道去哪里找它?如果你有按照數(shù)據(jù)類型(像是A,B,C)排序的索引,它就可以告訴你數(shù)據(jù)B的位置。然后你就只需要尋找那個(gè)位置,讀取數(shù)據(jù)B你想要的部分。(見圖1.16)。

這些索引通常是存儲在內(nèi)存中,或是在距離客戶請求很近的地方。人們通常使用Berkeley DBs(BDBs)和樹形的數(shù)據(jù)結(jié)構(gòu)來將數(shù)據(jù)以順序表的形式存儲,這種結(jié)構(gòu)非常適合索引的訪問。

索引通常有多層,像一個(gè)地圖一樣將你從一個(gè)地方送到另一個(gè)地方,直到你獲得你想要的特定數(shù)據(jù)為止。(見圖1.17)。

索引還可以用來創(chuàng)建關(guān)于同一個(gè)數(shù)據(jù)的不同視圖(view)。對于大數(shù)據(jù)集,這種是定義不同的過濾和排序的很好方法,不需要重新排序而創(chuàng)建數(shù)據(jù)的很多個(gè)拷貝。

例如,想象早前圖片托管系統(tǒng)實(shí)際上是托管著圖書頁面的圖片,且服務(wù)允許用戶請求這些圖片上的文字,搜索關(guān)于一個(gè)話題的所有圖書內(nèi)容(和搜索引擎允許你搜索HTML內(nèi)容的方式一樣)。在這種情況下,所有這些圖書圖片使用很多很多的服務(wù)器來存儲文件,尋找一個(gè)頁面來為用戶渲染可能有些復(fù)雜。首先,請求任意詞和詞組的倒排索引(inverse index)需要易于訪問;其次,精確定位到書中的頁面和位置,并將正確的圖片取回,也是一個(gè)挑戰(zhàn)。因此在這種情況下倒排索引會映射到一個(gè)位置上(如圖書B),然后B可能包含一個(gè)關(guān)于每一部分的所有詞、位置、出現(xiàn)次數(shù)的索引。

一個(gè)倒排索引(在上面的圖中可能表示索引1),可能是下面這個(gè)樣子的——每個(gè)詞或詞組都提供了包含它們的圖書的索引。

詞/詞組 圖書
being awesome Book B, Book C, Book D
always Book C, Book F
believe Book B

中間層索引樣子差不多,但只包含詞、位置和B書的信息。這個(gè)嵌套的索引架構(gòu)相比一個(gè)巨大的倒排索引,每個(gè)索引都占用較少的空間。這在大型系統(tǒng)中很重要,因?yàn)檫@些索引即使壓縮過也可能很大,帶來很大的存儲開銷。在這個(gè)系統(tǒng)中如果假設(shè)我們有很多圖書——1億本(見Inside Google Books博文)——為方便計(jì)算,每個(gè)圖書只有10頁大小,每頁250個(gè)詞,這意味著有2500億個(gè)詞。如果我們假設(shè)平均每個(gè)詞5個(gè)字母,每個(gè)字符占8位(即1字節(jié)),那么一個(gè)僅包含每個(gè)詞一次的索引就有超過TB的大小。你可以看出,創(chuàng)建包含很多信息(像詞組、數(shù)據(jù)位置、出現(xiàn)次數(shù))的索引,大小會迅速增長。

創(chuàng)建這些中間索引,表示數(shù)據(jù)的一小部分,使大數(shù)據(jù)的問題易于處理。數(shù)據(jù)可以分布在很多個(gè)服務(wù)器上,仍然訪問迅速。索引你一個(gè)信息檢索的里程碑,是現(xiàn)代搜索引擎的基礎(chǔ)。當(dāng)然,這一節(jié)只是講了一點(diǎn)皮毛,有很多的研究是關(guān)于如果使索引更小、更快、包含更多的信息(如relevancy),以及無縫更新。(有一些管理上的挑戰(zhàn),關(guān)于競爭挑戰(zhàn)的,和關(guān)于增添或改變數(shù)據(jù)時(shí)需要的更新次數(shù)的,尤其是在涉及到relevancy或scoring的時(shí)候)。

快速而容易地找到數(shù)據(jù)是一個(gè)重要的能力,索引是實(shí)現(xiàn)這個(gè)能力的一個(gè)有效和簡單的工具。

負(fù)載均衡器

  • 負(fù)載的定義
    最后,另一個(gè)在任何分布式系統(tǒng)上都很重要的部分的是負(fù)載均衡器。負(fù)載均衡器每個(gè)架構(gòu)上的首要部分,因?yàn)樗鼈兊慕巧菍⒇?fù)載分布到負(fù)責(zé)請求服務(wù)的結(jié)點(diǎn)上。這使系統(tǒng)中多個(gè)結(jié)點(diǎn)可以透明地服務(wù)同一個(gè)功能。(見圖1.18)。它們的主要目標(biāo)是處理很多相同的連接,并將這些連接發(fā)送到其中一個(gè)請求結(jié)點(diǎn)(request node),使系統(tǒng)得以擴(kuò)展,通過增加結(jié)點(diǎn)服務(wù)更多的請求。

  • 負(fù)載的測量
    服務(wù)請求有很多算法可以使用,包括隨機(jī)挑選結(jié)點(diǎn),輪流選擇,甚至是基于內(nèi)存或CPU利用率挑選結(jié)點(diǎn)。負(fù)載均衡器可以實(shí)現(xiàn)為軟件或硬件應(yīng)用。一個(gè)獲得廣泛使用的開源軟件負(fù)載均衡器是HAProxy)。

在一個(gè)分布式系統(tǒng)中,負(fù)載均衡器通常是在系統(tǒng)的最前端,于是所有進(jìn)來的請求都經(jīng)它發(fā)送。在一個(gè)復(fù)雜的分布式系統(tǒng)中,一個(gè)請求經(jīng)過多個(gè)負(fù)載均衡器并不少見。(見圖1.19)。

圖1.19:多個(gè)負(fù)載均衡器

和代理一樣,一些負(fù)載均衡器也可以根據(jù)請求的類型不同發(fā)往不同的結(jié)點(diǎn)。(技術(shù)上這些也叫反向代理。)

  • 負(fù)載均衡器會遇到的挑戰(zhàn)

負(fù)載均衡器的一個(gè)挑戰(zhàn)是處理特定于用戶會話的數(shù)據(jù)。(user-session-specific data 有狀態(tài)的數(shù)據(jù))

  • 例子購物車
    在一個(gè)電商網(wǎng)站上,如果你一個(gè)一個(gè)客戶,你很容易讓客戶將物品放入購物車,并在兩次訪問之間保留這些內(nèi)容(這很重要,因?yàn)槿绻谟脩艋貋淼臅r(shí)候商品已經(jīng)不在購物車?yán)铮憧赡芫唾u不出去商品了)。然而,如果用戶請求在第一次會話時(shí)發(fā)送到一個(gè)結(jié)點(diǎn),第二次的時(shí)候發(fā)送到另一個(gè)結(jié)點(diǎn),因?yàn)樾碌慕Y(jié)點(diǎn)可能缺少那個(gè)用戶購物車的內(nèi)容,就可能出現(xiàn)不一致。(如果你放了6包激浪在購物車?yán)?,回來的時(shí)候發(fā)現(xiàn)購物車是空的,你會不會很煩亂?)

一個(gè)解決方法是使會話有黏性,同一個(gè)用戶的請求總是發(fā)送到相同的結(jié)點(diǎn)上,但這樣就很難利用一些可靠性功能如自動(dòng)故障轉(zhuǎn)移。

在這種情況下,用戶的購物車可以一直保留其中的內(nèi)容,但如果它們黏住的結(jié)點(diǎn)不可用,就需要一個(gè)特殊情況,并且關(guān)于內(nèi)容的假設(shè)不再有效(雖然這假設(shè)不是固定在應(yīng)用中的)。當(dāng)然,這個(gè)問題可以通過使用其他策略和工具解決,例如服務(wù),以及其他未涉及到的內(nèi)容(如瀏覽器緩存,cookie,URL重寫)。

如果系統(tǒng)只有少數(shù)幾個(gè)結(jié)點(diǎn),循環(huán)式DNS之類的會更有用,因?yàn)樨?fù)載均衡器可以會很昂貴,而且增加了一層不必要的復(fù)雜性。當(dāng)然在大型系統(tǒng)中有各種不同的調(diào)度和負(fù)載均衡算法,包括簡單的隨機(jī)抽取和循環(huán)式,和更復(fù)雜的如考慮利用率和容量。所有這些算法都允許分布流量和請求,而且可以體用有用的可靠性工具如自動(dòng)故障轉(zhuǎn)移,或自動(dòng)移除壞點(diǎn)(像不可響應(yīng)的結(jié)點(diǎn))。然而,這些高級特性會使錯(cuò)誤診斷變得很麻煩。例如,對高負(fù)載的情況,負(fù)載均衡器會移除緩慢或超時(shí)的結(jié)點(diǎn)(因?yàn)檎埱筇啵@只會加重其他結(jié)點(diǎn)的負(fù)擔(dān)。在這種情況下廣泛的監(jiān)督很重要,因?yàn)榭傮w的系統(tǒng)流量和吞吐量可以看起來是下降的(因?yàn)榻Y(jié)點(diǎn)服務(wù)更少的請求),但單個(gè)結(jié)點(diǎn)已經(jīng)超載。

負(fù)載均衡器是一個(gè)擴(kuò)展系統(tǒng)容量的簡單方法,和這篇文章中的其他技術(shù)一樣,在分布式系統(tǒng)架構(gòu)中是一個(gè)不可缺少的角色。負(fù)載均衡器還提供了了測試結(jié)點(diǎn)健康度的關(guān)鍵功能,如果一個(gè)結(jié)點(diǎn)不響應(yīng)或過載了,它可以從處理請求的池中移除,利用系統(tǒng)中不同結(jié)點(diǎn)的冗余。

隊(duì)列

目前我們已經(jīng)談?wù)摿撕芏嗫焖僮x數(shù)據(jù)的方法,但擴(kuò)展數(shù)據(jù)層的另一個(gè)重要的部分是對寫的高效管理。當(dāng)系統(tǒng)很簡單時(shí),只有很少的處理負(fù)載,很小的數(shù)據(jù)庫,寫操作當(dāng)然會很快。然而,在更復(fù)雜的系統(tǒng)中,寫操作可能會占用幾乎無法確定的大量時(shí)間。例如,數(shù)據(jù)可能需要寫到不同服務(wù)器和索引上的幾個(gè)地方,或系統(tǒng)就簡單的在高負(fù)載下。在寫操作(或其他類似的任務(wù))可能占用很長時(shí)間的情況下,追求性能和可用性需要在系統(tǒng)中建立異步機(jī)制,常見的辦法就是使用隊(duì)列。

想象一個(gè)系統(tǒng),其中每個(gè)客戶都請求一個(gè)可被遠(yuǎn)程服務(wù)的任務(wù)。
每個(gè)客戶將它們的請求發(fā)到服務(wù)器,服務(wù)器盡快完成任務(wù),并將結(jié)果返回給各自的客戶。
在小型系統(tǒng)中,一個(gè)服務(wù)器(或邏輯服務(wù))可以很快處理到來的客戶,這種情形工作得還不錯(cuò)。然而,當(dāng)服務(wù)器接收到多于它所能處理的請求時(shí),每個(gè)客戶就必須等待其他客戶的請求處理完,才能生成響應(yīng)。這是一個(gè)通過請求的例子,如圖1.20所示。

這種同步行為可能嚴(yán)重降低客戶性能??蛻舯仨毜却?,在請求得到應(yīng)答前都做不了任何工作。增加額外的服務(wù)器處理系統(tǒng)負(fù)載也無法解決這個(gè)問題,甚至使用高效的負(fù)載均衡器也很難保證平等分配工作,最大化客戶性能。此外,如果處理請求的服務(wù)器是不可用的,或失效的,上游的客戶也會失效。

要有效解決這個(gè)問題,需要在客戶請求和實(shí)際的服務(wù)之間進(jìn)行抽象。

圖1.21:使用隊(duì)列管理請求

  • 隊(duì)列基本作用
    開始講隊(duì)列。一個(gè)隊(duì)列和它表面一樣簡單:一個(gè)任務(wù)來到后,被添加到隊(duì)列中,結(jié)點(diǎn)在它們有容量處理任務(wù)的時(shí)候從隊(duì)列中取出下一個(gè)。(見圖1.21。)這些任務(wù)可以表示簡單的寫數(shù)據(jù)庫,或是復(fù)雜到生成文檔預(yù)覽縮略圖。當(dāng)客戶提交了任務(wù)請求后,不再需要等待結(jié)果,而只需要確認(rèn)請求被收到。這個(gè)確認(rèn)在后面可以在客戶需要的時(shí)候引用到結(jié)果。

隊(duì)列使客戶可以用異步的方式工作,提供了一個(gè)對客戶請求和響應(yīng)策略上的抽象。另一方面,在同步系統(tǒng)中,請求和回復(fù)沒有區(qū)別,因此也不能分別管理。在異步系統(tǒng)中,客戶請求一個(gè)任務(wù),服務(wù)端響應(yīng)一個(gè)消息,確認(rèn)任務(wù)收到,然后客戶就可以周期性檢查任務(wù)狀態(tài),只在任務(wù)完成之后請求結(jié)果。在客戶等待異步請求完成時(shí),它可以隨意進(jìn)行其他工作,甚至是發(fā)送其他服務(wù)的異步請求。后者是隊(duì)列和消息在分布式系統(tǒng)中應(yīng)用的例子。

隊(duì)列還提供了一些保護(hù),免于服務(wù)中斷和失效。例如,很容易創(chuàng)建一個(gè)高度健壯的隊(duì)列,重試因?yàn)槎虝旱姆?wù)失效帶來的失敗的服務(wù)請求。更好的方法是使用隊(duì)列強(qiáng)制實(shí)行服務(wù)質(zhì)量保障,而不是將客戶直接暴露在間歇的服務(wù)中斷中,導(dǎo)致復(fù)雜的,常常是不一致的客戶端錯(cuò)誤處理。

隊(duì)列是管理大型分布式系統(tǒng)中不同部分的分布式通信的根基。實(shí)現(xiàn)隊(duì)列有很多種方法,
有很多開源的隊(duì)列如RabbitMQ, [ActiveMQ]
(http://activemq.apache.org/), BeanstalkD,但一些也使用了像Zookeeper的服務(wù),甚至是像Redis的數(shù)據(jù)存儲。

1.4 結(jié)論 Conclusion

設(shè)計(jì)高效的、可以快速訪問大量數(shù)據(jù)的系統(tǒng),是一件激動(dòng)人心的事,有很多很好的工具(great tools )帶來了各種新的應(yīng)用。
這一章涉及了幾個(gè)例子,簡單地進(jìn)行了介紹,但后面還有很多很多,會有不斷的創(chuàng)新出現(xiàn)

簡化后的圖片托管架構(gòu)圖-----讀寫分離------帶有冗余的圖片托管應(yīng)用 服務(wù)和數(shù)據(jù)都是有備份的------帶有冗余和分區(qū)的圖片托管應(yīng)用
---cahce -->代理 --負(fù)載均衡器---隊(duì)列

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容