提高數(shù)據(jù)密集型應(yīng)用程序性能的技巧
在大規(guī)模應(yīng)用程序中,數(shù)據(jù)流的重要性很容易被忽視,但是這可能會導(dǎo)致很嚴(yán)重的性能泄漏。在 Shantanu Bhattacharya 撰寫的這一篇文章中,我們將探索可能影響具有多個服務(wù)器的 n 層應(yīng)用程序性能的數(shù)據(jù)流的各個方面。您還會看到在大規(guī)模應(yīng)用程序的設(shè)計與架構(gòu)方面提高性能的一些技巧。
開發(fā)者論壇中對數(shù)據(jù)處理的討論通常都圍繞 applet 和 servlet 展開,而討論焦點往往是一些顯而易見的性能問題,例如觀感、安全性和加載時間。但從一臺機(jī)器傳輸?shù)搅硪慌_機(jī)器的實際數(shù)據(jù)量(通常稱為數(shù)據(jù)流)的問題卻很少被討論到。實際上,數(shù)據(jù)流是一個非常重要的課題,但卻很少被提起,其重要性也沒有得到充分的認(rèn)識;對于大規(guī)模的數(shù)據(jù)密集型應(yīng)用程序來說更是如此。
本文將介紹數(shù)據(jù)流在具有多個服務(wù)器的 n 層應(yīng)用程序中是如何對性能產(chǎn)生影響的。我們將使用一個數(shù)據(jù)流模型來展示一些數(shù)據(jù)可能延緩或阻塞應(yīng)用程序處理的接合點,并解釋如何解決常見的驗證、安全性和數(shù)據(jù)訪問問題。您還會看到一些更高級的設(shè)計和架構(gòu)決策,它們可以在很大程度上提高應(yīng)用程序的性能。另外,我們將對數(shù)據(jù)的集中存儲和分散存儲進(jìn)行評測,這在當(dāng)前的 n 層系統(tǒng)環(huán)境中是一個關(guān)系重大、但尚未得到充分考慮的因素。
數(shù)據(jù)流在任何階段都可能會延緩甚至破壞應(yīng)用程序的運(yùn)行,因此技巧就是預(yù)見問題,在問題出現(xiàn)前就將其解決掉。我將使用一個數(shù)據(jù)流模型來描述最常見的數(shù)據(jù)流瓶頸,以及避免此類瓶頸的一些技巧。圖 1 展示了通過一個具有多個服務(wù)器的典型大規(guī)模 n 層應(yīng)用程序的數(shù)據(jù)流。
圖 1. 大規(guī)模應(yīng)用程序中的數(shù)據(jù)流
下面讓我們來看一下數(shù)據(jù)在哪些地方容易造成程序的延緩,以及您可采取的對策。
- 1. 從客戶機(jī)到 Web 服務(wù)器
- 對于數(shù)據(jù)流來說,這是一個必不可少的步驟;但是在有些情況下,很多通過這個點的數(shù)據(jù)流都是不必要的。例如,在服務(wù)器端(而不是在客戶端)進(jìn)行大量簡單驗證的應(yīng)用就會造成系統(tǒng)速度變慢。理想情況下,我們希望只有在數(shù)據(jù)由客戶機(jī)成功進(jìn)行驗證之后才移動到服務(wù)器上。盡管在某些情況中驗證實際上并不是在客戶端發(fā)生的,但是如果我們對應(yīng)用程序進(jìn)行重組,即可解決這個問題。例如,數(shù)據(jù)驗證通常都被視為業(yè)務(wù)邏輯,因此也會作為服務(wù)器端的一項功能來考慮。實際上,數(shù)據(jù)驗證通常是 特定于數(shù)據(jù)的,因此應(yīng)在客戶端執(zhí)行。
- 2. Web 服務(wù)器到應(yīng)用服務(wù)器
- 如果您希望為客戶機(jī)處理數(shù)據(jù)的呈現(xiàn)規(guī)則,請在服務(wù)器上進(jìn)行。通常,Web 服務(wù)器的用途只是將數(shù)據(jù)傳遞到應(yīng)用層;為掃清大量性能障礙,我們可以改為在 Web 服務(wù)器上處理數(shù)據(jù)。在呈現(xiàn)數(shù)據(jù)的情況中,無論如何都要為應(yīng)用層來處理這些數(shù)據(jù)。盡管在應(yīng)用層上處理數(shù)據(jù)從代碼的角度來看要更加簡單,但這也會造成傳遞的數(shù)據(jù)遠(yuǎn)遠(yuǎn)超過您的需要。多傳輸 10 個字節(jié)看起來不是什么大問題,但是一旦經(jīng)過一百萬次傳輸(在大型應(yīng)用程序中很常見),所傳輸?shù)牟槐匾臄?shù)據(jù)就會多達(dá) 10 MB。這還沒有考慮數(shù)據(jù)所使用的報頭和報尾呢!
- 3. 應(yīng)用服務(wù)器到數(shù)據(jù)庫服務(wù)器
- 請在數(shù)據(jù)到達(dá)數(shù)據(jù)庫服務(wù)器之前完成所有數(shù)據(jù)的處理。這可確保數(shù)據(jù)庫服務(wù)器只需為快速簡單地訪問數(shù)據(jù)而對數(shù)據(jù)進(jìn)行重組即可。它還能確保只有必要的數(shù)據(jù)會到達(dá)數(shù)據(jù)庫服務(wù)器。我們應(yīng)根據(jù)這些考慮事項來確定用于在應(yīng)用層進(jìn)行處理的服務(wù)器個數(shù)。應(yīng)用層中每增加一臺服務(wù)器,不僅會增加硬件成本,而且還會增加數(shù)據(jù)傳輸?shù)呢?fù)載。
- 4. 從數(shù)據(jù)庫服務(wù)器檢索的數(shù)據(jù)
- 按照檢索過程中需要連接數(shù)最少的方法來存儲數(shù)據(jù),這意味著數(shù)據(jù)必須在適當(dāng)?shù)牡胤竭M(jìn)行規(guī)范化(normalization)和反規(guī)范化(denormalization)。稍后我們將更詳細(xì)討論 數(shù)據(jù)的規(guī)范化和反規(guī)范化 問題。
- 5. 從應(yīng)用層返回 Web 服務(wù)器
- 在檢索過程中,只有一個應(yīng)用服務(wù)器應(yīng)在利用之前接觸數(shù)據(jù)流,即使整個系統(tǒng)超過 3 層或應(yīng)用層有多種類型的服務(wù)器也是如此。注意存儲和處理數(shù)據(jù)所使用的路徑都不需要與檢索數(shù)據(jù)的路徑相同。根據(jù)正在編寫的應(yīng)用程序考慮一下要檢索的數(shù)據(jù)類型,這也是非常值得的。例如,在一個基于 Web 的交易站點上,我們很可能在客戶下訂單之后就立即檢索這些訂單(比如用戶希望修改或取消的訂單)。另一方面,如果從下訂單到發(fā)貨之間經(jīng)過了很長時間,我們也可能需要訪問很多數(shù)據(jù)來檢查訂單的狀態(tài)。這些考慮事項都可以引導(dǎo)您以一些原本不會采取的方式優(yōu)化代碼。 #p#page_title#e#
- 6. 在客戶機(jī)上顯示檢索到的數(shù)據(jù)
- 所有為數(shù)據(jù)存儲而進(jìn)行的處理也必須應(yīng)用于數(shù)據(jù)檢索。因此,我們有必要同時編寫編碼和解碼例程,從而盡可能有效地使數(shù)據(jù)檢索與處理相結(jié)合。
一旦您理解了數(shù)據(jù)流有可能在哪些特定點造成性能的大幅度降低,能夠針對此類瓶頸編碼,那么也就有了一個很好的開端。接下來,我們需要運(yùn)用一種更高級的方法,從而避免出現(xiàn)可能導(dǎo)致系統(tǒng)性能下降的設(shè)計和架構(gòu)錯誤??紤]一下大規(guī)模應(yīng)用程序中可能對性能造成影響的一些功能,以及如何設(shè)計才能獲得更好的性能。
如前所述,僅為了進(jìn)行數(shù)據(jù)驗證就將數(shù)據(jù)從客戶機(jī)移動到服務(wù)器上是一個巨大的錯誤。將數(shù)據(jù)從客戶機(jī)發(fā)送到服務(wù)器上,然后由于驗證失敗就拒絕用戶請求,這只會增加所傳輸?shù)牟槐匾獢?shù)據(jù)的總量。最好的選擇是重組應(yīng)用程序,使所有的驗證都在客戶端進(jìn)行。如果不能這樣做 —— 也就是說,如果您必須在服務(wù)器上驗證數(shù)據(jù) —— 至少可以使用諸如 ActiveX® 或 Java™ applet 之類的代碼傳輸機(jī)制來進(jìn)行這種操作。代碼傳輸只會發(fā)生一次,但每次在另外一端遇到驗證問題時都要傳輸數(shù)據(jù)。
如果您所處理的數(shù)據(jù)依賴于已為驗證而存儲的數(shù)據(jù),還要保持那些已存儲的數(shù)據(jù)在客戶端可用。例如,若需要保存一個用戶所提交值的列表,以便與新值比較,那么在客戶機(jī)而不是服務(wù)器上存儲這些數(shù)據(jù)會比較經(jīng)濟(jì)。多個用戶所輸入的數(shù)據(jù)量會很快超過您為驗證而必須傳輸?shù)臄?shù)據(jù)量。
對于這條規(guī)則,確實有例外情況,例如在數(shù)據(jù)庫列中進(jìn)行惟一性測試時。由于無法在客戶端完成這一任務(wù),因此在服務(wù)器上進(jìn)行驗證是有必要的。然而在大部分情況中,在客戶端完成數(shù)據(jù)驗證是最佳實踐。
需要確保安全性的數(shù)據(jù)通常會比不需保護(hù)的數(shù)據(jù)耗費(fèi)更多的投影(Projections)操作。盡管看似違背直覺,但不安全的數(shù)據(jù)所經(jīng)歷的操作往往與安全數(shù)據(jù)完全相同,這只是因為這兩類數(shù)據(jù)都被存儲到了相同的位置。您可通過在數(shù)據(jù)庫中獨立存儲安全數(shù)據(jù)和不安全數(shù)據(jù)來提高應(yīng)用程序的整體性能。
將兩種類型的數(shù)據(jù)獨立存儲無效果的惟一情況就是:安全數(shù)據(jù)量與不安全數(shù)據(jù)量相比非常少,例如,有些應(yīng)用程序中惟一的安全機(jī)制就是用戶密碼。此時可以將這兩類數(shù)據(jù)保存在一起,不過仍然需要確保對安全數(shù)據(jù)所進(jìn)行的操作不會發(fā)生在不安全數(shù)據(jù)上。
訪問頻率是我們在確定存儲標(biāo)準(zhǔn)時需要考慮的一個重要因素。例如,考慮一個醫(yī)院信息系統(tǒng)。在這個系統(tǒng)中,對一位患者統(tǒng)計信息的訪問頻率要遠(yuǎn)遠(yuǎn)低于其姓名和 ID 號。因此,將統(tǒng)計信息與姓名和 ID 數(shù)據(jù)分開存儲是很有意義的。如果將這兩類數(shù)據(jù)存儲在一起,那么每次訪問患者的 ID 和姓名時,數(shù)據(jù)庫都必須執(zhí)行一些操作來過濾掉關(guān)于統(tǒng)計信息的數(shù)據(jù)。
在這個例子中,在一個表中還是在一個數(shù)據(jù)庫中存儲所有這些信息并不重要。如果是在一個表中,那么您必須執(zhí)行一次投影操作來過濾數(shù)據(jù)。如果是在數(shù)據(jù)庫中,若用于脫機(jī)處理的數(shù)據(jù)(例如數(shù)據(jù)倉庫)與用于聯(lián)機(jī)處理的數(shù)據(jù)存儲在一起,就會降低系統(tǒng)速度。如果頻繁訪問的數(shù)據(jù)也會用于脫機(jī)處理,那么這種問題就會更加突出;此時,應(yīng)將這些數(shù)據(jù)存儲在另一個數(shù)據(jù)庫中。
即便在所有的數(shù)據(jù)都依賴于聯(lián)機(jī)事務(wù)處理時,您也可以考慮為某些數(shù)據(jù)使用另外一個數(shù)據(jù)庫。我們可以考慮為某些數(shù)據(jù)使用一個不同的數(shù)據(jù)庫,這樣這個數(shù)據(jù)庫中所有的數(shù)據(jù)都是用于在線事務(wù)處理的了。如果應(yīng)用程序不會同時訪問兩部分?jǐn)?shù)據(jù),而且這兩部分?jǐn)?shù)據(jù)都可能會增長到很大(達(dá)到數(shù) GB 或 TB),那么這是一種很好的策略。這時將數(shù)據(jù)劃分到兩個不同的數(shù)據(jù)庫中會很有幫助。
很多時候您都能夠根據(jù)應(yīng)用程序的當(dāng)前狀態(tài)來預(yù)測其后續(xù)狀態(tài),并且能保證一定的準(zhǔn)確度。例如,在貿(mào)易軟件中,如果用戶查詢某個訂單,那么他很可能將要查看這份訂單的具體細(xì)節(jié)。對于絕大多數(shù)應(yīng)用程序類型,我們都可以觀察到類似的模式。觀察這些模式,然后即可優(yōu)化應(yīng)用程序,方法是:根據(jù)您的預(yù)測算法,提前對某項給定操作后可能會被立即調(diào)用的數(shù)據(jù)進(jìn)行一些操作。 #p#page_title#e#
乍看起來,這種策略似乎在系統(tǒng)中引入了有狀態(tài)性,這可能會導(dǎo)致外擴(kuò)問題,但實際上不會。
這種方法的確可以幫助您優(yōu)化系統(tǒng):只有在這些信息丟失時,系統(tǒng)運(yùn)行速度才會減慢。預(yù)先操作失誤的惟一情況就是所預(yù)測的結(jié)果不可用,例如下一個調(diào)用被路由到其他系統(tǒng)上去了。(這種策略對于那些采用服務(wù)器場的系統(tǒng)也非常有用。在這種情況中,信息可以簡單地存儲到一個跨場中所有服務(wù)器的集中儲存庫內(nèi)。)
我們可以使用一個貿(mào)易應(yīng)用程序的例子來進(jìn)一步闡述這個問題,一名用戶會訪問一個訂單。該請求被發(fā)送到應(yīng)用服務(wù)器場內(nèi) 3 個服務(wù)器中的第 1 個(server1)。應(yīng)用程序預(yù)測這名用戶接下來會訪問同一訂單的詳細(xì)內(nèi)容,并提前獲取了這些數(shù)據(jù)。您可能會將這些數(shù)據(jù)存儲在 server1 上。但是由于 server1 和 server2 繁忙,與該訂單的詳細(xì)信息有關(guān)的后續(xù)請求都被發(fā)送到了 server3 上?,F(xiàn)在,如果與此訂單的詳細(xì)信息有關(guān)的數(shù)據(jù)在 server3 上不可用,那么它就只好自行獲取這些數(shù)據(jù)。盡管這并沒有帶來預(yù)期的優(yōu)化,但是也不會導(dǎo)致出現(xiàn)不一致的狀態(tài),因為訂單的詳細(xì)信息在任何一種情況中都可以獲取到。為了利用這種優(yōu)化機(jī)制,應(yīng)將數(shù)據(jù)保存到場中所有服務(wù)器都可訪問的共享存儲中
從理論上來說,總是建議您保證數(shù)據(jù)的規(guī)范化;但是在實踐中,過度的規(guī)范化(或者沒有恰當(dāng)反規(guī)范化的規(guī)范化)會導(dǎo)致對數(shù)據(jù)檢索連接的依賴性。例如,考慮這樣一種情況:患者姓名總是與其統(tǒng)計信息一起訪問的,而患者 ID 是關(guān)鍵字。此時,將患者姓名與患者統(tǒng)計數(shù)據(jù)存儲在一起是比較明智的(即便規(guī)范化要求與此不符),這樣可以確保我們不需要在每次要訪問患者統(tǒng)計信息時都需要對患者姓名執(zhí)行一次連接操作。如果是這樣,就需要在兩個位置更新患者姓名。但由于更新患者姓名的頻率遠(yuǎn)遠(yuǎn)低于更新患者統(tǒng)計信息的頻率,因此就性能來說,系統(tǒng)依然得到了優(yōu)化。
注意數(shù)據(jù)的規(guī)范化在任何面向數(shù)據(jù)庫的應(yīng)用程序中都是標(biāo)準(zhǔn)實踐。在優(yōu)化系統(tǒng)時,應(yīng)首先從以充分規(guī)范化的數(shù)據(jù)為基礎(chǔ)入手,這一點非常重要;否則,在更新數(shù)據(jù)時,就必須更新其所有實例。
在一個由多個服務(wù)器處理數(shù)據(jù)的 n 層應(yīng)用程序中,聚合非常重要。例如,考慮這樣一種情況,要打印的數(shù)據(jù)在抵達(dá)打印機(jī)之前必須通過多個服務(wù)器。如果不仔細(xì)考慮這個問題,這種類型的設(shè)置可能會造成相關(guān)服務(wù)器和打印進(jìn)程的速度降低,在打印大量數(shù)據(jù)時更是如此。為了解決這個問題,請確保數(shù)據(jù)的使用點要與數(shù)據(jù)存儲點盡可能地接近。(將數(shù)據(jù)移到數(shù)據(jù)庫中時,這種技巧也同樣適用。)
同時訪問的數(shù)據(jù)應(yīng)該總是存儲在一起;分別訪問的數(shù)據(jù)也應(yīng)該獨立存儲。違背這一基本準(zhǔn)則會導(dǎo)致數(shù)據(jù)庫在訪問數(shù)據(jù)時執(zhí)行更多操作。將通常會被一起訪問的數(shù)據(jù)分別存儲將導(dǎo)致更多不必要的連接操作;將通常分別訪問的數(shù)據(jù)存儲在一起則意味著需要執(zhí)行更多投影操作來過濾掉不需要的數(shù)據(jù)。
聯(lián)機(jī)事務(wù)處理(OLTP)與聯(lián)機(jī)分析處理(OLAP)并不相同,兩種技術(shù)所使用的數(shù)據(jù)訪問模式也有很大的區(qū)別。因此,每種處理可能需要不同的數(shù)據(jù)庫 —— 即使這意味著需要將相同的數(shù)據(jù)重復(fù)存儲兩次,也是需要這樣做的。獨立存儲 OLTP 數(shù)據(jù)和 OLAP 數(shù)據(jù)使您可為每種技術(shù)的訪問模式進(jìn)行優(yōu)化。在 表 1 中,您可以看到 OLTP 和 OLAP 數(shù)據(jù)庫間差異的細(xì)目,這些差異主要與每種技術(shù)使用的訪問模式和資源使用情況相關(guān)。
表 1. OLTP 與 OLAP 處理的對比
特性 | OLTP | OLAP |
---|---|---|
訪問模式 | 反復(fù) | 偶爾 |
操作 | 讀寫 | 全表掃描 |
工作單位 | 基于哈?;蜿P(guān)鍵字/索引的簡單查詢 | 復(fù)雜查詢 |
所訪問的記錄數(shù)量 | 數(shù)十或數(shù)百 | 數(shù)百萬(全表) |
大小 | 數(shù)十 MB 到 GB | 數(shù)十 GB 到 TB |
度量標(biāo)準(zhǔn) | 事務(wù)處理吞吐量 | 查詢吞吐量 |
盡管這個問題總是被忽視,但在數(shù)據(jù)密集型應(yīng)用程序中,數(shù)據(jù)流確實會消耗大量帶寬。應(yīng)用程序越大、越復(fù)雜,其中的數(shù)據(jù)移動就越多,設(shè)計時考慮到的問題也必須越復(fù)雜。因此最好建模數(shù)據(jù)流通過應(yīng)用程序的路徑,并考慮清楚常規(guī)應(yīng)用程序功能中可能減緩數(shù)據(jù)流速度的各點。接下來,考慮應(yīng)用程序的架構(gòu),確定可以優(yōu)化哪些地方來提高性能。只要遇到疑問,就回頭來做最基本的事情:檢查數(shù)據(jù)流的路徑,確保應(yīng)用程序在每個接合點處都執(zhí)行良好。
現(xiàn)貨:全球最快的Fusion-io SSD硬盤卡-提升IO速度200倍述評