隨著網(wǎng)站規(guī)模的變大,訪(fǎng)問(wèn)量提升,網(wǎng)站服務(wù)器越來(lái)越不堪重負(fù),瀏覽者也會(huì)對(duì)頁(yè)面打開(kāi)的速度怨聲載道。這時(shí)候最簡(jiǎn)單的解決方案就是增加緩存。網(wǎng)站服務(wù)器的緩存有很多中,可以放在數(shù)據(jù)庫(kù)和Web應(yīng)用程序之間,也可以放在Web應(yīng)用程序和Web服務(wù)器之間,還可以放在Web服務(wù)器和用戶(hù)瀏覽器之間,甚至可以直接放在瀏覽器端。
其中最簡(jiǎn)單,需要配置最少的莫過(guò)于數(shù)據(jù)庫(kù)和Web應(yīng)用程序之間了,并且見(jiàn)效也最快,因?yàn)閷?duì)于現(xiàn)代計(jì)算機(jī)系統(tǒng)來(lái)說(shuō)IO是最大的瓶頸。常見(jiàn)的方法就是使用MemCache或者K-V型NoSQL數(shù)據(jù)庫(kù)做緩存。
這么安逸了一段時(shí)間之后,網(wǎng)站的響應(yīng)速度還是會(huì)降下來(lái),如果你不想增加服務(wù)器的話(huà),這時(shí)需要做的就是頁(yè)面輸出緩存了。
因?yàn)閿?shù)據(jù)庫(kù)和Web應(yīng)用程序端的緩存只是將數(shù)據(jù)庫(kù)中的鍵和值緩存下來(lái),訪(fǎng)問(wèn)量大的話(huà)有一個(gè)命中率的問(wèn)題,并且一個(gè)頁(yè)面往往包含有大量的需要從數(shù)據(jù)庫(kù)查詢(xún)的值,就算數(shù)據(jù)庫(kù)緩存全部命中,這其中也需要一個(gè)查詢(xún)的過(guò)程,即時(shí)它很快。還有一個(gè)問(wèn)題,就是對(duì)于Web應(yīng)用程序來(lái)說(shuō),從獲取數(shù)據(jù)到輸出頁(yè)面,中間還需要一些邏輯性運(yùn)算和模板的渲染過(guò)程,這個(gè)過(guò)程也會(huì)消耗一段時(shí)間。
如果將一個(gè)頁(yè)面響應(yīng)的結(jié)果,也就是HTML頁(yè)面,整體緩存下來(lái),Web服務(wù)器直接輸出緩存結(jié)果,這樣的速度基本上相當(dāng)于直接輸出靜態(tài)頁(yè)面,對(duì)于服務(wù)器端的負(fù)載將會(huì)大大減輕。
對(duì)于頁(yè)面輸出緩存,或者有人稱(chēng)之為頁(yè)面靜態(tài)化,主要有以下幾個(gè)問(wèn)題:
頁(yè)面?zhèn)性化部分的處理,F(xiàn)在絕大部分網(wǎng)站都有登錄給功能,對(duì)于同一個(gè)頁(yè)面來(lái)說(shuō)登錄前后、不同用戶(hù)登錄后都會(huì)有不同的顯示,比如說(shuō)未登錄時(shí)顯示“登錄或注冊(cè)”,登錄之后顯示“歡迎用戶(hù)XXX”,這樣就會(huì)造成網(wǎng)站大部分頁(yè)面都不能直接緩存。
其實(shí)這個(gè)問(wèn)題是在頁(yè)面?zhèn)性化部分與非個(gè)性化部分合成的時(shí)間問(wèn)題上。傳統(tǒng)網(wǎng)站這個(gè)個(gè)性化部分也非個(gè)性化部分是直接放在Web應(yīng)用程序上的,這樣就導(dǎo)致不同用戶(hù)輸出頁(yè)面的不同,緩存不能進(jìn)行。如果把這個(gè)過(guò)程放在頁(yè)面緩存程序之后,使緩存的結(jié)果相同就沒(méi)有問(wèn)題了。
常見(jiàn)的方式就是使用SSI、ESI和CSI。
SSI(Server Side Include)這個(gè)現(xiàn)在大部分的Web服務(wù)器都支持,可以在頁(yè)面中使用來(lái)替代原來(lái)個(gè)性化的部分,然后在Web服務(wù)器上就會(huì)去尋找這個(gè).shtml文件,將它與頁(yè)面合成。不過(guò)這有一個(gè)問(wèn)題,ssi只能將靜態(tài)文件與頁(yè)面合成,這樣對(duì)于做頁(yè)面輸出緩存意義并不大。
ESI(Edge Side Include)正是為了應(yīng)付這種場(chǎng)合而出現(xiàn)的,它可以使用類(lèi)似
CSI(Client Side Include)或者叫做Browser Side Include,就是在用戶(hù)瀏覽器端將個(gè)性化信息與非個(gè)性化信息合成。說(shuō)白了就是用ajax異步獲取個(gè)性化信息,或者直接使用iframe顯示個(gè)性化信息。實(shí)際上這個(gè)合成的過(guò)程越靠近用戶(hù)瀏覽器端越好,這樣就可以一直從Web服務(wù)器到代理服務(wù)器的緩存到CDN都可以用到這些完全相同的頁(yè)面做緩存。但是使用ajax等方式有一些問(wèn)題,就是對(duì)于不支持或者不開(kāi)啟的JavaScript的瀏覽器來(lái)說(shuō)就不能正常訪(fǎng)問(wèn)頁(yè)面了,現(xiàn)在很多網(wǎng)站都要考慮到手機(jī)客戶(hù)端,其中不支持JavaScript的占大多數(shù)。并且大量使用ajax的話(huà)對(duì)用戶(hù)瀏覽器來(lái)說(shuō)也是一種負(fù)擔(dān),并且會(huì)造成頁(yè)面加載很“卡頓”的感覺(jué)。
具體的做法還是依賴(lài)實(shí)際環(huán)境來(lái)決定。
剩下需要解決的方式是如何來(lái)緩存頁(yè)面。
使用代理緩存服務(wù)器是一個(gè)比較不錯(cuò)的主意,比如Squid還有比較新的Varnish,它們都可以架設(shè)在Web服務(wù)器前端作為代理緩存服務(wù)器,將頁(yè)面緩存下來(lái)。當(dāng)頁(yè)面需要更新的時(shí)候可以給它發(fā)送一個(gè)Purge請(qǐng)求加具體頁(yè)面URL,就可以使代理緩存服務(wù)器去訪(fǎng)問(wèn)Web服務(wù)器,重新生成一個(gè)新的頁(yè)面,然后將現(xiàn)有的頁(yè)面緩存失效。這個(gè)過(guò)程中當(dāng)新的用戶(hù)訪(fǎng)問(wèn)請(qǐng)求達(dá)到時(shí),代理緩存服務(wù)器還是使用之前的緩存,新的緩存頁(yè)面生成結(jié)束后才使過(guò)期的頁(yè)面失效。這樣就不用擔(dān)心更新緩存過(guò)程中所有用戶(hù)請(qǐng)求都去直接訪(fǎng)問(wèn)Web服務(wù)器造成大量的壓力了。這其中有一個(gè)問(wèn)題,就是觸發(fā)這個(gè)更新動(dòng)作的用戶(hù),比如說(shuō)他發(fā)布了一條回復(fù)造成頁(yè)面更新,如果頁(yè)面更新請(qǐng)求與頁(yè)面緩存更新兩個(gè)動(dòng)作是異步的話(huà),假如他的網(wǎng)速足夠快就會(huì)看到自己剛才發(fā)布的帖子沒(méi)有顯示出來(lái),造成重復(fù)發(fā)帖等動(dòng)作。這個(gè)問(wèn)題接下來(lái)再來(lái)想辦法處理。
如果不想另外搞一個(gè)代理服務(wù)器的話(huà),Nginx的fastcgi cache是一個(gè)好東西,可以直接將fastcgi的頁(yè)面緩存下來(lái)。配合一個(gè)第三方的模塊cache purge,可以通過(guò)訪(fǎng)問(wèn)/purge/url的方式來(lái)時(shí)緩存過(guò)期。另外Nginx+proxy cache+cache purge也可以達(dá)到上面Squid和Varnish的作用。
剩下還有一種方式,使用Nginx的rewrite,將緩存保存為一個(gè)靜態(tài)文件,先檢查靜態(tài)頁(yè)面是否存在,存在的話(huà)rewrite到此,不存在則訪(fǎng)問(wèn)Web應(yīng)用程序。這樣還需要另外配合一個(gè)獨(dú)立的進(jìn)程,來(lái)生成緩存頁(yè)面。當(dāng)Web應(yīng)用程序觸發(fā)更新時(shí)給此進(jìn)程發(fā)送一個(gè)信號(hào),緩存進(jìn)程收到此信號(hào)后去Web服務(wù)器端或者Web應(yīng)用程序端請(qǐng)求頁(yè)面(為了做到松耦合和以后的服務(wù)器分離最好直接從Web服務(wù)器端),將請(qǐng)求生成的頁(yè)面文件直接覆蓋遠(yuǎn)緩存的靜態(tài)文件,這樣就可以更新緩存,并且在緩存更新過(guò)程中訪(fǎng)問(wèn)者依然可以使用之前的緩存文件。
還有一個(gè)問(wèn)題,當(dāng)很多用戶(hù)在同一個(gè)時(shí)間點(diǎn)內(nèi)同時(shí)觸發(fā)一個(gè)頁(yè)面的更新動(dòng)作,這樣就會(huì)導(dǎo)致此頁(yè)面頻繁更新,并且這些更新是不必要的,這就是所謂的驚群?jiǎn)栴}。
防止此事件發(fā)生,一個(gè)解決方案是使用隊(duì)列。
一般頁(yè)面發(fā)生內(nèi)容變化是在提交POST請(qǐng)求之后,在此時(shí)給緩存更新系統(tǒng)發(fā)送一條消息,記錄發(fā)生更新動(dòng)作的類(lèi)型、ID(比如說(shuō)帖子更新,更新的ID)。緩存更新系統(tǒng)在接受到此消息時(shí)將消息進(jìn)行預(yù)處理,根據(jù)配置文件計(jì)算出影響更新的頁(yè)面(此帖子的頁(yè)面,帖子列表的頁(yè)面,等等)。
將此頁(yè)面的URL插入進(jìn)隊(duì)列,更新程序在隊(duì)列另一端取URL,每取一次URL就過(guò)濾一遍整個(gè)隊(duì)列,將相同的URL隊(duì)列消息刪除出隊(duì)列。這樣就可以保證更新依次頁(yè)面,就可以相應(yīng)所有更新事件。取出后依據(jù)使用的緩存系統(tǒng)發(fā)送相應(yīng)的Purge請(qǐng)求或者自主生成新的緩存頁(yè)面覆蓋老的野蠻來(lái)更新緩存。
這種去重復(fù)的隊(duì)列實(shí)現(xiàn)可以用現(xiàn)有的模塊,比如Python的一個(gè)Queue模塊,繼承此模塊中的類(lèi)并重寫(xiě)幾個(gè)方法即可實(shí)現(xiàn)這種去重的隊(duì)列。還可以使用現(xiàn)有的隊(duì)列系統(tǒng),甚至直接使用數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn)隊(duì)列。使用數(shù)據(jù)庫(kù)有一個(gè)好處,就是當(dāng)整個(gè)系統(tǒng)Down掉的時(shí)候,系統(tǒng)重啟后隊(duì)列中的消息還存在,可以繼續(xù)更新這些頁(yè)面的緩存。如果直接使用內(nèi)存中的隊(duì)列的話(huà)系統(tǒng)Down掉重啟就會(huì)導(dǎo)致一些未處理的隊(duì)列消息丟失,導(dǎo)致這些頁(yè)面的緩存都沒(méi)有更新。這種情況出現(xiàn)的幾率還有出現(xiàn)后對(duì)系統(tǒng)的影響需要根據(jù)實(shí)際情況斟酌,論壇系統(tǒng)如果發(fā)帖不是十分頻繁,偶爾某個(gè)帖子或者回復(fù)沒(méi)有出現(xiàn)并不是非常嚴(yán)重的問(wèn)題。
一個(gè)好的系統(tǒng)應(yīng)該是針對(duì)發(fā)生故障的情況而設(shè)計(jì)的。假如緩存系統(tǒng)出現(xiàn)問(wèn)題,導(dǎo)致沒(méi)有向緩存服務(wù)器發(fā)送更新請(qǐng)求,這時(shí)所有更新動(dòng)作都不會(huì)得到相應(yīng),導(dǎo)致用戶(hù)重復(fù)提交數(shù)據(jù),可能會(huì)發(fā)生一些問(wèn)題。這時(shí)如果能停掉緩存服務(wù)器,讓用戶(hù)直接訪(fǎng)問(wèn)Web應(yīng)用程序或許是一個(gè)好主意(或者如果沒(méi)有緩存的話(huà)服務(wù)器能被直接大量直接訪(fǎng)問(wèn)干掉),如果去做是一個(gè)問(wèn)題。
Some Tips:
上面說(shuō)的如果異步處理更新動(dòng)作與緩存更新的話(huà),可能會(huì)使更新觸發(fā)者看不到自己更新的數(shù)據(jù)。這時(shí)可以使用一些小花招,比如現(xiàn)在大部分的論壇系統(tǒng)在發(fā)帖后跳轉(zhuǎn)到一個(gè)“3秒后跳轉(zhuǎn)到帖子頁(yè)面”,就可以給緩存系統(tǒng)提供充分的更新緩存時(shí)間,對(duì)于用戶(hù)體驗(yàn)來(lái)說(shuō)也不會(huì)大打折扣。
有些頁(yè)面不能使用緩存,比如說(shuō)帖子列表頁(yè)面。因?yàn)樘恿斜眄?yè)面是會(huì)隨著最后回復(fù)時(shí)間來(lái)動(dòng)態(tài)更新排列順序的,一個(gè)新帖或者回復(fù)可能會(huì)導(dǎo)致所有帖子列表分頁(yè)內(nèi)容發(fā)生變化,而帖子列表分頁(yè)可能有幾千甚至幾萬(wàn)頁(yè),全部都進(jìn)行更新的話(huà)顯然是不可能也沒(méi)有必要的。因此這些頁(yè)面不適用頁(yè)面輸出緩存,但我們可以只緩存帖子列表的第一頁(yè)或者前幾頁(yè),因?yàn)榻^大部分的訪(fǎng)問(wèn)請(qǐng)求都是來(lái)自與這幾個(gè)頁(yè)面,每次更新動(dòng)作都觸發(fā)這幾個(gè)頁(yè)面進(jìn)行緩存更新也是沒(méi)有問(wèn)題的。
假如網(wǎng)站每日發(fā)新帖的數(shù)量非常高,導(dǎo)致帖子列表更新十分頻繁,這時(shí)可以使用另外一種更新觸發(fā)機(jī)制,就是按時(shí)間來(lái)觸發(fā)更新。這樣定時(shí)更新的頻率比之前按動(dòng)作進(jìn)行更新會(huì)少一些,并且也不會(huì)過(guò)分加重隊(duì)列的負(fù)擔(dān)。
網(wǎng)站首頁(yè)的信息可能會(huì)比較復(fù)雜,觸發(fā)其更新的來(lái)源可能會(huì)很多,更新一次的成本也較高,而首頁(yè)對(duì)于事實(shí)性的要求也沒(méi)有那么高(比如說(shuō)在某個(gè)板塊發(fā)布新貼,這時(shí)在首頁(yè)的最新帖子列表中沒(méi)有看到此貼,對(duì)于絕大部分用戶(hù)來(lái)說(shuō)并不是什么問(wèn)題),因此也試用于按時(shí)間進(jìn)行更新的機(jī)制,并且這個(gè)更新時(shí)間的間隔可以適當(dāng)?shù)睦L(zhǎng)一些。
類(lèi)似的還有帖子回復(fù)頁(yè),此頁(yè)面也是一個(gè)列表排序頁(yè)面,但是是順序時(shí)間排列的(帖子列表為倒序時(shí)間排列的),因此也十分適用與使用緩存。如果網(wǎng)站當(dāng)初設(shè)計(jì)的功能為顯示所有樓層,刪除某一樓層的話(huà)此位置消失,這樣的話(huà)如果刪除一個(gè)樓層將會(huì)導(dǎo)致此頁(yè)面和接下來(lái)所有頁(yè)面的內(nèi)容發(fā)生變化。因此在網(wǎng)站設(shè)計(jì)時(shí)最好設(shè)計(jì)成這樣:刪除一條回復(fù),此回復(fù)還在,只不過(guò)內(nèi)容改為了“評(píng)論已刪除”,這樣就不會(huì)觸發(fā)所有后來(lái)頁(yè)面進(jìn)行緩存更新了。
江西省上饒市廣信區(qū)三清山中大道588號(hào)7棟5號(hào)
電話(huà):0793-8313026 7094119
傳真:0793-8313026
手機(jī):18079306668 13576325382 曾
郵箱:174216168@qq.com
QQ:174216168
Copyright © 2008-2019 (srlrcm.cn) 獵人傳媒. All Rights Reserved.
贛ICP備08101270號(hào)-1 百度統(tǒng)計(jì)