《架構真經:互聯網技術架構的設計》大道至簡
本節書摘來自華章出版社《架構真經:互聯網技術架構的設計》一書中的第1章,第1節,作者 小象學院 楊 磊,更多章節內容可以訪問雲棲社區“華章計算機”公眾號查看。
大道至簡
無論從哪個角度來看,傑瑞米·金都有一個成功和絢麗的職業生涯。20世紀90年代中期,在互聯網大潮來襲之前,傑瑞米參與了海灣網絡公司SAP項目的成功實施。從此,傑瑞米投身互聯網大潮,任Petopia.com公司的技術副總裁。他經常調侃說,在Petopia.com公司的這段經曆,相當於從“硬漢拓展營大學”取得了“現實世界的工商管理碩士”。離開Petopia.com後,傑瑞米加入eBay,作為總監負責新一代商務平台V3的架構。如果說傑瑞米在Petopia.com完成了財經專業的課程,那麼eBay(後升任副總裁)為他提供了前所未有的係統可擴展性方麵的教育。傑瑞米也曾在LiveOps做過三年常務副總裁,現任沃爾瑪實驗室首席技術官。
eBay為傑瑞米積累了豐富的經驗,包括架構需要簡化。2001年傑瑞米加入了eBay,那時的eBay與少數像亞馬遜和穀歌這樣的公司一樣方興未艾,在線交易初具規模。2001年全年,eBay的商品總銷售額為27.35億美元[1],同期,沃爾瑪在全球的銷售額是1913億美元(未包括在線交易)[2],亞馬遜31.2億美元[3]。然而,在這耀眼的成功背後卻隱藏著eBay黑暗的過去。
1999年6月,一起持續了將近24小時的宕機事件[4]讓eBay幾乎瀕臨死亡宣判。1999年6月宕機事件後的幾個月裏,eBay的網站又接二連三地出現了幾次不同規模的宕機,盡管引起每次宕機的原因有所不同,但是,問題的根源都指向該網站無法應付空前急劇增長的大量用戶請求。這些宕機事件徹底改變了該公司的文化。確切地說,這些事件使eBay關注於以高可用和高可靠的標準來約束其所提供的服務。
1999年,eBay賣出的大部分商品都是以拍賣方式完成的。與常見的在線交易相比,拍賣是一種很獨特的交易方式。首先,拍賣的周期很短;其次,臨近預定的投標截止時刻,往往會出現大量意外的投標(寫交易)和查詢(讀交易)。相對而言,傳統平台上的大多數交易都均勻分布,並帶有典型的季節性;盡管eBay平台可以展示數以百萬計的商品,但在任意給定時刻,全部用戶的活動都集中在少數商品上。這為數據庫的負載帶來了獨特的難題,例如,由於負載主要集中在少數商品上,支撐業務的數據庫(那時是單一數據庫)不得不在物理記錄和邏輯記錄的衝突中苦苦掙紮。這也證實了數據庫是eBay網站反應慢甚至徹底崩潰的原因。
傑瑞米在eBay的第一項任務是帶領團隊重新定義eBay的軟件架構,目標是防止類似1999年6月的宕機事件再次發生。與此同時,還需要考慮業務的飛速增長以及拍賣形式所遭遇的各種困難,使這項任務變得異常複雜。這個內部命名為V3的項目需要采用Java來重構eBay的商務引擎(之前是C++),這次係統重構還可以順便解決數據庫沿X、Y和Z軸分庫的問題(詳見本書第2章)。
該團隊試圖確保係統的各個部分都可以無限擴展,並能以最快的速度來解決任何係統故障,以把故障所帶來的影響降到最低。“我的主要教訓是,”傑瑞米說,“我們試圖把係統各個部分的複雜性和業務的關鍵性與實際的拍賣過程一視同仁。對網站上的各種功能,從圖片展示到eBay用戶評價係統(經常稱為反饋),我們均采取類似的高可靠性解決方案。”
“要知道,”傑瑞米接著說,“2001年那時候幾乎沒有什麼公司經曆過eBay那樣大規模的線上交易。因此,不論是供應商還是開源組織都無法幫助我們解決問題。隻能靠我們自己去發明創造,如果有選擇,我寧可不去做這些事。”
乍一看我們很難梳理出傑瑞米所吸取的經驗教訓。假如所有子係統做得和拍賣子係統一樣堅不可摧呢?傑瑞米笑著說,“其實並不是每個子係統都像拍賣子係統那麼複雜。以用戶評價引擎為例。在短期內,這個子係統並不需要像拍賣子係統那樣需要應付大量的數據資源競爭。因此,也就不需要把同樣的架構原則應用在該係統上。如果該子係統在短期內對某些數據資源的競爭並不像交易那樣嚴重,那麼該係統甚至可以在保持高可用性的情況下更加有效地擴展。更為重要的是,對每個商品的交易,用戶評價子係統都需要進行一次寫操作,而拍賣子係統則可能每秒鍾都需要做幾百次寫操作。與簡單的減庫存不同,這是一個複雜的比較過程,需要把當前的投標價與所有其他的投標價進行比較,然後重新計算出新的投標價。但是,我們卻把解決其他問題與拍賣等量齊觀,特別是要求其他子係統也能夠承受空前大量的用戶請求,事實上,這些請求隻集中在一小部分數據上,而且發生在拍賣的最後幾秒鍾內。”
在弄清楚這個問題後,我們開始思考如果把V3的某些部分做得比其他部分更複雜會帶來什麼影響。“那很容易,”傑瑞米說,“總體來看V3是成功的,如果讓我們重新來過,或許我們有機會以更低的成本或更快的速度完成它或兩者兼顧。此外,因為有些部分過於複雜,換句話說,問題並不需要那麼複雜的解決方案,因而造成這些部分的維護成本比較高。這也是我在沃爾瑪和LiveOps所學習與應用的架構原則:問題的複雜度要與解決問題的方法及成本相匹配。每個問題解決方案的複雜度都不同,要用最簡單的方法取得最佳的效果。從擴展性、可維護性或者可複用性的角度看,擁有一個標準的平台或者開發語言看起來似乎很理想,然而,采用一個基於開源項目、新開發語言或者新平台的簡單解決方案可能會大幅度地降低成本、縮短上市時間,甚至為客戶帶來創新的產品。”
傑瑞米的故事告訴我們不要把簡單的問題複雜化,換句話說,盡量保持問題解決方案的簡單。我們認為複雜問題隻是一個關於有待解決的小而簡問題的集合。本章就討論如何把大事化小,從而事半功倍。
本書的許多章節都會提到規則,這些規則會隨著係統的規模和複雜度而變化。有些比較宏觀,適用於多種不同的設計環境。有些則比較微觀,隻適用於某些特定係統的實施。
規則1——避免過度設計
內容: 在設計中要警惕複雜的解決方案。
場景: 適用於任何項目,而且應在所有大型或者複雜係統或項目的設計過程中使用。
用法: 通過測試同事是否能夠輕鬆地理解解決方案,來驗證是否存在過度設計。
原因: 複雜的解決方案實施成本過高,而且長期的維護費用昂貴。
要點: 過於複雜的係統限製了可擴展性。簡單的係統易維護、易擴展且成本低。
正如在維基百科中解釋的那樣,過度設計有兩大類[5]:第一類指產品的設計和實施超過了實際的需求。出於完整性,我們對此做簡單的討論,與第二類相比,實際上第一類對可擴展性的影響很小。第二類指所完成的產品過於複雜。如前所述,我們更關心第二類過度設計對可擴展性的影響。但是要先討論一下超過實際需求的含義。
為了討論第一類超過實際需求的過度設計,我們首先要解釋一下“實際”兩個字的含義,“實際”就是指“能夠使用”。例如,設計一款家用空調,在室外可以達到熱力學溫度0K,在室內可以達到300℉,這是在浪費資源,毫無必要。與此相對的是,設計和製造一款空調機,能夠在室外-20℉時把室內加熱到可以舒適生活的環境溫度。過度設計有過度使用資源的情況,包括為研發和實施硬件、軟件解決方案付出的較高費用。如果因為過度設計,造成係統的研發周期過長,影響公司產品的發布計劃。這些都會直接影響股東的利益,因為較高的成本導致較低的收益,較長的研發時間會影響公司的收入。對比初始產品定義與產品首次發布,如果項目範圍擴大了,那就是過度設計的一個表現。
一個更接近我們生活的例子是研發一個打卡係統,它可以支撐一個公司相當於地球總人口100倍數量的員工打卡的需求。在打卡軟件的生命周期內,地球人口增長100倍的概率極其渺茫。那麼多人都為一個公司工作的機會也很小。我們當然希望把係統建設得可以擴展以滿足客戶的需求,但是我們並不想浪費時間來實施和配置遠遠超過我們實際需求的係統能力(參見規則2)。
第二類過度設計是指把一件事情做得過於複雜和以複雜的方式去完成一個任務。簡單地說,它包括讓某些事物超過實際需要過度工作,讓用戶費不必要的精力去完成一件事,讓工程師付出很大的努力去理解不必要的需求。下麵將對過度設計的這三個方麵進行深入分析。
讓某些事物超過實際需要過度工作到底意味著什麼?傑瑞米·金提到的研發構成eBay網站的全部功能與拍賣過程的急迫需求,就是一個讓某些事物(例如用戶評價係統)超過實際需求過度工作的最好例子。再舉現實生活中的一些其他例子。假如你打發手下去雜貨店。他同意了,你告訴他每樣東西都拿一件,然後在排隊付款前給你回個電話。電話來了,你告訴他,其實你隻想從那些已經裝滿的購物籃中挑一些東西,讓他把其他不要的東西都放回去。你可能會說:“實際上沒有那麼荒唐!”但是,你是否曾經在代碼中執行過select (*) from schema_name.table_name這樣的SQL語句,然後從結果返回集中選取個別幾行數據呢?(參見第8章中的規則35。)前麵雜貨鋪的例子與select(*)異曲同工。在代碼中,你加了多少行條件判斷來處理小概率事件?你用什麼樣的順序來評估和判斷這些條件?你先處理那些大概率事件嗎?你是否經常讓數據庫再次返回剛剛請求過的結果集?你是否經常請求重新產生剛剛顯示過的HTML頁麵?這樣的問題比比皆是(本可以取最近的正確答案,卻做不必要的重複工作),而且很容易被忽略,為此,我們特別準備了一整章的內容(第6章)來討論此話題。你應該明白我們這麼做的原因了吧!
讓用戶費不必要的精力是什麼意思?在大多數情況下,少即是多。很多時候,為了試圖保持係統的靈活性,我們常會努力把盡可能多且不常用的功能塞進係統裏。生活並不是總需要多樣性的點綴。在大多數情況下,用戶隻想不受任何幹擾,盡快地從A點到達B點。如果市場上99%的用戶根本就不在意能否把微博的內容導入.pdf文件中,那麼就不要去設置一個選擇,問用戶是否要把微博的內容另存為.pdf文件。如果用戶對把文件從.wav格式轉換成MP3格式感興趣,就說明他們已經對音樂保真不在意,那就別再用可轉換高保真壓縮FLAC文件的新功能來分散用戶的注意力了。
最後,我們討論最常見的問題,就是研發者把軟件寫得異常複雜以至於其他的工程師無法輕而易舉地理解。這種做法曾經風靡一時,實際上還有看誰能把代碼寫得複雜到其他人難以理解的比賽。組織者會把獎牌發給那些能把代碼寫到高級研發者在進行代碼複查時欲哭無淚的人。複雜性成了知識分子的囚籠,電腦編程的極客們在這個囚籠裏為爭奪組織的優勢而互相廝殺。那些有興趣一展身手的極客們,通常都遠離實戰,躲在安全屋裏操作以免對股東價值帶來潛在的破壞,建議他們參加國際C語言混亂代碼大賽(詳情參見www0.us.ioccc.org/index.html)。除此之外的人要清楚地認識到自己的工作是研發簡單、易懂的解決方案,並通過這些方案為股東保持和創造價值。
我們都應該努力把代碼寫得通俗易懂。對一個好工程師的真實度量,是看他能多快簡化一個複雜的問題(見規則3),然後構思出一個易於理解並可以維護的解決方案。淺顯易懂的方案可以讓初中級工程師快速上手。淺顯易懂的方案也意味著在係統糾錯過程中可以很快地查出問題,確保係統可以更快地恢複正常。淺顯易懂的方案能夠增強組織和平台的可擴展性。
有一個非常好的驗證辦法可以用來確定方案是否過於複雜,讓負責解決該複雜問題的工程師把自己的解決方案展現給公司內的不同技術團隊。參與活動的技術團隊成員要在經驗水平和公司任期方麵有不同的代表性(區分經驗和任期是因為有些剛加入的工程師,雖然經驗豐富,但是對公司並不太了解)。要想通過這個測試,每個技術團隊都應該能輕鬆地理解方案,並可以在無人協助的情況下,向其他不知情的人描述該方案。如果有任何一個技術團隊對此方案表示不理解,那麼就應該針對該方案是否過於複雜進行深入的辯論。
過度設計是可擴展性的大敵之一。設計一個超過實際需要的方案就是在浪費金錢和時間。更進一步來說,這種方案會浪費係統的處理資源,增加係統擴展的成本,限製係統的整體可用性(係統能擴展到什麼程度)。構建過於複雜的解決方案與此相似。過度工作的係統會增加成本並限製平台最終的規模。那些讓用戶費很多精力的係統,會在增加用戶數量和快速開展業務時遇到瓶頸。複雜到難以理解的係統會扼殺組織的生產力,加大工程師團隊擴大的難度,也提高了為係統增加新功能的難度。
規則2——方案中包括擴展
內容: 提供及時可擴展性的DID方法。
場景: 所有項目通用,是保證可擴展性的最經濟有效的方法(資源和時間)。
用法:
Design (D)設計20倍的容量。
Implement (I)實施3倍的容量。
Deploy (D)部署1.5倍的容量。
原因: DID為產品擴展提供了經濟、有效、及時的方法。
要點: 在早期考慮可擴展性可以幫助團隊節省時間和金錢。在需求發生大約一個月前實施(寫代碼),在客戶蜂擁而至的幾天前部署。
我們公司致力於幫助客戶解決他們的擴展性需求。你可能想象得到,客戶經常問我們,“什麼時候該在可擴展性上投入?”有些輕率的回答是,最好在需要的前一天投入和部署。如果你能夠做到在需要改善可擴展性方案的前一天部署,那麼這筆投資的時機最佳(及時),而且有助於實現公司財務和股東利益的最大化。這與戴爾(Dell)帶給世人的按需訂製係統和準時生產相似。
讓我們麵對現實,諸如及時投入和部署根本就不可能,即使可能,也無法確定具體的時間,而且會帶來很多風險。比在需要前一天投入和部署稍遜一籌的,是AKF合夥公司在思考可擴展性時用的DID(設計-實施-部署)方法。這幾個步驟與眾所周知的認知階段一致:思考問題和設計方案,為方案構建係統和編寫代碼,實際安裝或者部署方案。這種方法既不主張也不需要瀑布模型。我們認為敏捷方法遵守這樣一個過程,顧名思義,也非常需要人類的參與。你無法為自己所不知道的問題設計一個解決方案,而且如果沒有設計,也不可能製造或發布產品。無論哪種開發方法(敏捷、瀑布、混合或其他),我們的每個設計都應該是基於一套定義和指導我們如何設計的架構原則和標準。
設計(D, Design)
我們先從一個概念開始,討論和設計很明顯要比我們在代碼中具體實現該設計成本更低。鑒於成本相對低,我們可以未雨綢繆,討論和草擬好如何擴展平台的設計。例如,我們顯然不想部署比現在的生產環境需要高10倍、20倍甚至100倍的容量。但是,討論和決定如何擴展到這些維度的成本相對來說比較低。然後,在DID方法的D(設計)階段聚焦在擴展到20倍和無限大之間。因為聘請“思想家”來思考“大問題”,所以我們的智力成本很高。然而,由於我們不編寫代碼或部署昂貴的係統,所以技術和資產成本較低。通過召集可擴展性大會,把領導者和工程師團隊聚集在一起,共同討論產品的擴展瓶頸,這是在DID設計階段發現和確定需要擴展部分的一個好辦法。表1-1列出了DID的各個階段。
表1-1 擴展的DID過程
設 計 實 施 部 署
擴展的目標 20倍~無限 3~20倍 1.5~3倍
智力成本 高 中 低到中
工程成本 低 高 中
資產成本 低 低到中 高到很高
總成本 低到中 中 中
實施(I, Implement)
隨著時間的推移,我們逐步接近對未來擴展預想的需求,於是開始編寫軟件實現設計。我們把規模需求的範圍縮小到更接近現實,例如當前規模的3~20倍。“規模”在這裏指被視為擴展最大瓶頸的係統組件,這部分最需要亟待解決以實現業務目標。可能在有些情況下,把當前的規模擴大100倍或更多倍的成本與擴大20倍沒有區別。假設如此,也許我們可以一次完成所需要的改變,而非反複折騰。如果我們要基於用戶屬性取模,然後把用戶分散到多個(N個)數據庫係統,那就會是這種情況。我們可能會定義一個可配置的變量Cust_MOD,其取值範圍是1(現在)~1?000(5年內)。這種改變的實施成本確實不會隨著規模N的變化而變化,所以我們可以把Cust_MOD的取值範圍定義得盡可能大。這類改變以工程時間計算的成本很高,以智力時間計算的成本中等,以資產計算的成本較低,因為隻打算在第一階段部署1或2的模數,就沒有必要在當前部署比現有規模大100倍的係統。
部署(D, Deploy)
DID的最後階段是部署(D)。仍以前麵的取模為例,我們希望以及時的方式部署係統,沒有理由因為資產空閑而稀釋股東的價值。如果是一家適度高增長的公司,也許我們可以把最大產能提高到1.5倍;如果是一家超高增長的公司,也許我們可以把最大產能提高到5倍。我們經常引導客戶利用雲計算來應付突發請求,這樣就沒有必要把33%的資產放在那裏等待突然爆發的用戶活動。在部署階段,資產的成本比較高,其他的成本則在從低到中的範圍內。該階段的總成本往往是最高的,部署相當於現有規模100倍的係統容量將會使許多公司破產。記住,擴展具有彈性,它既可以擴張也可以收縮,我們的解決方案應該認識到這兩個方麵。因此,靈活性是關鍵,因為你需要響應客戶的請求,隨著規模的收縮和擴張,在係統之間調整容量。
對可擴展性的設計和思考的成本相對低,因此應該經常進行。理想情況下,這些活動會產生某些書麵文件,可以作為基礎文檔在需要時快速參考。架構或設計解決方案的整體成本更高,可以留在日後處理,而且實際上也沒必要在生產中實現。正如取模的案例,我們可以根據需要進行參數的修改和發布,而不需要購買100倍的容量。最後,遵循這個過程可以在需求發生前安排好設備采購,這也許需要提前6周從主要設備供應商那裏訂貨,或者安排係統管理員跑到本地服務器商店去直接購買。顯然,在基礎設施即服務(IaaS)的環境下,我們沒有必要在需求到來前購買容量,在係統接近所需和接近實時的情況下,可以在部署階段很容易地把計算資產“旋轉”起來。
規則3——三次簡化方案
內容: 在設計複雜係統時,從項目的範圍、設計和實施角度簡化方案。
場景: 當設計複雜係統或產品時,麵臨著技術和計算資源的限製。
用法:
采用帕累托(Pareto)原則簡化範圍。
考慮成本優化和可擴展性來簡化設計。
依靠其他人的經驗來簡化部署。
原因: 隻聚焦“不過度複雜”,並不能解決需求或曆史發展與沿革中的各種問題。
要點: 在產品研發的各個階段都需要做好簡化。
鑒於規則1主要是關於避免超過“有用的”(實際的)需求和降低複雜度,本規則聚焦簡化包括從感知需求到實際設計和實施在內的一切。規則1是關於抑製某些事情過於複雜的衝動,而規則3則是關於試圖采用本文所述的方法來進一步簡化方案。有時候我們告訴客戶把這條規則想成“問三個如何”:如何簡化方案範圍?如何簡化方案設計?如何簡化方案實施?
如何簡化方案範圍
對這個簡化問題的答案是不斷地應用帕累托原則(也叫80-20原則)。收益的80%來自於20%的工作?對此,直接的問題是,“你收入的80%是由哪些20%的功能實現的?做得很少(工作的20%)同時取得顯著的效益(價值的80%),解放團隊去做其他的工作。如果刪除產品中不必要的功能,那麼可以做五倍的工作,而且產品並沒有那麼複雜!減少五分之四的功能,毫無疑問,係統將會減少功能之間的依賴關係,因而可以更高效率和更高本益比地進行擴展。此外,釋放出的80%的時間可用於推出新產品,以及投資於思考未來產品可擴展性的需求。
在保持大多數好處的前提下,思考如何減少不必要的功能,在這個問題上,我們並不孤單。以前有個公司叫37signals,現在改名為Basecamp,他們是這個概念的強力支持者,他們曾在《Rework》[6]一書並經常在《You Can Always Do Less》[7]微博上討論減少工作的必要性和機會。的確,由艾瑞克·裏斯提出,並經過馬蒂·凱甘傳播的“最小化可行產品”的概念,得到了“以最小的努力獲得經過驗證的最大化客戶感知數量”[8]的佐證。這種聚焦“敏捷”的方法允許我們快速發布簡單和容易擴展的產品。這樣做,我們可以獲得更大的產品吞吐量(組織可擴展性),可以花費更多的時間專注於以更具擴展性的方式構建最小產品。因為簡化方案覆蓋的範圍使我們要處理的事情少了,所以可以獲得更多的計算能力。如果不相信,你可以回到前麵去閱讀傑瑞米·金的故事及其總結的經驗教訓。假如當年eBay團隊簡化了諸如用戶評價子係統的功能範圍,V3項目將會以更低的成本、更快的速度,把相對而言同樣的價值交付給最終的消費者。
如何簡化方案設計
簡化後縮窄了的項目範圍,可以使後續的設計和實施工作更加容易。簡化設計與過度設計的複雜性密切相關。消除複雜性相當於在工作中忽略無關緊要的活動,簡化就是尋找一條捷徑。規則1中的例子說明了在數據庫中隻查詢需要的數據,select (*) from schema_name.table_name became select (column) from schema_name.table_name。簡化設計的方法表明,首先要看在本地,像內存這樣的信息共享資源中是否已經有了數據請求。消除複雜性涉及做更少的工作,而簡化設計涉及更快和更容易地完成工作。
想象一下,我們想要讀取一些源數據,並根據該數據的中間特征符號進行一些計算,然後把特征符號和計算結果捆綁在一起存入對象。在許多情況下,這裏的每一個動詞都可能被分解成一係列服務。實際上,這種方法看起來類似於現在的流行算法MapReduce。這種方法並不複雜,所以它不違反規則1。但是,假如我們知道要讀取的文件很小,而且不需要跨文件來組合特征字符,那麼有可能不把它分解為服務,而是直接通過一個簡單的應用來實現更有道理。讓我們回到前麵考勤卡的例子,如果目標是計算個人的工作小時數,那麼克隆多個單體應用來從考勤卡隊列讀取數據並進行計算就有道理了。簡單地說,簡化設計的步驟要求以易於理解、低成本、高效益和可擴展的方式來完成工作。
如何簡化方案實施
最後,我們來討論一下實施的問題。與規則2的DID擴展過程保持一致,我們把實施定義為解決方案的代碼實現。這裏我們遇到了一些問題,諸如使用遞歸或迭代是否更有意義。我們是否應該定義一個特定大小的數組,或者準備好在需要時動態地分配內存?對於解決方案,需要自己研發還是利用開源項目?或者從市場上采購?所有這些問題的答案都指向一個共同的主題:“如何利用其他經驗和已經存在的解決方案來簡化方案實施?”
因為不可能在每件事上都做到最好,所以我們應該首先尋找被廣泛采用的開源或第三方解決方案來滿足需求。如果那些都不存在,那麼我們應該看看在組織內部是否有人已經準備了可擴展的解決方案來解決問題。在沒有專有解決方案的情況下,我們應該再從外部看看是否有人已經描述了一種可以合法複製或模仿的可擴展方案。隻有無法在這三項中找到合適的選擇情況下,我們才會開始嚐試自己創建解決方案。最簡單的實施幾乎總是那些有過實施經曆並通過實踐證明了的可擴展方案。
規則4——減少域名解析
內容: 從用戶角度減少域名解析次數。
場景: 對性能敏感的所有網頁。
用法: 盡量減少下載頁麵所需的域名解析次數,但要保持與瀏覽器的並發連接平衡。
原因: 域名解析耗時而且大量解析會影響用戶體驗。
要點: 減少對象、任務、計算等是加快頁麵加載速度的好辦法,但要考慮好分工。
本書中的許多規則都聚焦在SaaS解決方案的後端架構上,但本規則卻讓我們考慮客戶的瀏覽器。如果用過任何基於瀏覽器的調試工具,如火狐的插件Firebug[9],或者Chrome的標準研發人員工具,當加載服務頁麵時,你會觀察到一些有趣的結果。最可能引起你注意的事情之一是網頁上那些大小差不多的對象,其下載時間卻不同。仔細分析你會看到有些對象在下載開始時有個額外的步驟,那就是域名解析。
域名服務係統(DNS)是互聯網或其他任何使用互聯網協議(TCP/IP)的網絡基礎設施中最重要的部分之一。與電話簿類似,它可以把一個網站的域名(www.akfpartners.com)解析為對應的IP地址(184.72.236.173)。域名服務係統由一係列分布式數據庫係統構成,其節點被稱為域名服務器。層次結構的頂部由根域名服務器組成。每個域至少有一個權威域名服務器用來發布有關該域的信息。
使用多層緩存可以使域名轉換為IP地址的過程更快完成,緩存部署在包括瀏覽器、計算機操作係統、互聯網服務提供商等許多層級上。今天的網頁可能包含來自於多個互聯網域的數以百計甚至數以千計的對象,緩存的使用顯著地提高了域名解析的性能。因為每個域都需要進行域名解析,如果把這些解析請求都累加起來,用戶就會明顯地感覺時間延遲。
在深入討論減少域名解析之前,我們需要在高層次上先了解一下大多數瀏覽器是怎麼下載頁麵的。這並不意味著要對瀏覽器進行深入研究,但是了解這些基礎知識將有助於優化應用的性能和提高可擴展性。事實上,幾乎所有的網頁都是由許多不同的對象組成的(圖片、JavaScript、CSS文件等),瀏覽器就是基於此,通過並發連接擁有同時下載多個對象的能力。瀏覽器對每個服務器或網關代理的最大持久並發連接數有限製。根據HTTP/1.1 RFC協議[10],這個最大值應該設置為2。但是,現在有許多瀏覽器都忽略此限製,把最大值設置成6或者更大。我們將基於此功能在下一個規則中討論如何優化網頁的下載時間。我們暫時先聚焦於把網頁分解成許多個對象,然後通過多個連接下載。
在網頁中,每個不同的域都關聯著一個或多個對象,而每個域都需要進行一次域名解析。該解析可能在中間的緩存中完成,也許需要多次往返訪問域名服務器。例如,假設我們有一個簡單的互聯網頁麵,它包含4個對象:(1)HTML頁麵本身和指向其他對象的文本和指令,(2)用於布局的CSS文件,(3)用於菜單選項的JavaScript文件,和(4)JPG圖像。HTML頁麵來自於我們公司網站的主域名(akfpartners.com),但是,CSS和JPG則由網站的子域名(static.akfpartners.com)提供,而JavaScript連接至穀歌(ajax.googleapis.com)。在這種情況下,瀏覽器首先接收請求前往www.akfpartners.com,這需要對akfpartners.com域名進行一次解析。在HTML下載後,瀏覽器將對其進行分析,結果發現它需要從static.akfpartners.com下載CSS和JPG文件,這需要進行另一次域名解析。最後,頁麵文件分析後發現還需要從另一個域下載JavaScript文件。取決於瀏覽器和操作係統中域名服務緩存數據的新鮮程度,域名解析可能從基本上不費什麼時間到長達幾百毫秒。圖1-1對這種情況進行了描述。
作為一條通用的規則,網頁上的域名解析的次數越少,網頁的下載性能就越好。把所有對象都放在同一個域裏會帶來問題,前麵的討論已經對最大並發連接數的限製做了暗示。我們將在下一個規則中更詳細地探討這個問題。
圖1-1 網頁對象下載時間
規則5——減少頁麵目標
內容: 盡可能減少網頁上的對象數量。
場景: 對性能敏感的所有網頁。
用法:
減少或者合並對象,但要平衡最大並發連接數。
尋找機會減輕對象的重量。
不斷測試確保性能的提升。
原因: 對象數量的多少直接影響網頁的下載時間。
要點: 對象和服務對象的方法之間的平衡是一門科學,需要不斷地測量和調整。這是在客戶的易用性、可用性和性能之間的平衡。
正如我們在規則4中所討論的那樣,網頁包括許多不同的對象(HTML、CSS、圖像、JavaScript等)。瀏覽器分別獨立下載這些對象,而且這些下載經常是並行的。改進網頁性能,從而提高可擴展性的最簡單方法之一,是減少對象的數量(頁麵對象需要較少的服務意味著服務器可以服務更多的網頁)。大多數頁麵最大的違規者是圖形對象。讓我們來看看穀歌的搜索頁麵(www.google.com),這是極簡主義的典範[11]。在寫作本書時,穀歌的頁麵上隻有少量對象,包括幾個.png文件,還有一些腳本和樣式表。在我們非常不科學的實驗中,搜索頁麵的加載時間大約在300毫秒之內。我們的客戶有一個在線雜誌,其網站的主頁有300多個對象,其中200個是圖像,平均加載時間超過12秒。這個客戶並沒有意識到頁麵性能不好使其損失了有價值的讀者。2009年穀歌發布了一份白皮書,聲稱測試表明搜索延遲如果增加400毫秒將使每日搜索量減少大約0.6%[12]。從那時起,許多客戶紛紛向我們表示,用戶活躍程度的增加與較快的頁麵響應相關。
減少頁麵上的對象數量是提高性能和可擴展性的好方法,但在你動手刪除所有的圖像之前還有其他的一些事情要考慮。顯然首先要把重要信息傳達給客戶。如果沒有圖像,網頁看起來會像1992年萬維網的項目頁麵,據稱那是世界上首個互聯網的頁麵[13]。因為需要圖像、JavaScript和CSS文件,你的第二個考慮可能是把所有類似的對象都放進一個文件。這個主意不壞,事實上CSS圖像精靈正是這個目的。圖像精靈是把一些小圖像組合成一個較大的圖像,可以通過CSS來單獨顯示其中的任何一個圖像。這樣做的好處是圖像的請求數量顯著減少。回到對穀歌搜索頁麵的討論,搜索頁麵上的兩個圖片中有一個是精靈,它大約由20多個較小的圖像所組成,可以獨立控製每個小圖像的顯示[14]。
到目前為止,我們已經介紹了通過減少頁麵的對象數量以提高性能和可擴展性的概念,但這必須要與需要圖像、CSS和JavaScript來支撐的頁麵相平衡。接下來我們將介紹如何把這些要素組合成單個對象以減少瀏覽器渲染頁麵所必需的不同請求的數量。然而另外一方麵,把所有的要素都組合成單個對象,將無法充分利用我們在規則4中討論的每個服務器的最大並發持久連接數。回顧一下,從單一域名同時下載多個對象是瀏覽器的能力。如果所有要素都集中在一個對象中,那麼瀏覽器可以同時下載兩個或更多對象的能力無法起作用。現在我們需要考慮把這些對象拆分成幾個較小的對象,以便同時下載。添加到方程式的最後一個變量是前麵提到的服務器並發持久連接,這將把我們帶回到規則4有關域名服務的討論。
瀏覽器並發連接存在頂限是因為負責提供對象的每個域名服務都存在著資源限製。如果網頁上的所有對象都來自單個域名(www.akfpartners.com),那麼瀏覽器的最大並發連接數設置為多少,可以同時下載的對象最多就是多少。如前所述,雖然協議建議這個最大值設置為2,但許多瀏覽器已將默認值增加為6甚至更高。因此,最好把網頁的內容(圖片、CSS、JavaScript等)拆分成足夠多的對象數,以充分利用大多數瀏覽器的該項功能。有一種技巧可以真正充分地利用瀏覽器的這種功能,那就是把不同的網頁對象分別存儲在不同的子域名上(例如static1.akfpartners.com,static2.akfpartners.com等)。瀏覽器把這些當成不同的域名對待,並允許每個子域名擁有自己的最大並發連接數。前麵提到那個12秒頁麵加載時間的在線雜誌客戶就采用了該技巧,用7個子域名把平均加載時間減少到5秒以內。
在本規則和規則4中曾論述過,如果不考慮整個頁麵的重量和構成該頁麵對象的重量(字節數),那麼關於頁麵速度的討論將是不完整的。短小精悍是硬道理。有道是水漲船高,因為不斷加大的帶寬越來越容易獲得,所以人們期待網絡頁麵將更加豐富和更有“份量”。保持頁麵盡可能輕,以取得理想的效果是明智的。在頁麵必須很重的情況下,采用Gzip壓縮以減輕頁麵的傳輸壓力,並把頁麵的整體響應時間減到最少。
不幸的是,理想的網頁應該有多少個對象以及多大重量沒有絕對的答案。提高網頁性能和可擴展性的關鍵是測試。這需要在必要的內容和功能、對象大小、渲染時間、總下載時間和涉及的域名數量等要素之間做好平衡。假設頁麵上有100個圖像,每個50KB,把它們組合成一個精靈可能就不是一個好主意,因為直到整個4.9MB的對象下載完畢之前,該頁麵將無法顯示任何圖像。同樣的概念也適用於JavaScript。如果把所有的.js文件合並為一個,那麼在整個文件被下載前,頁麵將無法使用任何JavaScript功能。確保擁有最好的頁麵速度的唯一方法,就是對不同的組合進行測試,直到找到最合適的那個。
總之,頁麵上的對象越少性能越好,但是這必須與許多其他因素平衡。這些因素包括必須顯示內容的大小,可以組合對象的多少,通過添加域名最大化並發連接的數量,頁麵的總重量以及懲罰是否有幫助等。雖然許多網站的性能改進技術都提及了這條規則,但是真正的重點是如何通過減少頁麵上的對象來提高性能和網站的可擴展性。除此之外,還應該考慮許多其他的性能優化技術,包括把CSS文件加載到頁麵的頂部、把JavaScript文件加載到頁麵的底部、縮小文件、使用緩存、延遲加載等。
規則6——采用同構網絡
內容: 確保交換機和路由器源於同一供應商。
場景: 設計和擴大網絡。
用法:
不要混合使用來自不同OEM的交換機和路由器。
購買或者使用開源的其他網絡設備(防火牆、負載均衡等)。
原因: 節省的成本與間歇性的互用性及可用性問題相比不值得。
要點: 異構網絡設備容易導致可用性和可擴展性問題,選擇單一供應商。
作為一家公司,我們信奉技術不可知論,這意味著我們相信如果有正確的架構和部署,幾乎任何技術都可以實現擴展。這種不可知論的範圍包括從對編程語言的偏好到數據庫供應商,直至硬件設備。但是對網絡設備(諸如路由器和交換機)需要特別小心。幾乎所有的供應商都聲稱在他們的設備上實現了標準協議(例如,互聯網控製消息協議RFC 792[15],路由信息協議RFC 1058[16],邊界網關協議RFC 4271[17]),允許來自不同供應商的設備之間進行通信,但是許多供應商也在其設備上實現了專有協議,如思科的增強型內部網關路由協議(EIGRP)。在我們的實踐中,以及我們的許多客戶那裏,我們發現每個供應商對如何實現標準協議的解釋經常是不同的。做一個類比,如果你曾經研發過網站頁麵的用戶界麵,並在諸如Internet Explorer、Firefox和Chrome等不同的瀏覽器上做過測試,那麼你已經親身了解了同一標準的不同實現會有多麼大的不同。現在,想象一下如果同樣的情況發生在網絡內部會怎麼樣?把供應商A的網絡設備與供應商B的網絡設備混合使用是自找苦吃。
這並不是說我們偏愛某個供應商。隻要供應商能提供一個可供參考的標準,其設備被網絡流量比你大的客戶使用,那麼我們就沒有什麼問題。這個規則不適用於諸如集線器、負載均衡器和防火牆這樣的網絡設備。我們所關心的同構性網絡設備,是指那些能夠彼此通信以完成網絡流量路由的設備。對於可能包含或不包含的所有其他網絡設備,例如,入侵檢測係統(IDS)、防火牆、負載均衡器和分布式拒絕服務保護設備(DDOS),我們的建議是選擇最好的。對於這些設備,從功能、可靠性、成本和服務角度比較,選擇最能滿足你需要的供應商。
總結
本章圍繞的是簡化這個主題。討論了防止複雜性(規則1),以及從初始需求或曆史沿革開始簡化產品直到最終實施的每一步(規則3),所得到的產品從技術角度來看容易理解,因此也容易擴展。如果盡早考慮擴展(規則2),即使不實施,我們仍然可以根據業務的需要做好解決方案。規則4和規則5教導我們通過減少對象的數量和減少下載對象所必需的域名解析,來減少瀏覽器必需要完成的工作。規則6教導我們要保持網絡的簡單和同構,以減少混合網絡設備可能引起的可擴展性和可用性問題的機會。
注釋
1. “eBay Announces Fourth Quarter and Year End 2001 Financial Results,” https://
investor.ebay.com/common/mobile/iphone/releasedetail.cfm?releaseid= 69550&
CompanyID=ebay&mobileid=.
2. Walmart Annual Report 2001, https://c46b2bcc0db5865f5a76-91c2ff8eba65983a1c33
d367b8503d02.r78.cf2.rackcdn.com/de/18/2cd2cde44b8c8ec84304db7f38ea/2001-annual-report-for-walmart-storesinc_130202938087042153.pdf.
3. “Amazon.com Announces 4th Quarter Profit 2002,”https://media.corporate-ir.net/
media_files/irol/97/97664/reports/q401.pdf.
4. “Important Letter from Meg and Pierre,” June 11, 1999, https://pages.ebay.com/outage-letter.html.
5. Wikipedia, “Overengineering,” https://en.wikipedia.org/wiki/Overengineering.
6. Jason Fried and David Heinemeier Hansson, Rework (New York: Crown Business,
2010).
7. 37signals, “You Can Always Do Less,” Signal vs. Noise blog, January 14, 2010, https://
37signals.com/svn/posts/2106-you-can-always-do-less.
8. Wikipedia, “Minimum Viable Product,” https://en.wikipedia.org/wiki/Minimum_viable_product.
9. To get or install Firebug, go to https://getfirebug.com/.
10. R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, and T. Berners-Lee, Network Working Group Request for Comments 2616, “Hypertext Transfer Protocol— HTTP/1.1,” June 1999, www.ietf.org/rfc/rfc2616.txt.
11. The Official Google Blog, “A Spring Metamorphosis—Google’s New Look,” May 5, 2010, https://googleblog.blogspot.com/2010/05/spring-metamorphosis-googles-new-look.html.
12. Jake Brutlag, “Speed Matters for Google Web Search,” Google, Inc., June 2009, https://services.google.com/fh/files/blogs/google_delayexp.pdf.
13. World Wide Web, www.w3.org/History/19921103-hypertext/hypertext/WWW/TheProject.html.
14. Google.com, www.google.com/images/srpr/nav_logo14.png.
15. J. Postel, Network Working Group Request for Comments 792, “Internet Control Message Protocol,” September 1981, https://tools.ietf.org/html/rfc792.
16. C. Hedrick, Network Working Group Request for Comments 1058, “Routing Information Protocol,” June 1988, https://tools.ietf.org/html/rfc1058.
17. Y. Rekhter, T. Li, and S. Hares, eds., Network Working Group Request for Comments 4271, “A Border Gateway Protocol 4 (BGP-4),” January 2006, https://tools.ietf.org/html/rfc4271.
最後更新:2017-05-19 15:32:17