寫(xiě)在前頭
靜態(tài)化是解決減輕網(wǎng)站壓力,提高網(wǎng)站訪問(wèn)速度的常用方案,但在強(qiáng)調(diào)交互的We2.0 時(shí)代,對(duì)靜態(tài)化提出了更高的要求,靜態(tài)不僅要能靜,還要能動(dòng),下面我通過(guò)一個(gè)項(xiàng)目,談?wù)劸W(wǎng)站靜態(tài)化后的架構(gòu)設(shè)計(jì)方案,同時(shí)和大家探討一下,在開(kāi)源產(chǎn)品大行其道,言架構(gòu)必稱(chēng)MemberCache, Nginx,的時(shí)代,微軟技術(shù)在網(wǎng)站架構(gòu)設(shè)計(jì)中的運(yùn)用.
靜態(tài)化的設(shè)計(jì)原則和步驟
靜態(tài)化是解決減輕網(wǎng)站壓力,但是靜態(tài)化也會(huì)帶來(lái)一系列的問(wèn)題,包括開(kāi)發(fā)上復(fù)雜度的增加,維護(hù)難度的增加,運(yùn)用不的當(dāng),更可能適得其反,而許多替代方案,比如頁(yè)面緩存,如果運(yùn)用得當(dāng),也能起到很好的效果,所以在開(kāi)始之前,必須進(jìn)行詳細(xì)的考察,確定是否適合靜態(tài)化,并制定適合的靜態(tài)化方式,下面先介紹一下
l 考查讀寫(xiě)比:
讀寫(xiě)比,準(zhǔn)確的說(shuō)是讀寫(xiě)負(fù)荷比,是否值得靜態(tài)化的最終考慮,由于一般寫(xiě)入的壓力明顯大于讀出的壓力,如果寫(xiě)入太頻繁,或者每次寫(xiě)入消耗的資源太多,都不能達(dá)到效果,我覺(jué)得讀寫(xiě)比例10:1應(yīng)該是個(gè)上限.具體情況需要根據(jù)自己的業(yè)務(wù)邏輯判斷
l 確定頁(yè)面呈現(xiàn)的內(nèi)容是否適合靜態(tài)化:
在設(shè)計(jì)方案時(shí),必須詳細(xì)考慮每個(gè)原型頁(yè)面,找到頁(yè)面上展示的信息,和他的更新方式,更新時(shí)機(jī),更新頻率,一定要注意那些不起眼的信息,他們可能左右你的設(shè)計(jì),
比如:我們以CSDN的論壇的任意一篇帖子為例,進(jìn)行分析

上面的帖子中呈現(xiàn)的內(nèi)容主要是這樣幾塊,帖子內(nèi)容,回復(fù)內(nèi)容,發(fā)帖人回復(fù)人的用戶信息
n 帖子內(nèi)容和回復(fù)內(nèi)容在發(fā)帖時(shí)更新,發(fā)帖后用戶可以修改其內(nèi)容,更新頻率高
n 用戶信息,用戶修改個(gè)人信息時(shí)可能會(huì)發(fā)生更改,用戶等級(jí)增加時(shí)也可能發(fā)生更改,比如加星,更新頻率低
n 回復(fù)數(shù)將每次回復(fù)后都要更改,更新頻率高
n 設(shè)計(jì)時(shí)要注意細(xì)節(jié),如上圖中圈出來(lái)的部分,這些部分是怎么修改的,頻率有多大,一個(gè)都不能放過(guò).
l 確定生成方式:
在上面帖子一例中.每次更改都重新生成頁(yè)面是不可取的,一篇比回復(fù)數(shù)多的帖子,需要的數(shù)據(jù)量是巨大的(每層樓的用戶信息,回復(fù)內(nèi)容),任何修改,都需要重新取出數(shù)據(jù)進(jìn)行生成是不能允許的.一般除非你的頁(yè)面基本不用更新,或者更新開(kāi)銷(xiāo)極小,(比如一段嵌入的廣告代碼)才能采用整體更新的方式,不然就需要我們找到合適的更新頁(yè)面局部區(qū)域的方法:
一般有下面兩個(gè)方法:
1) 正則修改法:
比如,如果帖子中的回復(fù)數(shù),html代碼是這樣
<label>回復(fù)數(shù)<var id="replyCount">34</var></label>
我們可以通過(guò)用下面正則來(lái)查找并替換計(jì)數(shù)
(?<=id="replyCount">)"d{1,}
2) 頁(yè)面區(qū)域分塊:
把頁(yè)面分成很多小塊,在顯示時(shí)組裝起來(lái),比如DotText就采用這個(gè)方法

這是一篇典型的Dottext blog頁(yè)面,其中紅色標(biāo)定部分是一個(gè)獨(dú)立的文件,而黃色框內(nèi)的是腳本動(dòng)態(tài)加載,這些部分在最終顯示的時(shí)候組合起來(lái),最終構(gòu)成了一篇Blog,具體的組合方法也有多種,可以使用Include,也可以自己來(lái)實(shí)現(xiàn).DotText就自己實(shí)現(xiàn)了一套加載機(jī)制
上面的兩種方法并不孤立,并可以根據(jù)需要,配合使用
l 確定需要?jiǎng)討B(tài)加載的信息:
頁(yè)面上總有一些內(nèi)容看起來(lái)不太適合靜態(tài)化,最典型的是一些統(tǒng)計(jì)結(jié)果,比如如果你在做一個(gè)圖書(shū)介紹頁(yè)面,可能就會(huì)需要展示圖書(shū)的當(dāng)天綜合評(píng)分,或者書(shū)籍排名,這些內(nèi)容需要用腳本進(jìn)行動(dòng)態(tài)加載
既然做了靜態(tài)化,就是希望減少服務(wù)器負(fù)載,動(dòng)態(tài)加載的數(shù)據(jù)總是不得已而為之,有的時(shí)候在需求允許的情況下,我們?cè)跀?shù)據(jù)在實(shí)時(shí)性和性能方面做一些妥協(xié),比如上面帖子中的用戶星級(jí)和昵稱(chēng),從數(shù)據(jù)實(shí)時(shí)性上說(shuō),當(dāng)用戶的星級(jí)增長(zhǎng),他發(fā)言的所有帖子都應(yīng)該發(fā)生變化,所以應(yīng)該用動(dòng)態(tài)加載.然而其實(shí)上這些信息如果不發(fā)生變化,也無(wú)傷大雅,用戶反而能夠看到自己在多年前發(fā)帖時(shí)的級(jí)別和昵稱(chēng).
現(xiàn)實(shí)中的項(xiàng)目
X網(wǎng)站是大型的電影資訊,電影社區(qū),向外提供電影相關(guān)信息服務(wù),以及用戶社區(qū),其中信息服務(wù)部分, 其中大部分頁(yè)面屬于信息呈現(xiàn)頁(yè),讀取量比較大,百萬(wàn)級(jí)別pv,信息主要由編輯在后臺(tái)發(fā)布,更新較少,但其頁(yè)面上有大量的交互性的內(nèi)容,比如評(píng)論,收藏列表,同時(shí)許多內(nèi)容允許用戶創(chuàng)造,比如上傳圖片,添加注釋.交互內(nèi)容的數(shù)量和交互的頻繁程度,都超過(guò)了普通的咨詢頁(yè)面,這次調(diào)整,準(zhǔn)備將其中訪問(wèn)量最大的幾塊:電影資料頁(yè),影人資料頁(yè),進(jìn)行靜態(tài)化,如果成功,還將運(yùn)用到更多的頻道,基本實(shí)現(xiàn)全站靜態(tài)化
通過(guò)對(duì)頁(yè)面設(shè)計(jì)和前一版本的分析,下面是具有挑戰(zhàn)性的地方.這些特點(diǎn)基本使用于大多數(shù)web2.0的站點(diǎn),很具有典型意義
l 頁(yè)面生成的觸發(fā)條件復(fù)雜
一般論壇中的帖子或者blog,更新方式比較單一:主要是由回復(fù)進(jìn)行觸發(fā)還有少數(shù)的修改動(dòng)作,然而該網(wǎng)站一個(gè)頁(yè)面上需要根據(jù)不同觸發(fā)條件就有20多個(gè), 比如光二級(jí)菜單:用戶發(fā)布圖片,刪除圖片,發(fā)布或者刪除影片信息,發(fā)布或者修改視頻,后臺(tái)修改電影信息,都有可能觸發(fā)
l 一個(gè)動(dòng)作觸發(fā)生成的頁(yè)面可能很多而且相互交疊
每一個(gè)動(dòng)作都會(huì)觸發(fā)一系列的生成,并且不同動(dòng)作可能都會(huì)涉及同一個(gè)頁(yè)面或者區(qū)域的生成.
比如:用戶給一步電影評(píng)分,需要生成評(píng)分更多頁(yè),評(píng)分統(tǒng)計(jì)更多頁(yè),首頁(yè)右側(cè)誰(shuí)還關(guān)注此影片小區(qū)域,等等.用戶收藏一個(gè)影片,也需要更新首頁(yè)右側(cè)誰(shuí)還關(guān)注此影片小區(qū)域
l 觸發(fā)頻繁:
雖然不及某些更大規(guī)模的網(wǎng)站,但是由于涉及眾多用戶參與的內(nèi)容,評(píng)論,收藏等等,觸發(fā)點(diǎn)多,發(fā)生頻度相當(dāng)頻繁
l 頁(yè)面多,結(jié)構(gòu)復(fù)雜,空間占用大:
通常,需要生成的頁(yè)面規(guī)模是這樣粗略估算的,Rn*P,Rn為資源數(shù),P為每個(gè)資源的頁(yè)面數(shù),所謂資源,可以看做一個(gè)生成單位,其頁(yè)面數(shù)可以簡(jiǎn)單看做發(fā)布一個(gè)資源,就需要生成其所有相關(guān)頁(yè)面數(shù)量,比如:發(fā)布一個(gè)blog,就需要生成一個(gè)Blog頁(yè),同時(shí)還需要生成或者更新個(gè)人主頁(yè)的blog列表,算上個(gè)人主頁(yè)右側(cè)的分類(lèi)文章數(shù)的小塊,也就是最多10來(lái)個(gè)頁(yè)面或者區(qū)域,但是發(fā)布一個(gè)電影,其相關(guān)的頁(yè)面至少有50個(gè)以上,而且有的頁(yè)面還帶有分頁(yè),一個(gè)信息比較豐富的電影,其頁(yè)面竟可以達(dá)到千個(gè)以上,空間10~20M,而且資源總數(shù)也不少,電影80000左右,電影人雖然P值較少,但是總量確有幾十萬(wàn)之巨,估計(jì)靜態(tài)頁(yè)面磁盤(pán)占用量幾百個(gè)G
l 向下兼容
這是一個(gè)已有系統(tǒng),舊系統(tǒng)的框框需要突破,但又沒(méi)有時(shí)間,或者不能完全突破,比如Url,已經(jīng)被收錄到搜索引擎,就不能隨便調(diào)整,還有一些地方,原本沒(méi)有為靜態(tài)生成考慮,另一些地方又需要兼容舊的設(shè)計(jì).
l 多臺(tái)前端Web
這種結(jié)構(gòu)要求生成的文件可能需要分布到多個(gè)服務(wù)器(另一個(gè)方案是放在幾臺(tái)專(zhuān)用的機(jī)器上,等前端來(lái)取)
l 任務(wù)緊迫
架構(gòu)討論結(jié)束儀式六月初,離奧運(yùn)開(kāi)幕上線只有兩月,也就是說(shuō)所有底層框架實(shí)現(xiàn),頁(yè)面模板開(kāi)發(fā),調(diào)試測(cè)試,動(dòng)作的整理,必須在7月底全部完成,按我原來(lái)估計(jì),光實(shí)現(xiàn)這幾塊的上百個(gè)頁(yè)面模板和填充方法,也需要那么長(zhǎng)的時(shí)間
綜合考慮上述因素,架構(gòu)必須要有以下幾個(gè)方面的特點(diǎn)
l 動(dòng)作可以靈活擴(kuò)展配置,某個(gè)動(dòng)作對(duì)應(yīng)哪些生成,應(yīng)該可以配置,并且可以分組
l 文件必須有分發(fā)機(jī)制
l 分發(fā)和生成必須獨(dú)立出來(lái),并且支持分布式
l 各種的動(dòng)作,必須轉(zhuǎn)化為消息,發(fā)送到生成和分發(fā)服務(wù)器進(jìn)行處理
l 針對(duì)同意資源頻繁動(dòng)作,在變量相同的情況下能夠具有合并的能力
l 動(dòng)作必須有記錄
l 盡量考慮使用已有成熟技術(shù),節(jié)省開(kāi)發(fā)時(shí)間
下面是設(shè)計(jì)的第一個(gè)架構(gòu)

用戶的動(dòng)作經(jīng)過(guò)MSMQ[1]傳入到生成分發(fā)中心(途中綠色箭頭)進(jìn)行處理,,處理中心接受到消息后,負(fù)責(zé)生成對(duì)應(yīng)的頁(yè)面或者頁(yè)面區(qū)域,并將頁(yè)面分發(fā)到各個(gè)服務(wù)器,負(fù)載均衡沿用以前的架構(gòu),采用微軟的NLB[2]
之所以用MSMQ,就是看上了他提供的完整的消息存儲(chǔ)恢復(fù)機(jī)制,這樣我們能確保即使服務(wù)器down掉重啟后,消息依然能正常處理,碰巧我們cms組的同事MSMQ非常熟悉,并且真準(zhǔn)備在另外一個(gè)項(xiàng)目中使用類(lèi)似的架構(gòu)—于是一拍即合
頁(yè)面采用分塊存儲(chǔ),這樣能保證生成時(shí)目標(biāo)小,開(kāi)銷(xiāo)小,也能重用性,然后再藉由SSI[3](shtml include)進(jìn)行整合,之所以采取這樣的方案,而不采用Dottext的整合方式,是因?yàn)槿绻捎?/span>Dottext的方式,就必須走IIS和.Net的管道[4],而據(jù)測(cè)試,經(jīng)過(guò)管道和直接返回html性能有非常大的差異,而使用ssi,在性能上是一個(gè)折中,并且可以Light HTTPd等高性能web服務(wù)器
模板生成方式,采用了XSLT和另外一種自定義的模板(我的同事開(kāi)發(fā)的機(jī)制,很有趣, 理論上能把傳統(tǒng)模板替換的性能開(kāi)銷(xiāo)全部消除),生成的最終產(chǎn)物是shtml,之所以生成shtml是為了使用其ssi(Server Side Include)的特性,保證一定的靈活性,并實(shí)現(xiàn)熱點(diǎn)數(shù)據(jù)的分離:某些頁(yè)面上的部分可能會(huì)頻繁更新和生成,而其它地方不變,或者某個(gè)部分是所有頁(yè)面通用的(比如頁(yè)頭和頁(yè)腳),較之php下常常使用smarty,生成php文件,雖然靈活性不如php,但是性能上不相上下,還略高.
但是這個(gè)設(shè)計(jì)的問(wèn)題是動(dòng)態(tài)內(nèi)容和靜態(tài)內(nèi)容沒(méi)有分開(kāi),生成的html頁(yè)面,和動(dòng)態(tài)頁(yè)面都放在前端服務(wù)器上,通過(guò)負(fù)載均衡訪問(wèn),也造成了分發(fā)服務(wù)器需要分發(fā)到多臺(tái)服務(wù)器,網(wǎng)絡(luò)IO效率較低,而且靜態(tài)內(nèi)容需要的磁盤(pán)空間很大,且小文件非常多,和動(dòng)態(tài)頁(yè)面混在一起不便于優(yōu)化,所以第二個(gè)方案對(duì)生成的靜態(tài)內(nèi)容與動(dòng)態(tài)內(nèi)容使用不同的服務(wù)器
方案二:

我們把生成的靜態(tài)文件單獨(dú)放置,可以看到,前端增加Nginx,作為跳轉(zhuǎn),把電影,影人資料庫(kù)的頁(yè)面轉(zhuǎn)向靜態(tài)服務(wù)器,其他的調(diào)用轉(zhuǎn)向動(dòng)態(tài)服務(wù)器,這樣我們就可以單獨(dú)為靜態(tài)服務(wù)器進(jìn)行優(yōu)化,比如采用更高效的服務(wù)器等等.
同時(shí)減少了文件分發(fā)的次數(shù)(甚至可以只分發(fā)到本機(jī)),提高生成分發(fā)的處理能力
更進(jìn)一步,可以把圖片服務(wù)分到另外一組機(jī)器上,使用獨(dú)立的域名,比如img.xxx.com,這樣可以有效的減少帶寬
最終完整架構(gòu):
文件生成分發(fā)中心
下圖是文件生成分發(fā)中心的工作流程圖

生成服務(wù)對(duì)外只有一個(gè)輸入,就是消息,一個(gè)輸出:靜態(tài)文件,內(nèi)部根據(jù)消息,從配置文件中找到對(duì)應(yīng)的生成方法,取出相應(yīng)的模板,進(jìn)行數(shù)據(jù)填充
分發(fā)服務(wù)主要吧生成服務(wù)產(chǎn)生的文件進(jìn)行分發(fā),分發(fā)到前端的N臺(tái)服務(wù)器上,開(kāi)始考慮得比較復(fù)雜,希望分發(fā)服務(wù)可以跨越協(xié)議(本地文件系統(tǒng),局域網(wǎng),http協(xié)議),跨越多種存儲(chǔ)介質(zhì)(文件系統(tǒng),數(shù)據(jù)庫(kù)),實(shí)際最后定下來(lái)基本是本地文件系統(tǒng)或者局域網(wǎng)傳輸
注:上圖中文件分發(fā)的部分也可以通過(guò)定制MogileFS,來(lái)實(shí)現(xiàn)分布式文件系統(tǒng)
馬后炮:
總結(jié)起來(lái),靜態(tài)化除了對(duì)架構(gòu)方面的影響,對(duì)開(kāi)發(fā)和測(cè)試流程也有影響
對(duì)測(cè)試提出更高的要求:
因?yàn)橐坏┥暇€后,某個(gè)頁(yè)面發(fā)現(xiàn)問(wèn)題,即使是文字的修改,也需要重新生成許多頁(yè)面,所以測(cè)試人員必須非常仔細(xì),測(cè)試周期也需要延長(zhǎng)
開(kāi)發(fā)人員需要掌握模板語(yǔ)言
需要掌握一種模板預(yù)言,無(wú)論是Xslt還是自己開(kāi)發(fā)的模板語(yǔ)言,都需要花一定的時(shí)間掌握
需要給第一次生成騰出足夠時(shí)間:
如果不是新系統(tǒng),那么數(shù)據(jù)遷移和生成的過(guò)程就比較痛苦,由于頁(yè)面眾多,第一次生成的過(guò)程可能需要以天來(lái)計(jì)算,在制定上線方案是就需要考慮到這個(gè)方面
Nginx作為前端的跳轉(zhuǎn),根據(jù)其他網(wǎng)站的經(jīng)驗(yàn),應(yīng)該可以達(dá)到2-3萬(wàn)并發(fā)連接,但是使用之后,常常有卡殼的情況發(fā)生,具體癥狀為在瀏覽器中訪問(wèn)頁(yè)面時(shí),連接超時(shí),或者一直不響應(yīng),此時(shí)Nginx連接數(shù)并不高,好在還有第一套方案可以備用,讓我們有時(shí)間去解決這個(gè)問(wèn)題,如果大家對(duì)這個(gè)問(wèn)題有什么心得,歡迎交流