基於資源的HTTP Cache的實現介紹
我們都知道瀏覽器會快取訪問過網站的網頁,瀏覽器通過URL地址訪問一個網頁,顯示網頁內容的同時會在電腦上面快取網頁內容。如果網頁沒有更新的話,瀏覽器再次訪問這個URL地址的時候,就不會再次下載網頁,而是直接使用本地快取的網頁。只有當網站明確標識資源已經更新,瀏覽器才會再次下載網頁。
[size=medium]一、什麼是HTTP Cache[/size]
對於瀏覽器的這種網頁快取機制大家已經耳熟能詳了,舉個例子來說,JavaEye的新聞訂閱地址:http://www.iteye.com/rss/news , 當瀏覽器或者訂閱程式訪問這個URL地址的時候,JavaEye的伺服器在response的header裡面會傳送給瀏覽器如下狀態標識:
這就是告訴瀏覽器,新聞訂閱這個網路資源的最後修改時間和Etag。於是瀏覽器把這兩個狀態資訊連同網頁內容在本地進行快取,當瀏覽器再次訪問JavaEye新聞訂閱地址的時候,瀏覽器會傳送如下兩個狀態標識給JavaEye伺服器:
就是告訴伺服器,我本地快取的網頁最後修改時間和Etag是什麼,請問你伺服器的資源有沒有在我上次訪問之後有更新啊?於是JavaEye伺服器會核對一下,如果該使用者上次訪問之後沒有更新過新聞,那麼根本就不必生成這個RSS了,直接告訴瀏覽器:“沒什麼新東西,你還是看自己快取的網頁吧”,於是伺服器就傳送一個304 Not Modified的訊息,其他什麼都不用幹了。
這就是HTTP層的Cache,使用這種基於資源的快取機制,不但大大節省伺服器程式資源,而且還減少了網頁下載次數,節約了很多網路頻寬。
[size=medium]二、HTTP Cache究竟有什麼作用?[/size]
我們通常的動態網站程式設計,伺服器端程式根本就不去處理瀏覽器傳送過來的If-None-Match和If-Modified-Since狀態標識,只要有請求就生成網頁傳送給瀏覽器。對於一般情況來說,使用者不會總是沒完沒了重新整理一個頁面,所以大家並不認為這種基於資源的快取有什麼太大的作用,但實際情況並非如此:
[size=medium]1、像Google這種比較智慧的網路爬蟲可以有效識別資源的狀態資訊,如果使用這種快取機制,可以大大減少爬蟲的爬取次數。[/size]
比方說Google每天爬JavaEye網站大概15萬次左右,但實際上JavaEye每天有更新的內容不會超過1萬個網頁。因為很多內容更新比較快,因此Google就會反覆不停的爬取,這樣本身就造成了很多資源的浪費。如果我們使用HTTP Cache,那麼只有當網頁內容發生改變的時候,才會真正進行爬取,其他時候我們直接告訴Google的爬蟲304 Not Modified就可以了。這樣不但降低了伺服器本身的負載和爬蟲造成的網路頻寬消耗,實際上也大大提高了Google爬蟲的工作效率,豈不是皆大歡喜?
[size=medium]2、很多內容更新不頻繁的網頁,儘管使用者不會頻繁的重新整理,但是從一個比較長的時間段來看使用HTTP Cache,仍然可以起到很大的快取作用。[/size]
比方說一些歷史討論帖子,已經過去了幾個月了,這些帖子內容很少更新。使用者可能通過搜尋,收藏連結,文章關聯等方式時不時訪問到這個頁面。那麼只要使用者訪問過一次以後,後續所有訪問伺服器直接傳送304 Not Modified就可以了,不用真正生成頁面。
[size=medium]3、對於歷史帖子使用HTTP Cache可以避免爬蟲反覆的爬取。[/size]
比方說JavaEye的論壇帖子列表頁面,分頁到20頁後面的帖子已經很少有人直接訪問了,但是從伺服器日誌去看,每天仍然有大量爬蟲反覆爬取這些分頁到很後面的頁面。這些頁面由於使用者很少去點選,所以基本上沒有被應用程式的memcached快取住,每次訪問都會造成很高的資源消耗,爬蟲隔一段時間就爬一次,對伺服器是很大的負擔。如果使用了HTTP Cache,那麼只要爬蟲爬過一次以後,以後無論爬蟲爬多少次,都可以直接返回304 Not Modified了,極大的節省了伺服器的負載。
[size=medium]三、如何在應用程式裡面使用HTTP Cache[/size]
如果我們要在自己的程式裡面實現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%。
[size=medium]一、什麼是HTTP Cache[/size]
對於瀏覽器的這種網頁快取機制大家已經耳熟能詳了,舉個例子來說,JavaEye的新聞訂閱地址:http://www.iteye.com/rss/news , 當瀏覽器或者訂閱程式訪問這個URL地址的時候,JavaEye的伺服器在response的header裡面會傳送給瀏覽器如下狀態標識:
Etag "427fe7b6442f2096dff4f92339305444"
Last-Modified Fri, 04 Sep 2009 05:55:43 GMT
這就是告訴瀏覽器,新聞訂閱這個網路資源的最後修改時間和Etag。於是瀏覽器把這兩個狀態資訊連同網頁內容在本地進行快取,當瀏覽器再次訪問JavaEye新聞訂閱地址的時候,瀏覽器會傳送如下兩個狀態標識給JavaEye伺服器:
If-None-Match "427fe7b6442f2096dff4f92339305444"
If-Modified-Since Fri, 04 Sep 2009 05:55:43 GMT
就是告訴伺服器,我本地快取的網頁最後修改時間和Etag是什麼,請問你伺服器的資源有沒有在我上次訪問之後有更新啊?於是JavaEye伺服器會核對一下,如果該使用者上次訪問之後沒有更新過新聞,那麼根本就不必生成這個RSS了,直接告訴瀏覽器:“沒什麼新東西,你還是看自己快取的網頁吧”,於是伺服器就傳送一個304 Not Modified的訊息,其他什麼都不用幹了。
這就是HTTP層的Cache,使用這種基於資源的快取機制,不但大大節省伺服器程式資源,而且還減少了網頁下載次數,節約了很多網路頻寬。
[size=medium]二、HTTP Cache究竟有什麼作用?[/size]
我們通常的動態網站程式設計,伺服器端程式根本就不去處理瀏覽器傳送過來的If-None-Match和If-Modified-Since狀態標識,只要有請求就生成網頁傳送給瀏覽器。對於一般情況來說,使用者不會總是沒完沒了重新整理一個頁面,所以大家並不認為這種基於資源的快取有什麼太大的作用,但實際情況並非如此:
[size=medium]1、像Google這種比較智慧的網路爬蟲可以有效識別資源的狀態資訊,如果使用這種快取機制,可以大大減少爬蟲的爬取次數。[/size]
比方說Google每天爬JavaEye網站大概15萬次左右,但實際上JavaEye每天有更新的內容不會超過1萬個網頁。因為很多內容更新比較快,因此Google就會反覆不停的爬取,這樣本身就造成了很多資源的浪費。如果我們使用HTTP Cache,那麼只有當網頁內容發生改變的時候,才會真正進行爬取,其他時候我們直接告訴Google的爬蟲304 Not Modified就可以了。這樣不但降低了伺服器本身的負載和爬蟲造成的網路頻寬消耗,實際上也大大提高了Google爬蟲的工作效率,豈不是皆大歡喜?
[size=medium]2、很多內容更新不頻繁的網頁,儘管使用者不會頻繁的重新整理,但是從一個比較長的時間段來看使用HTTP Cache,仍然可以起到很大的快取作用。[/size]
比方說一些歷史討論帖子,已經過去了幾個月了,這些帖子內容很少更新。使用者可能通過搜尋,收藏連結,文章關聯等方式時不時訪問到這個頁面。那麼只要使用者訪問過一次以後,後續所有訪問伺服器直接傳送304 Not Modified就可以了,不用真正生成頁面。
[size=medium]3、對於歷史帖子使用HTTP Cache可以避免爬蟲反覆的爬取。[/size]
比方說JavaEye的論壇帖子列表頁面,分頁到20頁後面的帖子已經很少有人直接訪問了,但是從伺服器日誌去看,每天仍然有大量爬蟲反覆爬取這些分頁到很後面的頁面。這些頁面由於使用者很少去點選,所以基本上沒有被應用程式的memcached快取住,每次訪問都會造成很高的資源消耗,爬蟲隔一段時間就爬一次,對伺服器是很大的負擔。如果使用了HTTP Cache,那麼只要爬蟲爬過一次以後,以後無論爬蟲爬多少次,都可以直接返回304 Not Modified了,極大的節省了伺服器的負載。
[size=medium]三、如何在應用程式裡面使用HTTP Cache[/size]
如果我們要在自己的程式裡面實現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.find params[:id]
user_session.update_....... if logged_in?
Topic.increment_counter(...) if ......
@posts = @topic.post_by_page params[: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%。
相關文章
- 基於FPGA的乘法器原理介紹及設計實現FPGA
- 介紹基於事件的架構事件架構
- 簡單介紹基於Redis的List實現特價商品列表功能Redis
- Couchbase 介紹 - 更好的 Cache 系統
- 基於 OData V4 的本地 Mock Server 實現的深入介紹試讀版MockServer
- 基於TableStore的資料採集分析系統介紹
- 簡單介紹recorder.js 基於Html5錄音功能的實現JSHTML
- Spring Cache 介紹Spring
- Http Module 的詳細介紹HTTP
- HTTPS 和HTTP的介紹HTTP
- 基於Flutter實現的 IT換換(已開源)Flutter
- 前端基礎之HTTP協議介紹前端HTTP協議
- 關於mysql基礎知識的介紹MySql
- 基於DataX的資料同步(上)-DataX介紹以及安裝
- Wire:基於安卓的谷歌的Protocol Buffers的開源實現安卓谷歌Protocol
- LSM樹的不同實現介紹
- 基於Yarp實現內網http穿透內網HTTP穿透
- 基於HttpClient實現Http訪問工具類HTTPclient
- Apache (http server)的詳細介紹ApacheHTTPServer
- 從零手寫實現 nginx-19-HTTP CORS(Cross-Origin Resource Sharing,跨源資源共享)介紹+解決方案NginxHTTPCORSROS
- 分散式鎖(5)-MLock使用介紹(自己實現,基於redis,適用於真實專案)分散式Redis
- 基於.NET CORE微服務框架 -surging的介紹和簡單示例 (開源)微服務框架
- MAE自監督演算法介紹和基於EasyCV的復現演算法
- javascript繼承的實現方式介紹JavaScript繼承
- 使用 proxy 實現的 mobx - dob 介紹
- 簡單介紹NMS的實現方法
- 利用基於samba服務的cifs檔案系統實現共享資源Samba
- 基於註解的springboot+mybatis的多資料來源元件的實現Spring BootMyBatis元件
- Cache中的MESI協議基本知識介紹協議
- HTTP介紹和HTML簡介HTTPHTML
- HTTP header介紹HTTPHeader
- HTTP Client Hints 介紹HTTPclient
- 快取融合(Cache Fusion)介紹快取
- 【轉載】Spring Cache介紹Spring
- 資源 | Facebook開源DrQA的PyTorch實現:基於維基百科的問答系統PyTorch
- 2.1 基於python開發的資料比對工具--SYDCTOOL介紹Python
- ActiveMq的基礎介紹MQ
- JDBC的基礎介紹JDBC