我們都知道瀏覽器會緩存訪問過網站的網頁,瀏覽器通過URL地址訪問一個網頁,顯示網頁內容的同時會在電腦上面緩存網頁內容。如果網頁沒有更新的話,瀏覽器再次訪問這個URL地址的時候,就不會再次下載網頁,而是直接使用本地緩存的網頁。只有當網站明確標識資源已經更新,瀏覽器才會再次下載網頁。
一、什么是HTTP Cache
對于瀏覽器的這種網頁緩存機制大家已經耳熟能詳了,舉個例子來說,JavaEye的新聞訂閱地址:http://www.iteye.com/rss/news , 當瀏覽器或者訂閱程序訪問這個URL地址的時候,JavaEye的服務器在response的header里面會發送給瀏覽器如下狀態標識:
這就是告訴瀏覽器,新聞訂閱這個網絡資源的最后修改時間和Etag。于是瀏覽器把這兩個狀態信息連同網頁內容在本地進行緩存,當瀏覽器再次訪問JavaEye新聞訂閱地址的時候,瀏覽器會發送如下兩個狀態標識給JavaEye服務器:
就是告訴服務器,我本地緩存的網頁最后修改時間和Etag是什么,請問你服務器的資源有沒有在我上次訪問之后有更新啊?于是JavaEye服務器會核對一下,如果該用戶上次訪問之后沒有更新過新聞,那么根本就不必生成這個RSS了,直接告訴瀏覽器:“沒什么新東西,你還是看自己緩存的網頁吧”,于是服務器就發送一個304 Not Modified的消息,其他什么都不用干了。
這就是HTTP層的Cache,使用這種基于資源的緩存機制,不但大大節省服務器程序資源,而且還減少了網頁下載次數,節約了很多網絡帶寬。
二、HTTP Cache究竟有什么作用?
我們通常的動態網站編程,服務器端程序根本就不去處理瀏覽器發送過來的If-None-Match和If-Modified-Since狀態標識,只要有請求就生成網頁發送給瀏覽器。對于一般情況來說,用戶不會總是沒完沒了刷新一個頁面,所以大家并不認為這種基于資源的緩存有什么太大的作用,但實際情況并非如此:
1、像Google這種比較智能的網絡爬蟲可以有效識別資源的狀態信息,如果使用這種緩存機制,可以大大減少爬蟲的爬取次數。
比方說Google每天爬JavaEye網站大概15萬次左右,但實際上JavaEye每天有更新的內容不會超過1萬個網頁。因為很多內容更新比較快,因此Google就會反復不停的爬取,這樣本身就造成了很多資源的浪費。如果我們使用HTTP Cache,那么只有當網頁內容發生改變的時候,才會真正進行爬取,其他時候我們直接告訴Google的爬蟲304 Not Modified就可以了。這樣不但降低了服務器本身的負載和爬蟲造成的網絡帶寬消耗,實際上也大大提高了Google爬蟲的工作效率,豈不是皆大歡喜?
2、很多內容更新不頻繁的網頁,盡管用戶不會頻繁的刷新,但是從一個比較長的時間段來看使用HTTP Cache,仍然可以起到很大的緩存作用。
比方說一些歷史討論帖子,已經過去了幾個月了,這些帖子內容很少更新。用戶可能通過搜索,收藏鏈接,文章關聯等方式時不時訪問到這個頁面。那么只要用戶訪問過一次以后,后續所有訪問服務器直接發送304 Not Modified就可以了,不用真正生成頁面。
3、對于歷史帖子使用HTTP Cache可以避免爬蟲反復的爬取。
比方說JavaEye的論壇帖子列表頁面,分頁到20頁后面的帖子已經很少有人直接訪問了,但是從服務器日志去看,每天仍然有大量爬蟲反復爬取這些分頁到很后面的頁面。這些頁面由于用戶很少去點擊,所以基本上沒有被應用程序的memcached緩存住,每次訪問都會造成很高的資源消耗,爬蟲隔一段時間就爬一次,對服務器是很大的負擔。如果使用了HTTP Cache,那么只要爬蟲爬過一次以后,以后無論爬蟲爬多少次,都可以直接返回304 Not Modified了,極大的節省了服務器的負載。
三、如何在應用程序里面使用HTTP Cache
如果我們要在自己的程序里面實現HTTP Cache,是件非常簡單的事情,特別是對Rails來說只需要添加一點點代碼,以上面的JavaEye新聞訂閱來說,只要添加一行代碼:
用最新新聞文章作為Etag,該文章最后修改時間作為資源的最后修改時間,這樣就OK了。如果瀏覽器發送過來的標識和服務器標識一致,說明內容沒有更新,直接發送304 Not Modified;如果不一致,說明內容更新,瀏覽器本地的緩存太古老了,那么就需要服務器真正生成頁面了。
以上只是一個最簡單的例子,如果我們需要根據狀態做一些更多的工作也是很容易的。比方說JavaEye博客的RSS訂閱地址: http://robbin.iteye.com/rss
這個實現稍微復雜一些。我們需要判斷博客訂閱所有的輸出文章是否有更新,所以我們用博客文章內容最后修改時間和博客的評論數量做一個hash,然后用這個hash值作為資源的Etag,那么只要這些博客文章當中任何文章內容被修改,或者有新評論,都會改變Etag值,從而通知瀏覽器內容有更新了。
除了RSS訂閱之外,JavaEye網站還有很多地方適合使用HTTP Cache,比方說JavaEye論壇的版面列表頁面,一些經常喜歡泡論壇的用戶,可能時不時會上來刷新一下版面, 看看有沒有新的帖子,那么我們就不必每次用戶請求的時候都去執行程序,生成頁面給他。我們判斷一下如果沒有新帖子的話,直接告訴他304 Not Modified就可以了,在沒有使用HTTP Cache之前的版面Action代碼:
添加HTTP Cache以后,代碼如下:
對于登錄用戶,不使用HTTP Cache,這是因為登錄用戶需要實時接收站內短信通知和訂閱通知,因此我們只能對匿名用戶使用HTTP Cache,然后我們使用當前所有帖子id和回帖數構造hash作Etag,這樣只要當前分頁列表頁面有任何帖子發生改變或者有了新回帖,就更新頁面,否則就不必重新生成頁面。
論壇帖子頁面實際上也可以使用HTTP Cache,只不過Etag的hash算法稍微復雜一些,需要保證帖子的任何改動都要引起hash值的改變,示例代碼如下:
要分別根據主題貼,該分頁的所有回帖和帖子頁面的廣告內容進行hash,計算出來一個唯一的Etag值,保證任何改動都會生成新的Etag,這樣就搞定了,是不是很簡單!這種帖子的緩存非常有效,可以避免Rails去render頁面和下載頁面,極大的減輕了服務器負載和帶寬。
再舉一個需求比較特殊的例子:對于知識庫搜索相關文章的推薦頁面,比方說:http://www.iteye.com/wiki/topic/462476,也就是本文的相關文章推薦內容,我們并不希望用戶和爬蟲每次訪問這個頁面都實際執行一遍全文檢索,然后構造頁面內容,在一個相對不長的時間范圍內,這篇文章的相關推薦文章改變的概率不大,因此我們希望比方說5天之內,用戶重復訪問該頁面,就直接返回304 Not Modified,那么Rails沒有直接的設施給我們使用,需要我們稍微了解一些Rails的機制,自己編寫,代碼示例如下:
每次用戶請求,我們判斷用戶是否5天之內訪問過該頁面,如果訪問過,直接返回304 Not Modified,如果沒有訪問過,或者上次訪問已經超過了5天,那么設置最近修改時間為當前時間,然后生成頁面給用戶。是不是很簡單?
在給JavaEye網站所有的RSS訂閱輸出添加了HTTP Cache以后,通過一天的觀察發現,超過一半的RSS訂閱請求已經被緩存了,直接返回304 Not Modified,所以效果非常明顯,由于JavaEye網站每天RSS訂閱的動態請求就超過了10萬次,因此添加HTTP Cache可以減輕不少服務器的負擔和帶寬消耗。除此之外,新聞文章頁面,整個論壇頻道,知識庫相關推薦文章頁面都可以添加HTTP Cache,粗粗計算下來,JavaEye這些頁面統統使用HTTP Cache以后,網站整體性能至少可以提高10%。
一、什么是HTTP Cache
對于瀏覽器的這種網頁緩存機制大家已經耳熟能詳了,舉個例子來說,JavaEye的新聞訂閱地址:http://www.iteye.com/rss/news , 當瀏覽器或者訂閱程序訪問這個URL地址的時候,JavaEye的服務器在response的header里面會發送給瀏覽器如下狀態標識:
- Etag "427fe7b6442f2096dff4f92339305444"
- Last-ModifiedFri,04Sep200905:55:43GMT
這就是告訴瀏覽器,新聞訂閱這個網絡資源的最后修改時間和Etag。于是瀏覽器把這兩個狀態信息連同網頁內容在本地進行緩存,當瀏覽器再次訪問JavaEye新聞訂閱地址的時候,瀏覽器會發送如下兩個狀態標識給JavaEye服務器:
- If-None-Match "427fe7b6442f2096dff4f92339305444"
- If-Modified-SinceFri,04Sep200905:55:43GMT
就是告訴服務器,我本地緩存的網頁最后修改時間和Etag是什么,請問你服務器的資源有沒有在我上次訪問之后有更新啊?于是JavaEye服務器會核對一下,如果該用戶上次訪問之后沒有更新過新聞,那么根本就不必生成這個RSS了,直接告訴瀏覽器:“沒什么新東西,你還是看自己緩存的網頁吧”,于是服務器就發送一個304 Not Modified的消息,其他什么都不用干了。
這就是HTTP層的Cache,使用這種基于資源的緩存機制,不但大大節省服務器程序資源,而且還減少了網頁下載次數,節約了很多網絡帶寬。
二、HTTP Cache究竟有什么作用?
我們通常的動態網站編程,服務器端程序根本就不去處理瀏覽器發送過來的If-None-Match和If-Modified-Since狀態標識,只要有請求就生成網頁發送給瀏覽器。對于一般情況來說,用戶不會總是沒完沒了刷新一個頁面,所以大家并不認為這種基于資源的緩存有什么太大的作用,但實際情況并非如此:
1、像Google這種比較智能的網絡爬蟲可以有效識別資源的狀態信息,如果使用這種緩存機制,可以大大減少爬蟲的爬取次數。
比方說Google每天爬JavaEye網站大概15萬次左右,但實際上JavaEye每天有更新的內容不會超過1萬個網頁。因為很多內容更新比較快,因此Google就會反復不停的爬取,這樣本身就造成了很多資源的浪費。如果我們使用HTTP Cache,那么只有當網頁內容發生改變的時候,才會真正進行爬取,其他時候我們直接告訴Google的爬蟲304 Not Modified就可以了。這樣不但降低了服務器本身的負載和爬蟲造成的網絡帶寬消耗,實際上也大大提高了Google爬蟲的工作效率,豈不是皆大歡喜?
2、很多內容更新不頻繁的網頁,盡管用戶不會頻繁的刷新,但是從一個比較長的時間段來看使用HTTP Cache,仍然可以起到很大的緩存作用。
比方說一些歷史討論帖子,已經過去了幾個月了,這些帖子內容很少更新。用戶可能通過搜索,收藏鏈接,文章關聯等方式時不時訪問到這個頁面。那么只要用戶訪問過一次以后,后續所有訪問服務器直接發送304 Not Modified就可以了,不用真正生成頁面。
3、對于歷史帖子使用HTTP Cache可以避免爬蟲反復的爬取。
比方說JavaEye的論壇帖子列表頁面,分頁到20頁后面的帖子已經很少有人直接訪問了,但是從服務器日志去看,每天仍然有大量爬蟲反復爬取這些分頁到很后面的頁面。這些頁面由于用戶很少去點擊,所以基本上沒有被應用程序的memcached緩存住,每次訪問都會造成很高的資源消耗,爬蟲隔一段時間就爬一次,對服務器是很大的負擔。如果使用了HTTP Cache,那么只要爬蟲爬過一次以后,以后無論爬蟲爬多少次,都可以直接返回304 Not Modified了,極大的節省了服務器的負載。
三、如何在應用程序里面使用HTTP Cache
如果我們要在自己的程序里面實現HTTP Cache,是件非常簡單的事情,特別是對Rails來說只需要添加一點點代碼,以上面的JavaEye新聞訂閱來說,只要添加一行代碼:
- def news
- fresh_when( :last_modified =>News.last.created_at, :etag =>News.last)
- end
用最新新聞文章作為Etag,該文章最后修改時間作為資源的最后修改時間,這樣就OK了。如果瀏覽器發送過來的標識和服務器標識一致,說明內容沒有更新,直接發送304 Not Modified;如果不一致,說明內容更新,瀏覽器本地的緩存太古老了,那么就需要服務器真正生成頁面了。
以上只是一個最簡單的例子,如果我們需要根據狀態做一些更多的工作也是很容易的。比方說JavaEye博客的RSS訂閱地址: http://robbin.iteye.com/rss
- @blogs = @blog_owner .last_blogs
- @hash = @blogs .collect{|b|{b.id=>b.post.modified_at.to_i+b.posts_count}}.hash
- if stale?( :last_modified =>( @blog_owner .last_blog.post.modified_at|| @blog_owner .last_blog.post.created_at), :etag => @hash )
- render :template => "rss/blog"
- end
這個實現稍微復雜一些。我們需要判斷博客訂閱所有的輸出文章是否有更新,所以我們用博客文章內容最后修改時間和博客的評論數量做一個hash,然后用這個hash值作為資源的Etag,那么只要這些博客文章當中任何文章內容被修改,或者有新評論,都會改變Etag值,從而通知瀏覽器內容有更新了。
除了RSS訂閱之外,JavaEye網站還有很多地方適合使用HTTP Cache,比方說JavaEye論壇的版面列表頁面,一些經常喜歡泡論壇的用戶,可能時不時會上來刷新一下版面, 看看有沒有新的帖子,那么我們就不必每次用戶請求的時候都去執行程序,生成頁面給他。我們判斷一下如果沒有新帖子的話,直接告訴他304 Not Modified就可以了,在沒有使用HTTP Cache之前的版面Action代碼:
- def board
- @topics = @forum .topics.paginate...
- @announcements =(params[ :page ]||1).to_i==1?Topic.find :all , :conditions =>...
- render :action => 'show'
- end
添加HTTP Cache以后,代碼如下:
- def board
- @topics = @forum .topics.paginate...
- if logged_in?||stale?( :last_modified => @topics [0].last_post.created_at, :etag => @topics .collect{|t|{t.id=>t.posts_count}}.hash)
- @announcements =(params[ :page ]||1).to_i==1?Topic.find :all , :conditions ...
- render :action => 'show'
- end
- end
對于登錄用戶,不使用HTTP Cache,這是因為登錄用戶需要實時接收站內短信通知和訂閱通知,因此我們只能對匿名用戶使用HTTP Cache,然后我們使用當前所有帖子id和回帖數構造hash作Etag,這樣只要當前分頁列表頁面有任何帖子發生改變或者有了新回帖,就更新頁面,否則就不必重新生成頁面。
論壇帖子頁面實際上也可以使用HTTP Cache,只不過Etag的hash算法稍微復雜一些,需要保證帖子的任何改動都要引起hash值的改變,示例代碼如下:
- def show
- @topic =Topic.findparams[ :id ]
- user_session.update_....... if logged_in?
- Topic.increment_counter(...) if ......
- @posts = @topic .post_by_pageparams[ :page ]
- posts_hash= @posts .collect{|p|{p.id=>p.modified_at}}.hash
- topic_hash= @topic .forum_id+ @topic .sys_tag_id.to_i+ @topic .title.hash+ @topic .status_flag.hash
- ad_hash=...(廣告的hash算法,略)
- if logged_in?||stale?( :etag =>[posts_hash,topic_hash,ad_hash])
- render
- end
- end
要分別根據主題貼,該分頁的所有回帖和帖子頁面的廣告內容進行hash,計算出來一個唯一的Etag值,保證任何改動都會生成新的Etag,這樣就搞定了,是不是很簡單!這種帖子的緩存非常有效,可以避免Rails去render頁面和下載頁面,極大的減輕了服務器負載和帶寬。
再舉一個需求比較特殊的例子:對于知識庫搜索相關文章的推薦頁面,比方說:http://www.iteye.com/wiki/topic/462476,也就是本文的相關文章推薦內容,我們并不希望用戶和爬蟲每次訪問這個頁面都實際執行一遍全文檢索,然后構造頁面內容,在一個相對不長的時間范圍內,這篇文章的相關推薦文章改變的概率不大,因此我們希望比方說5天之內,用戶重復訪問該頁面,就直接返回304 Not Modified,那么Rails沒有直接的設施給我們使用,需要我們稍微了解一些Rails的機制,自己編寫,代碼示例如下:
- def topic
- @topic =Topic.find(params[ :id ])
- unless logged_in?
- if request.not_modified?(5.days.ago)
- head :not_modified
- else
- response.last_modified= Time .now
- end
- end
- end
每次用戶請求,我們判斷用戶是否5天之內訪問過該頁面,如果訪問過,直接返回304 Not Modified,如果沒有訪問過,或者上次訪問已經超過了5天,那么設置最近修改時間為當前時間,然后生成頁面給用戶。是不是很簡單?
在給JavaEye網站所有的RSS訂閱輸出添加了HTTP Cache以后,通過一天的觀察發現,超過一半的RSS訂閱請求已經被緩存了,直接返回304 Not Modified,所以效果非常明顯,由于JavaEye網站每天RSS訂閱的動態請求就超過了10萬次,因此添加HTTP Cache可以減輕不少服務器的負擔和帶寬消耗。除此之外,新聞文章頁面,整個論壇頻道,知識庫相關推薦文章頁面都可以添加HTTP Cache,粗粗計算下來,JavaEye這些頁面統統使用HTTP Cache以后,網站整體性能至少可以提高10%。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
