本節書摘來自華章出版社《架構真經:網際網路技術架構的設計》一書中的第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,” http://
investor.ebay.com/common/mobile/iphone/releasedetail.cfm?releaseid=
69550&
companyid=ebay&mobileid=.
2. walmart annual report 2001,
http://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,”http://media.corporate-ir.net/
media_files/irol/97/97664/reports/q401.pdf.
4. “important letter from meg and
pierre,” june 11, 1999,
http://pages.ebay.com/outage-letter.html.
5. wikipedia, “overengineering,” http://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,
http://
37signals.com/svn/posts/2106-you-can-always-do-less.
8. wikipedia, “minimum viable
product,”
http://en.wikipedia.org/wiki/minimum_viable_product.
9. to get or install firebug, go to
http://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,
http://googleblog.blogspot.com/2010/05/spring-metamorphosis-googles-new-look.html.
12. jake brutlag, “speed matters
for google web search,” google, inc., june 2009,
http://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, http://tools.ietf.org/html/rfc792.
16. c. hedrick, network working group
request for comments 1058, “routing information protocol,”
june 1988, http://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,
http://tools.ietf.org/html/rfc4271.