開(kāi)源軟件近年來(lái)已變?yōu)闃?gòu)建一些大型網(wǎng)站的基礎(chǔ)組件宣增。并且伴隨著網(wǎng)站的成長(zhǎng),圍繞著它們架構(gòu)的最佳實(shí)踐和指導(dǎo)準(zhǔn)則已經(jīng)顯露矛缨。這篇文章旨在涉及一些在設(shè)計(jì)大型網(wǎng)站時(shí)需要考慮的關(guān)鍵問(wèn)題和一些為達(dá)到這些目標(biāo)所使用的組件爹脾。上篇文章介紹了Web分布式系統(tǒng)設(shè)計(jì)準(zhǔn)則和基本原理,本文介紹構(gòu)建快速箕昭、可伸縮數(shù)據(jù)訪問(wèn)的組件灵妨。
(上文)談及了在設(shè)計(jì)分布式系統(tǒng)中需要考慮的一些核心問(wèn)題,現(xiàn)在讓我們來(lái)聊聊(比較)困難的部分:訪問(wèn)數(shù)據(jù)的可伸縮性落竹。大多數(shù)簡(jiǎn)單的web應(yīng)用泌霍,例如LAMP棧應(yīng)用,看上去如圖1.5
隨著它們的成長(zhǎng)述召,會(huì)有兩個(gè)主要的挑戰(zhàn):訪問(wèn)應(yīng)用服務(wù)器和數(shù)據(jù)庫(kù)的可伸縮性朱转。在一個(gè)高可伸縮的應(yīng)用設(shè)計(jì)中,應(yīng)用(或者web)服務(wù)器通常會(huì)最小化(minimized)并通常表現(xiàn)為一個(gè)非共享(無(wú)狀態(tài))架構(gòu)积暖。這樣使得系統(tǒng)的應(yīng)用服務(wù)層能夠很好地進(jìn)行伸縮藤为。這樣數(shù)據(jù)的結(jié)果是,壓力被向下推到了數(shù)據(jù)庫(kù)服務(wù)器和相關(guān)(底層)支持服務(wù)呀酸;真正的伸縮和性能挑戰(zhàn)就在這一層起到作用凉蜂。本章余下部分致力于(介紹)一些更加通用的策略和方法琼梆,通過(guò)更快的數(shù)據(jù)訪問(wèn)使得這些類(lèi)型的服務(wù)更加快速和可伸縮性誉。
大多數(shù)系統(tǒng)可以極度簡(jiǎn)化為像圖1.6這樣的。這是一個(gè)很好的開(kāi)始茎杂。如果你有大量的數(shù)據(jù)且希望快速错览、簡(jiǎn)單地訪問(wèn),就像你把糖果藏在你桌子第一個(gè)抽屜里煌往。雖然被極度簡(jiǎn)化倾哺,前面觀點(diǎn)仍暗示著兩個(gè)難題:存儲(chǔ)的可伸縮性和數(shù)據(jù)的快速訪問(wèn)。
為了本節(jié)刽脖,我們假設(shè)你有數(shù)以TB計(jì)的數(shù)據(jù)并且希望能讓用戶(hù)隨機(jī)訪問(wèn)這些數(shù)據(jù)的一小部分羞海。(見(jiàn)圖1.7)這就類(lèi)似于在圖片應(yīng)用例子里定位文件服務(wù)器上一個(gè)圖片文件的位置。
由于很難將TB級(jí)的數(shù)據(jù)加載到內(nèi)存曲管,所以這會(huì)使得事情變得非常有挑戰(zhàn)性却邓;這(種訪問(wèn))將直接變?yōu)榇疟P(pán)IO操作。從磁盤(pán)讀取會(huì)比從內(nèi)存要慢得多——訪問(wèn)內(nèi)存就像Chuck Norris一樣快院水,然而訪問(wèn)磁盤(pán)比DMV線還要慢腊徙。這樣的速度差異對(duì)于大數(shù)據(jù)來(lái)說(shuō)比較客觀(This speed difference really adds up for large data sets);順序讀方面訪問(wèn)內(nèi)存的速度是訪問(wèn)磁盤(pán)的6倍简十,而在隨機(jī)讀方面,前者是后者的十萬(wàn)倍(參見(jiàn)”The Pathologies of Big Data”, http://queue.acm.org/detail.cfm?id=1563874)撬腾。而且螟蝙,即使有唯一ID,從哪里能夠找到這樣一小塊數(shù)據(jù)仍然是一項(xiàng)艱巨的任務(wù)民傻。這就好比從你藏糖果的地方不看一眼地想拿到最后一塊Jolly Rancher胰默。
幸運(yùn)的是,你有很多能把事情變得更加容易的選擇漓踢;其中重要的有如下4個(gè):緩存初坠、代理、索引彭雾、負(fù)載均衡碟刺。本節(jié)剩余部分將會(huì)討論每個(gè)用于加速數(shù)據(jù)訪問(wèn)的概念。
緩存
緩存利用了本地引用原則的好處:最近訪問(wèn)的數(shù)據(jù)可能被再次訪問(wèn)薯酝。緩存幾乎被用在計(jì)算機(jī)運(yùn)行的各層:硬件半沽,操作系統(tǒng),web瀏覽器吴菠,web應(yīng)用等等者填。緩存就像短期的內(nèi)存:有著限定大小的空間,但通常比訪問(wèn)原始數(shù)據(jù)源更快做葵,并且包含有最近最多被訪問(wèn)過(guò)的(數(shù)據(jù))項(xiàng)占哟。緩存可以存在于架構(gòu)的各個(gè)層次,但會(huì)發(fā)現(xiàn)到經(jīng)常更靠近前端(非web前端界面酿矢,架構(gòu)上層)榨乎,這樣就可盡快返回?cái)?shù)據(jù)而不用經(jīng)過(guò)繁重的下層(處理)了。
在我們的API例子中瘫筐,如何使用一個(gè)緩存來(lái)加速你的數(shù)據(jù)訪問(wèn)速度呢蜜暑?在這個(gè)場(chǎng)景下,你可以在很多地方插入一個(gè)緩存策肝。選擇之一是在你的請(qǐng)求層節(jié)點(diǎn)中插入一個(gè)緩存肛捍,如圖1.8.
將緩存直接放置在請(qǐng)求層節(jié)點(diǎn)中讓本地存儲(chǔ)響應(yīng)數(shù)據(jù)變?yōu)榭赡堋C看螌?duì)于一個(gè)服務(wù)的請(qǐng)求之众,節(jié)點(diǎn)將立即返回存在的本地拙毫、緩存的數(shù)據(jù)。如果(對(duì)應(yīng)的)緩存不存在棺禾,請(qǐng)求節(jié)點(diǎn)將會(huì)從磁盤(pán)中查詢(xún)數(shù)據(jù)缀蹄。請(qǐng)求層節(jié)點(diǎn)的緩存既可以放置在內(nèi)存(更快)也可以在節(jié)點(diǎn)本地磁盤(pán)(比通過(guò)網(wǎng)絡(luò)快)上。
當(dāng)你擴(kuò)展到多個(gè)節(jié)點(diǎn)時(shí),會(huì)發(fā)生什么呢袍患?正如你看到的圖1.9坦康,如果請(qǐng)求曾擴(kuò)展到多個(gè)節(jié)點(diǎn),那么每個(gè)節(jié)點(diǎn)都可以擁有它自身的緩存诡延。但是滞欠,如果你的負(fù)載均衡器將請(qǐng)求隨機(jī)分發(fā)到這些節(jié)點(diǎn)上,同樣的請(qǐng)求會(huì)到達(dá)不同的節(jié)點(diǎn)肆良,就會(huì)提高緩存miss率筛璧。兩種克服這種困難的方法是:全局緩存和分布式緩存。
全局緩存
正如聽(tīng)起來(lái)的一樣惹恃,全局緩存是指:所有節(jié)點(diǎn)使用同一緩存空間夭谤。這包括增加一臺(tái)服務(wù)器或是某種類(lèi)型的文件存儲(chǔ),比從你原始存儲(chǔ)地方(訪問(wèn))更快巫糙,并且所有請(qǐng)求層的節(jié)點(diǎn)均可以訪問(wèn)(全局緩存)朗儒。所有請(qǐng)求節(jié)點(diǎn)統(tǒng)一像訪問(wèn)其本地緩存般訪問(wèn)(全局)緩存。這種類(lèi)型的緩存機(jī)制可能會(huì)變得比較復(fù)雜参淹,因?yàn)殡S著客戶(hù)端和請(qǐng)求數(shù)量的增加醉锄,單個(gè)緩存(服務(wù)器)很容易被壓垮,但是在一些架構(gòu)中非常有效(特別是有專(zhuān)門(mén)定制的硬件使得訪問(wèn)全局緩存非痴阒担快速恳不,或者需要緩存的數(shù)據(jù)集是固定的)。
通常有兩種形式的全局緩存开呐,如下圖烟勋。圖1.10中,如果緩存中找不到對(duì)應(yīng)的響應(yīng)筐付,那緩存自身會(huì)去從下層存儲(chǔ)中獲取丟失的數(shù)據(jù)卵惦。在圖1.11中,當(dāng)緩存中找不到相應(yīng)數(shù)據(jù)時(shí)家妆,需要請(qǐng)求節(jié)點(diǎn)自己去獲取數(shù)據(jù)鸵荠。
【譯者注】第一種方式相當(dāng)于是全局緩存將查詢(xún)緩存伤极、底層獲取數(shù)據(jù)、填充緩存這些操作一并做掉姨伤,理想情況下對(duì)于上層應(yīng)用應(yīng)該只需要提供一個(gè)獲取數(shù)據(jù)的API哨坪,上層應(yīng)用無(wú)需關(guān)心所請(qǐng)求的數(shù)據(jù)是已存在于緩存中的還是從底層存儲(chǔ)中獲取的,能夠更專(zhuān)注于上層業(yè)務(wù)邏輯乍楚,但這就可能需要這種全局緩存設(shè)計(jì)成能夠根據(jù)傳入API接口的參數(shù)去獲取底層存儲(chǔ)的數(shù)據(jù)当编,譯者認(rèn)為接口簽名可以簡(jiǎn)化為Object getData(String uniqueId, DataRetrieveCallback callback),第一個(gè)參數(shù)代表與緩存約定的唯一標(biāo)示一個(gè)數(shù)據(jù)的ID徒溪,第二個(gè)是一個(gè)獲取數(shù)據(jù)回調(diào)接口忿偷,具體實(shí)現(xiàn)由調(diào)用該接口的業(yè)務(wù)端來(lái)實(shí)現(xiàn)金顿,即當(dāng)全局緩存中未找到uniqueId對(duì)應(yīng)的緩存數(shù)據(jù)時(shí),那就會(huì)以該callback去獲取數(shù)據(jù)鲤桥,并以u(píng)niqueId為key揍拆、callback獲取數(shù)據(jù)為value放入全局緩存中。第二種方式相對(duì)來(lái)說(shuō)自由一些茶凳。請(qǐng)求節(jié)點(diǎn)自行根據(jù)業(yè)務(wù)場(chǎng)景需求來(lái)決定查詢(xún)數(shù)據(jù)的方式嫂拴,以及查數(shù)據(jù)后的處理(比如緩存回收策略),全局緩存只作為一個(gè)基礎(chǔ)組件讓請(qǐng)求節(jié)點(diǎn)能夠在其中存取數(shù)據(jù)贮喧。
大多數(shù)應(yīng)用傾向于通過(guò)第一種方式使用全局緩存筒狠,由緩存自身來(lái)管理回收、獲取數(shù)據(jù)箱沦,來(lái)應(yīng)對(duì)從客戶(hù)端發(fā)起的對(duì)同一數(shù)據(jù)的眾多請(qǐng)求辩恼。但是,對(duì)于一些場(chǎng)景來(lái)說(shuō)谓形,第二種實(shí)現(xiàn)就比較有意義运挫。比如,如果是用來(lái)緩存大型文件套耕,那緩存低命中率將會(huì)導(dǎo)致緩存緩沖區(qū)被緩存miss給壓垮谁帕;在這種情況下,緩存中緩存大部分?jǐn)?shù)據(jù)集(或熱門(mén)數(shù)據(jù))將會(huì)有助解決這個(gè)問(wèn)題冯袍。另一個(gè)例子是匈挖,一個(gè)架構(gòu)中緩存的文件是靜態(tài)、不應(yīng)回收的康愤。(這可能跟應(yīng)用對(duì)于數(shù)據(jù)延遲的需求有關(guān)——對(duì)于大數(shù)據(jù)集來(lái)說(shuō)儡循,某些數(shù)據(jù)段需要被快速訪問(wèn)——這時(shí)應(yīng)用的業(yè)務(wù)邏輯會(huì)比緩存更懂得回收策略或熱點(diǎn)處理。)
分布式緩存
在一個(gè)分布式緩存中(如圖1.12)征冷,沒(méi)個(gè)節(jié)點(diǎn)擁有部分緩存的數(shù)據(jù)择膝,如果將雜貨店里的冰箱比作一個(gè)緩存,那么一個(gè)分布式緩存好比是將你的食物放在幾個(gè)不同的地方——你的冰箱检激、食物柜肴捉、午餐飯盒里——非常便于取到快餐的地方而無(wú)需跑一趟商店。通常這類(lèi)緩存使用一致性Hash算法進(jìn)行切分叔收,這樣一個(gè)請(qǐng)求節(jié)點(diǎn)在查詢(xún)指定數(shù)據(jù)時(shí)齿穗,可以很快知道去哪里查詢(xún),并通過(guò)分布式緩存來(lái)判斷數(shù)據(jù)可用性饺律。這種場(chǎng)景下窃页,每個(gè)節(jié)點(diǎn)都會(huì)擁有一部分緩存,并且會(huì)將請(qǐng)求傳遞到其他節(jié)點(diǎn)來(lái)獲取數(shù)據(jù),最后才到原始地方查詢(xún)數(shù)據(jù)脖卖。因此乒省,分布式緩存的一個(gè)優(yōu)勢(shì)就是通過(guò)往請(qǐng)求池里增加節(jié)點(diǎn)來(lái)擴(kuò)大緩存空間。
分布式緩存的一個(gè)缺點(diǎn)在于節(jié)點(diǎn)丟失糾正問(wèn)題畦木。一些分布式緩存通過(guò)將復(fù)制數(shù)據(jù)多份存放在不同的節(jié)點(diǎn)來(lái)解決這個(gè)問(wèn)題袖扛;但是,你可以想象到這樣做會(huì)讓邏輯迅速變得復(fù)雜馋劈,特別是當(dāng)你向請(qǐng)求層增加或減少節(jié)點(diǎn)的時(shí)候攻锰。雖然一個(gè)節(jié)點(diǎn)丟失并且緩存失效,但請(qǐng)求仍然可以從源頭來(lái)獲燃宋怼(數(shù)據(jù))——所以這不一定是最悲劇的娶吞。
緩存的偉大之處在于它們讓事情進(jìn)行的更快(當(dāng)然需要執(zhí)行正確)。你所選擇的方法只是讓你能夠更快處理更多的請(qǐng)求械姻。但是妒蛇,這些緩存是以需要維護(hù)更多存儲(chǔ)空間為代價(jià)的,特別是昂貴的內(nèi)存方式楷拳;天下沒(méi)有免費(fèi)的午餐绣夺。緩存讓事情變得更快,同時(shí)還保證了高負(fù)載條件下系統(tǒng)的功能欢揖,否則(系統(tǒng))服務(wù)可能早已降級(jí)陶耍。
一個(gè)非常受歡迎的開(kāi)源緩存叫做Memcached(http://memcached.org/)(既可以是本地又可以是分布式緩存);但是她混,還有很多其他選擇(包括許多語(yǔ)言/框架特定選擇)烈钞。Memcached被應(yīng)用于許多大型web網(wǎng)站,縱然它功能強(qiáng)大坤按,但它簡(jiǎn)單來(lái)說(shuō)就是一個(gè)內(nèi)存key-value存儲(chǔ)毯欣,對(duì)任意數(shù)據(jù)存儲(chǔ)和快速查找做了優(yōu)化(時(shí)間復(fù)雜度O(1))。
Facebook使用了若干種不同類(lèi)型的緩存以達(dá)到他們網(wǎng)站的性能(要求臭脓,參加see “Facebook caching and performance“)酗钞。他們?cè)谡Z(yǔ)言層面使用$GLOBALS和APC緩存(在PHP中提供的函數(shù)調(diào)用)使得中間功能調(diào)用和(得到)結(jié)果更加快速。(大多數(shù)語(yǔ)言都有這種類(lèi)型的類(lèi)庫(kù)來(lái)提高web性能来累,應(yīng)該經(jīng)常去使用砚作。)Facebook使用一種全局緩存,分布在多臺(tái)服務(wù)器上(參見(jiàn)”Scaling memcached at Facebook“)佃扼,這樣一個(gè)訪問(wèn)緩存的函數(shù)調(diào)用就會(huì)產(chǎn)生很多并行請(qǐng)求來(lái)從Memcached服務(wù)器(集群)獲取數(shù)據(jù)偎巢。這使得他們能夠在用戶(hù)概況數(shù)據(jù)上獲得更高的性能和吞吐量,并且有一個(gè)集中的地方去更新數(shù)據(jù)(當(dāng)你運(yùn)行著數(shù)以千計(jì)的服務(wù)器時(shí)兼耀,緩存失效、管理一致性都將變得很有挑戰(zhàn),所以這是很重要的)瘤运。
現(xiàn)在讓我們來(lái)聊聊當(dāng)數(shù)據(jù)不存在于緩存的時(shí)候應(yīng)該做什么窍霞。
代理
從基本層面來(lái)看,代理服務(wù)器是硬件/軟件的一個(gè)中間層拯坟,用于接收從客戶(hù)端發(fā)起的請(qǐng)求并傳遞到后端服務(wù)器但金。通常來(lái)說(shuō),代理是用來(lái)過(guò)濾請(qǐng)求郁季、記錄請(qǐng)求日志或者有時(shí)對(duì)請(qǐng)求進(jìn)行轉(zhuǎn)換(增加/去除頭文件冷溃,加密/解密或者進(jìn)行壓縮)。
代理同樣能夠極大幫助協(xié)調(diào)多個(gè)服務(wù)器的請(qǐng)求梦裂,有機(jī)會(huì)從系統(tǒng)的角度來(lái)優(yōu)化請(qǐng)求流量似枕。使用代理來(lái)加快數(shù)據(jù)訪問(wèn)速度的方式之一是將多個(gè)同種請(qǐng)求集中放到一個(gè)請(qǐng)求中,然后將單個(gè)結(jié)果返回到請(qǐng)求客戶(hù)端年柠。這就叫做壓縮轉(zhuǎn)發(fā)(原文叫做collapsed forwarding)凿歼。
假設(shè)在幾個(gè)節(jié)點(diǎn)上存在對(duì)同樣數(shù)據(jù)的請(qǐng)求(我們叫它littleB),并且這份數(shù)據(jù)不在緩存里冗恨。如果請(qǐng)求通過(guò)代理路由答憔,那么這些請(qǐng)求可以被壓縮為一個(gè),就意味著我們只需要從磁盤(pán)讀取一次littleB即可掀抹。(見(jiàn)圖1.14)這種設(shè)計(jì)是會(huì)帶來(lái)一定的開(kāi)銷(xiāo)虐拓,因?yàn)槊總€(gè)請(qǐng)求都會(huì)產(chǎn)生更高的延遲(跟不用代理相比),并且一些請(qǐng)求會(huì)因?yàn)橐c相同請(qǐng)求合并而產(chǎn)生一些延遲傲武。但這種做法在高負(fù)載的情況下提高系統(tǒng)性能蓉驹,特別是當(dāng)相同的數(shù)據(jù)重復(fù)被請(qǐng)求。這很像緩存谱轨,但不用像緩存那樣存儲(chǔ)數(shù)據(jù)/文件戒幔,而是優(yōu)化了對(duì)那些文件的請(qǐng)求或調(diào)用,并且充當(dāng)那些客戶(hù)端的代理土童。
例如诗茎,在局域網(wǎng)(LAN)代理中,客戶(hù)端不需有自己的IP來(lái)連接互聯(lián)網(wǎng)献汗,而局域網(wǎng)會(huì)將對(duì)同樣內(nèi)容的客戶(hù)端請(qǐng)求進(jìn)行壓縮敢订。這里可能很容易產(chǎn)生困惑,因?yàn)樵S多代理同樣也是緩存(因?yàn)樵谶@里放一個(gè)緩存很合理)罢吃,但不是所有緩存都能充當(dāng)代理楚午。
另一個(gè)使用代理的好方法是,不單把代理用來(lái)壓縮對(duì)同樣數(shù)據(jù)的請(qǐng)求尿招,還可以用來(lái)壓縮對(duì)那些在原始存儲(chǔ)中空間上緊密聯(lián)系的數(shù)據(jù)(磁盤(pán)連續(xù)塊)的請(qǐng)求矾柜。使用這一策略最大化(利用)所請(qǐng)求數(shù)據(jù)的本地性阱驾,可以減少請(qǐng)求延遲。例如怪蔑,我們假設(shè)一群節(jié)點(diǎn)請(qǐng)求B的部分(數(shù)據(jù)):B1里覆, B2,等缆瓣。我們可以對(duì)代理進(jìn)行設(shè)置使其能夠識(shí)別出不同請(qǐng)求的空間局部性喧枷,將它們壓縮為單個(gè)請(qǐng)求并且只返回bigB,最小化對(duì)原始數(shù)據(jù)的讀取操作弓坞。(見(jiàn)圖1.15)當(dāng)你隨機(jī)訪問(wèn)TB級(jí)的數(shù)據(jù)時(shí)隧甚,這樣會(huì)大幅改變(降低)請(qǐng)求時(shí)間。在高負(fù)載情況下或者當(dāng)你只有有限的緩存渡冻,代理是非常有幫助的戚扳,因?yàn)榇砜梢詮母旧蠈⑷舾蓚€(gè)請(qǐng)求合并為一個(gè)。
你完全可以一并使用代理和緩存菩帝,但通常最好將緩存放在代理之前使用咖城,正如在馬拉松賽跑中最好讓跑得快的選手跑在前面。這是因?yàn)榫彺嫱ㄟ^(guò)內(nèi)存來(lái)提供數(shù)據(jù)非澈羯荩快速宜雀,并且它也不關(guān)心多個(gè)對(duì)同樣結(jié)果的請(qǐng)求。但如果緩存被放在代理服務(wù)器的另一邊(后面)握础,那在每個(gè)請(qǐng)求訪問(wèn)緩存前就會(huì)有額外的延遲辐董,這會(huì)阻礙系統(tǒng)性能。
如果你在尋找一款代理想要加入到你的系統(tǒng)中禀综,那有很多選擇可供考慮简烘;Squid和Varnish都是經(jīng)過(guò)路演并廣泛應(yīng)用于很多網(wǎng)站的生產(chǎn)環(huán)境中。這些代理方案做了很多優(yōu)化來(lái)充分使用客戶(hù)端與服務(wù)端的通信定枷。安裝其中之一并在web服務(wù)器層將其作為一個(gè)反向代理(將在下面的負(fù)載均衡小節(jié)解釋?zhuān)┛梢蕴岣遷eb服務(wù)器相當(dāng)大的性能孤澎,降低處理來(lái)自客戶(hù)端的請(qǐng)求所消耗的工作量。
索引
使用索引來(lái)加快訪問(wèn)數(shù)據(jù)已經(jīng)是優(yōu)化數(shù)據(jù)訪問(wèn)性能眾所周知的策略欠窒;可能更多來(lái)自數(shù)據(jù)庫(kù)覆旭。索引是以增加存儲(chǔ)開(kāi)銷(xiāo)和減慢寫(xiě)入速度(因?yàn)槟惚仨毻瑫r(shí)寫(xiě)入數(shù)據(jù)并更新索引)的代價(jià)來(lái)得到更快讀取的好處。
就像對(duì)于傳統(tǒng)的關(guān)系數(shù)據(jù)庫(kù)岖妄,你同樣可以將這種概念應(yīng)用到大數(shù)據(jù)集上型将。索引的訣竅在于你必須仔細(xì)考慮你的用戶(hù)會(huì)如何使用你的數(shù)據(jù)。對(duì)于TB級(jí)但單項(xiàng)數(shù)據(jù)比較屑雠啊(比如1KB七兜,原文這里寫(xiě)的是small payload)的數(shù)據(jù)集,索引是優(yōu)化數(shù)據(jù)訪問(wèn)非常必要的方式福扬。在一個(gè)大數(shù)據(jù)集中尋找一個(gè)小單元是非常困難的腕铸,因?yàn)槟悴豢赡茉谝粋€(gè)可接受的時(shí)間里遍歷這么大的數(shù)據(jù)惜犀。并且,像這么一個(gè)大數(shù)據(jù)集很有可能是分布在幾個(gè)(或更多)物理設(shè)備上——這就意味著你需要有方法能夠找到所要數(shù)據(jù)正確的物理位置恬惯。索引是達(dá)到這個(gè)的最好方法向拆。
索引可以像一張可以引導(dǎo)你至所要數(shù)據(jù)位置的表格來(lái)使用亚茬。例如酪耳,我們假設(shè)你在尋找B的part2數(shù)據(jù)——你將如何知道到哪去找到它?如果你有一個(gè)按照數(shù)據(jù)類(lèi)型(如A,B,C)排序好的索引刹缝,它會(huì)告訴你數(shù)據(jù)B在哪里碗暗。然后你查找到位置,然后讀取你所要的部分梢夯。(見(jiàn)圖1.16)這些索引通常存放在內(nèi)存中言疗,或者在更靠近客戶(hù)端請(qǐng)求的地方。伯克利數(shù)據(jù)庫(kù)(BDBs)和樹(shù)形數(shù)據(jù)結(jié)構(gòu)經(jīng)常用來(lái)有序地存儲(chǔ)數(shù)據(jù)颂砸,非常適合通過(guò)索引來(lái)訪問(wèn)噪奄。
索引經(jīng)常會(huì)有很多層,類(lèi)似一個(gè)map人乓,將你從一個(gè)地方引導(dǎo)至另一個(gè)勤篮,以此類(lèi)推,直到你獲取到你所要的那份數(shù)據(jù)色罚。(見(jiàn)圖1.17)
索引也可以用來(lái)對(duì)同樣的數(shù)據(jù)創(chuàng)建出一些不同的視圖碰缔。對(duì)于大數(shù)據(jù)集來(lái)說(shuō),通過(guò)定義不同的過(guò)濾器和排序是一個(gè)很好的方式戳护,而不需要?jiǎng)?chuàng)建很多額外數(shù)據(jù)拷貝金抡。
例如,假設(shè)之前的圖片托管系統(tǒng)就是在管理書(shū)頁(yè)上的圖片腌且,并且服務(wù)能夠允許客戶(hù)端查詢(xún)圖片中的文字梗肝,按照標(biāo)題搜索整本書(shū)的內(nèi)容,就像搜索引擎允許你搜索HTML內(nèi)容一樣铺董。這種場(chǎng)景下巫击,所有書(shū)中的圖片需要很多很多的服務(wù)器去存儲(chǔ)文件,查找到其中一頁(yè)渲染給用戶(hù)將會(huì)是比較復(fù)雜的柄粹。首先喘鸟,對(duì)需要易于查詢(xún)的任意單詞、詞組進(jìn)行倒排索引驻右;然后挑戰(zhàn)在于導(dǎo)航至那本書(shū)具體的頁(yè)面什黑、位置并獲取到正確的圖片。所以堪夭,在這一場(chǎng)景愕把,倒排索引將會(huì)映射到一個(gè)位置(比如B書(shū))越除,然后B可能會(huì)包含每個(gè)部分的所有單詞霞捡、位置、出現(xiàn)次數(shù)的索引。倒排索引可能如同下圖——每個(gè)單詞或詞組會(huì)提供一個(gè)哪些書(shū)包含它的索引漓库。
這種中間索引看上去都類(lèi)似,僅會(huì)包含單詞驹溃、位置和B的一些信息垄分。這種嵌套索引的架構(gòu)允許每個(gè)索引占用更少的空間而非將所有的信息存放在一個(gè)巨大的倒排索引中。
在大型可伸縮的系統(tǒng)中计福,即使索引已被壓縮但仍會(huì)變得很大跌捆,不易存儲(chǔ)。在這個(gè)系統(tǒng)里象颖,我們假設(shè)世界上有很多書(shū)——100,000,000本——并且每本書(shū)僅有10頁(yè)(為了便于計(jì)算)佩厚,每頁(yè)有250個(gè)單詞,這就意味著一共有2500億個(gè)單詞说订。如果我們假設(shè)平均每個(gè)單詞有5個(gè)字符抄瓦,每個(gè)字符占用8個(gè)比特,每個(gè)單詞5個(gè)字節(jié)陶冷,那么對(duì)于僅包含每個(gè)單詞的索引的大小就達(dá)到TB級(jí)钙姊。所以你會(huì)發(fā)現(xiàn)創(chuàng)建像一些如詞組、數(shù)據(jù)位置埃叭、出現(xiàn)次數(shù)之類(lèi)的其他信息的索引將會(huì)增長(zhǎng)得更快摸恍。
創(chuàng)建這些中間索引并且以更小的方式表達(dá)數(shù)據(jù),將大數(shù)據(jù)的問(wèn)題變得易于處理赤屋。數(shù)據(jù)可以分布在多臺(tái)服務(wù)器但仍可以快速訪問(wèn)立镶。索引是信息獲取的基石,也是當(dāng)今現(xiàn)代搜索引擎的基礎(chǔ)类早。當(dāng)然媚媒,這一小節(jié)僅僅是揭開(kāi)表面,為了把索引變得更小涩僻、更快缭召、包含更多信息(比如關(guān)聯(lián))、無(wú)縫更新逆日,還有大量的研究工作要做嵌巷。(還有一些可管理性方面的挑戰(zhàn),比如競(jìng)爭(zhēng)條件室抽、增加或修改數(shù)據(jù)所帶來(lái)的更新操作搪哪,特別是再加上關(guān)聯(lián)、scoring)
能夠快速坪圾、簡(jiǎn)單地找到你的數(shù)據(jù)非常重要晓折;索引是達(dá)到這一目標(biāo)非常有效惑朦、簡(jiǎn)單的工具。
負(fù)載均衡
另一個(gè)任何分布式系統(tǒng)的關(guān)鍵組件是負(fù)載均衡器漓概。負(fù)載均衡器是任何架構(gòu)的關(guān)鍵部分漾月,用于將負(fù)載分?jǐn)傇谝恍┝胸?fù)責(zé)服務(wù)請(qǐng)求的節(jié)點(diǎn)上。這使得一個(gè)系統(tǒng)的多個(gè)節(jié)點(diǎn)能夠?yàn)橄嗤δ芴峁┓?wù)胃珍。(見(jiàn)圖1.18)它們主要目的是處理許多同時(shí)進(jìn)行的連接并將這些連接路由到其中的一個(gè)請(qǐng)求節(jié)點(diǎn)上梁肿,使得系統(tǒng)能夠可伸縮地通過(guò)增加節(jié)點(diǎn)來(lái)服務(wù)更多請(qǐng)求。
有很多不同的用于服務(wù)請(qǐng)求的算法堂鲜,包括隨機(jī)挑選一個(gè)節(jié)點(diǎn)栈雳、循環(huán)(round robin)或給予某些標(biāo)準(zhǔn)如內(nèi)存/CPU使用率選取節(jié)點(diǎn)。一個(gè)廣泛使用的開(kāi)源軟件級(jí)負(fù)載均衡器是HAProxy缔莲。
在一個(gè)分布式系統(tǒng)中,負(fù)責(zé)均衡器通常是放置在系統(tǒng)很前端的地方霉旗,這樣就能路由所有進(jìn)入(系統(tǒng))的請(qǐng)求痴奏。在一個(gè)復(fù)雜的分布式系統(tǒng)中,一個(gè)請(qǐng)求被多個(gè)負(fù)載均衡器路由也不是不可能厌秒。(見(jiàn)圖1.19)
如同代理一般读拆,一些負(fù)載均衡器也能根據(jù)不同類(lèi)型的請(qǐng)求進(jìn)行路由。(從技術(shù)上來(lái)說(shuō)鸵闪,就是所謂的反向代理檐晕。)
負(fù)載均衡器的挑戰(zhàn)之一在于(如何)管理用戶(hù)session數(shù)據(jù)。在一個(gè)電子商務(wù)網(wǎng)站蚌讼,當(dāng)你只有一個(gè)客戶(hù)端時(shí)很容易讓用戶(hù)把東西放到他們的購(gòu)物車(chē)并且在不同的訪問(wèn)間保存(這是很重要的辟灰,因?yàn)楫?dāng)用戶(hù)回來(lái)時(shí)很有可能買(mǎi)放在購(gòu)物車(chē)?yán)锏漠a(chǎn)品)。但是篡石,如果一個(gè)用戶(hù)先被路由到一個(gè)session節(jié)點(diǎn)芥喇,然后在他們下次訪問(wèn)時(shí)路由到另一個(gè)不同的節(jié)點(diǎn),那將會(huì)因?yàn)樾鹿?jié)點(diǎn)可能丟失用戶(hù)購(gòu)物車(chē)?yán)锏臇|西而產(chǎn)生不一致凰萨。(如果你精心挑選了6包Mountain Dew放到購(gòu)物車(chē)继控,但當(dāng)你回來(lái)的時(shí)候發(fā)現(xiàn)購(gòu)物車(chē)清空了,你會(huì)不會(huì)很沮喪胖眷?)解決辦法之一通過(guò)粘性session機(jī)制總是將用戶(hù)路由到同一節(jié)點(diǎn)武通,但這樣既很難享受到一些像自動(dòng)failover的可靠機(jī)制了。在這一場(chǎng)景下珊搀,用戶(hù)的購(gòu)物車(chē)總是會(huì)有東西的冶忱,如果他們所對(duì)應(yīng)的粘性節(jié)點(diǎn)不可用了,那么就會(huì)是一個(gè)特殊情況對(duì)于(保存)在那里的東西的假設(shè)就無(wú)效了(當(dāng)然我們希望這種假設(shè)不會(huì)出現(xiàn)在應(yīng)用里)食棕。當(dāng)然朗和,這個(gè)問(wèn)題可以通過(guò)本章中的一些其他策略或者工具來(lái)解決错沽,比如服務(wù),還有一些沒(méi)有提到的(如瀏覽器緩存眶拉、cookie千埃、URL地址重寫(xiě))。
【譯者注】上段中提到的用戶(hù)session問(wèn)題忆植,實(shí)際上在很多大型網(wǎng)站如淘寶放可、支付寶,都是通過(guò)一個(gè)分布式session的中間件來(lái)解決的朝刊。原理其實(shí)很簡(jiǎn)單耀里,比如用戶(hù)登錄了支付寶,那么系統(tǒng)會(huì)給當(dāng)前用戶(hù)分配一個(gè)全局唯一的sessionId并寫(xiě)入到瀏覽器的cookie中拾氓,在后臺(tái)服務(wù)端也會(huì)有專(zhuān)門(mén)的一個(gè)分布式存儲(chǔ)以sessionId為key開(kāi)辟一個(gè)空間存放該用戶(hù)session數(shù)據(jù)冯挎。雖然應(yīng)用都是集群部署方式,但每個(gè)無(wú)狀態(tài)應(yīng)用節(jié)點(diǎn)都會(huì)統(tǒng)一連接到該分布式存儲(chǔ)咙鞍。由于用戶(hù)session數(shù)據(jù)是統(tǒng)一保存在分布式存儲(chǔ)上房官,即對(duì)session數(shù)據(jù)的存取都是發(fā)生在同一個(gè)地方,而非各個(gè)節(jié)點(diǎn)內(nèi)部续滋,所以不會(huì)因?yàn)椴煌恼?qǐng)求路由到不同的應(yīng)用節(jié)點(diǎn)上導(dǎo)致session數(shù)據(jù)不一致的情況翰守。同時(shí),這一方法不會(huì)像sticky session機(jī)制那樣限制了系統(tǒng)的可伸縮性疲酌。如果出現(xiàn)session存取的性能問(wèn)題蜡峰,那只需通過(guò)擴(kuò)展后端分布式存儲(chǔ)即可解決。如果系統(tǒng)只是由少數(shù)節(jié)點(diǎn)構(gòu)成的朗恳,那么像Round Robin DNS那樣的系統(tǒng)就更加明智湿颅,因?yàn)樨?fù)責(zé)均衡器很貴而且增加了一層不必要的復(fù)雜度。當(dāng)然在大型系統(tǒng)里有各種各樣的調(diào)度和負(fù)載均衡算法僻肖,包括簡(jiǎn)單的像隨機(jī)選擇或循環(huán)方式肖爵,還有更加復(fù)雜的機(jī)制如考慮(系統(tǒng))使用率和容量的。所有這些算法都分布化了流量和請(qǐng)求臀脏,并且提供像自動(dòng)failover或者自動(dòng)去除壞節(jié)點(diǎn)(當(dāng)該節(jié)點(diǎn)失去響應(yīng)后)這類(lèi)對(duì)可靠性非常有幫助的工具劝堪。但是,這些先進(jìn)特性也會(huì)使得問(wèn)題診斷變得復(fù)雜化揉稚。比如秒啦,在一個(gè)高負(fù)載情況下,負(fù)載均衡器會(huì)去除掉那些變慢或者超時(shí)(由于請(qǐng)求過(guò)多)的節(jié)點(diǎn)搀玖,但這樣反而加重了其他節(jié)點(diǎn)的(惡劣)處境余境。在這些情況下,全面監(jiān)控變得很重要,因?yàn)閺娜謥?lái)看系統(tǒng)的流量和吞吐量正在下降(由于各節(jié)點(diǎn)服務(wù)請(qǐng)求越來(lái)越少)芳来,但從節(jié)點(diǎn)個(gè)體來(lái)看正在達(dá)到極限含末。
負(fù)載均衡器是一個(gè)非常簡(jiǎn)單能讓你提高系統(tǒng)容量的方法,并且像本文其他的技術(shù)一樣即舌,在分布式系統(tǒng)架構(gòu)中扮演者重要角色佣盒。負(fù)載均衡器還能用來(lái)判斷一個(gè)節(jié)點(diǎn)的健康度,這樣當(dāng)一個(gè)節(jié)點(diǎn)失去響應(yīng)或者過(guò)載時(shí)顽聂,得益于系統(tǒng)不同節(jié)點(diǎn)的冗余性肥惭,可以將其從請(qǐng)求處理池中去除。
至此紊搪,我們已經(jīng)覆蓋了很多用于加快數(shù)據(jù)讀取的方法蜜葱,另一個(gè)擴(kuò)展數(shù)據(jù)層的重要部分是有效管理寫(xiě)入操作。當(dāng)系統(tǒng)比較簡(jiǎn)單耀石,系統(tǒng)處理負(fù)載很低牵囤,數(shù)據(jù)庫(kù)也很小,可以預(yù)見(jiàn)寫(xiě)入操作是很快的娶牌;但是奔浅,在更加復(fù)雜的系統(tǒng)中,寫(xiě)入操作的時(shí)間可能無(wú)法確定诗良。例如,數(shù)據(jù)需要被寫(xiě)入到不同服務(wù)器或索引的多個(gè)地方鲁驶,或者系統(tǒng)負(fù)載很高鉴裹。這些情況下,由于上面的原因钥弯,寫(xiě)操作或者任何任務(wù)都會(huì)花費(fèi)很長(zhǎng)的時(shí)間径荔,這時(shí)需要異步化系統(tǒng)才能提高系統(tǒng)的性能和可靠性;通常的方法之一是使用隊(duì)列脆霎。
假設(shè)在一個(gè)系統(tǒng)中总处,每個(gè)客戶(hù)端在請(qǐng)求遠(yuǎn)程服務(wù)來(lái)處理任務(wù)。每個(gè)客戶(hù)端將其請(qǐng)求送至服務(wù)器睛蛛,服務(wù)器盡可能快地完成這些任務(wù)并返回結(jié)果給相應(yīng)的客戶(hù)端鹦马。在小型系統(tǒng)中,當(dāng)一臺(tái)服務(wù)器(或者邏輯上的一個(gè)服務(wù))可以盡快地服務(wù)到來(lái)的客戶(hù)端(請(qǐng)求)忆肾,這種情況下(系統(tǒng))工作會(huì)比較好荸频。但是,當(dāng)服務(wù)器接收到超過(guò)其處理能力的請(qǐng)求時(shí)客冈,那每個(gè)客戶(hù)端都只能被迫等待其他客戶(hù)端請(qǐng)求完成才能得到響應(yīng)旭从。圖1.20描繪的就是一個(gè)同步請(qǐng)求的例子。
這種同步的方式將會(huì)嚴(yán)重降低客戶(hù)端性能;客戶(hù)端被強(qiáng)制等待和悦,在請(qǐng)求被響應(yīng)前什么都做不了退疫。增加額外的服務(wù)器并不能解決這個(gè)問(wèn)題;即使通過(guò)有效的負(fù)載均衡鸽素,依然難以保證最大化客戶(hù)端性能所需做的公平分配的工作褒繁。更進(jìn)一步來(lái)說(shuō),當(dāng)處理請(qǐng)求的服務(wù)器不可用或掛掉了付鹿,那么上游的客戶(hù)端同樣也會(huì)失敗澜汤。有效解決這個(gè)問(wèn)題需要抽象化客戶(hù)端的請(qǐng)求和真正服務(wù)它所做的工作。
現(xiàn)在進(jìn)入隊(duì)列環(huán)節(jié)舵匾。一個(gè)隊(duì)列俊抵,正如聽(tīng)上去的,簡(jiǎn)單來(lái)說(shuō)就是當(dāng)一個(gè)任務(wù)過(guò)來(lái)時(shí)坐梯,會(huì)被加入到隊(duì)列中徽诲,然后會(huì)有當(dāng)前有能力處理(任務(wù))的worker去取下一個(gè)任務(wù)來(lái)做。(見(jiàn)圖1.21吵血。)這些任務(wù)可以是對(duì)數(shù)據(jù)庫(kù)的寫(xiě)入操作谎替,或是復(fù)雜一些的如生成文件的小型預(yù)覽圖。當(dāng)一個(gè)客戶(hù)端將任務(wù)的請(qǐng)求提交到隊(duì)列后蹋辅,它們不再需要被迫等待結(jié)果钱贯;取而代之的是,它們只需要確認(rèn)請(qǐng)求被得到正確接收侦另。當(dāng)客戶(hù)端需要的時(shí)候秩命,這個(gè)確認(rèn)此后可以當(dāng)做是任務(wù)結(jié)果的引用。
隊(duì)列使得客戶(hù)端能夠以異步的方式進(jìn)行工作褒傅,至關(guān)重要地抽象了一個(gè)客戶(hù)端請(qǐng)求及其響應(yīng)弃锐。另一方面,一個(gè)同步化系統(tǒng)不會(huì)區(qū)分請(qǐng)求和響應(yīng)殿托,因此就無(wú)法分開(kāi)管理霹菊。在一個(gè)異步化系統(tǒng)里,客戶(hù)端提交任務(wù)請(qǐng)求支竹,后端服務(wù)反饋一個(gè)收到任務(wù)的確認(rèn)信息旋廷,并且客戶(hù)端可以定期地查看任務(wù)的狀態(tài),一旦完成即可取得任務(wù)結(jié)果唾戚。在客戶(hù)端等待一個(gè)異步請(qǐng)求完成時(shí)柳洋,它可以自由地處理其他的工作,即使是發(fā)起對(duì)其他服務(wù)的異步請(qǐng)求叹坦。上面第二個(gè)就是分布式系統(tǒng)中采用隊(duì)列和消息的例子熊镣。
隊(duì)列還能提供對(duì)服務(wù)斷供/失敗的保護(hù)措施。比如,很容易創(chuàng)建一個(gè)健壯的隊(duì)列來(lái)重試那些由于服務(wù)器短暫失敗的服務(wù)請(qǐng)求绪囱。更好的是通過(guò)使用隊(duì)列來(lái)確保服務(wù)品質(zhì)测蹲,而非將客戶(hù)端直接面對(duì)斷斷續(xù)續(xù)的服務(wù),因?yàn)槟菢訒?huì)需要客戶(hù)端復(fù)雜且經(jīng)常不一致的錯(cuò)誤處理鬼吵。
隊(duì)列是管理大型可伸縮分布式應(yīng)用不同部分間通信的基礎(chǔ)扣甲,可以通過(guò)很多方式來(lái)實(shí)現(xiàn)。有一些開(kāi)源的隊(duì)列如RabbitMQ, ActiveMQ, BeanstalkD齿椅,也有一些使用像Zookeeper的服務(wù)琉挖,還有像Redis那樣的數(shù)據(jù)存儲(chǔ)。
【譯者注】隊(duì)列是分布式系統(tǒng)異步化的一個(gè)關(guān)鍵基礎(chǔ)組件涣脚。在淘寶示辈、支付寶這類(lèi)大型分布式網(wǎng)站中應(yīng)用廣泛。正如大家所知的雙十一遣蚀、雙十二矾麻,這兩天用戶(hù)的請(qǐng)求可謂超級(jí)海量。拿支付寶來(lái)說(shuō)芭梯,核心系統(tǒng)如支付险耀、賬務(wù),即使使用了很多技術(shù)方案來(lái)確保高性能玖喘、高可用甩牺,但面對(duì)數(shù)倍、數(shù)十倍于平時(shí)的請(qǐng)求量累奈,依然捉急柴灯。在開(kāi)發(fā)了一套分布式隊(duì)列基礎(chǔ)中間件后,網(wǎng)站的吞吐量费尽、可用性得到了很大的提高。同時(shí)羊始,對(duì)于隊(duì)列來(lái)說(shuō)旱幼,除了將客戶(hù)端請(qǐng)求與服務(wù)端處理分離外,通過(guò)對(duì)隊(duì)列加上額外的一些特性突委,能夠起到非常大的作用柏卤。比如,在隊(duì)列上加入限流特性匀油,當(dāng)請(qǐng)求量大大超過(guò)后端服務(wù)處理能力時(shí)缘缚,可以采取丟棄請(qǐng)求的方式來(lái)保證系統(tǒng)、隊(duì)列不至于被海量請(qǐng)求壓垮敌蚜;當(dāng)請(qǐng)求量回到一定水平桥滨,再將限流放開(kāi)。這種做法,正好滿(mǎn)足了系統(tǒng)對(duì)可用性齐媒、性能蒲每、可伸縮性、可管理性的要求喻括。
總結(jié)
設(shè)計(jì)出能夠快速訪問(wèn)大量數(shù)據(jù)的高效系統(tǒng)(的方法)是存在的邀杏,并且又很多非常棒的工具來(lái)幫助各種各樣的新應(yīng)用來(lái)達(dá)到這一點(diǎn)。本章只覆蓋了少量例子唬血,僅僅是掀開(kāi)了面紗望蜡,但其實(shí)還有更多,并將繼續(xù)保持創(chuàng)新拷恨。