回顧一下在REST WCF 4.0中可以這樣簡單實現緩存:
1、配置
<caching>
<outputCacheProfiles>
<add duration= " 20 " name= " outCache " varyByParam= " none "/>
</outputCacheProfiles>
</outputCacheSettings>
2、使用配置
[OperationContract]
[AspNetCacheProfile( " outCache ")]
public DateTime GetTime()
{
return DateTime.Now;
}
用Fiddler測試一下請求:在Request Builder選項卡中,選擇Get方法,輸入測試地址,點擊執行獲取的HTTP響應頭如下圖:
(圖1)
注意之間紅色下划線部門的兩個HTTP頭:Cache-Control、Expires這是HTTP協議中有關緩存信息
的兩個重要頭信息,留個懸念先,稍后會講到。
緩存在Web獲取信息上是一個很重要的手段,通過HTTP安全且冪等的GET操作進行。
在REST架構中,由於服務端與客戶端交互的無狀態性(注意:REST中所提到的無狀態指的就是應
用無狀態。Web架構的關鍵原則之一就是:服務器和服務不應該維護應用狀態,作為基於HTTP協議
的REST架構風格的服務也完全如此)。
這樣就會有一個問題,服務為了識別客戶端,每次客戶端希望通過服務獲取資源時,
都必須將交換應用狀態的信息,這就有可能帶來性能上的損失。好在REST架構中,我們也可以
利用緩存,這在一定程度上大大提升了服務的吞吐量。但是弊端也是顯而易見的:客戶端獲取到
的信息誰也無法保證和服務器同步。這就是緩存的弱一致性(weak consistency)。
一個資源的表述只在一段時間范圍內是有效的,這段時間就是此資源的保鮮周期。一個
過期的資源表述需要通過服務端的驗證然后才能使用。
緩存類型
按照部署方式分,緩存分為本地緩存與共享緩存。共享緩存又包括代理緩存(proxy cache)與
反向代理緩存(reverse proxy cache)。
- 本地緩存(也稱客戶端緩存)客戶端在獲取資源表述后,通過瀏覽器將資源緩存在本地磁盤或者內存之中。
這樣當下次訪問這些資源時就可以直接從本地獲取。
優點:避免了網絡傳輸,速度最快
缺點:由於只緩存在客戶端本地,所以當另外有客戶端請求時只能從服務端獲取。
- 代理緩存在服務端與客戶端之間,部署一個雙方都能共享的服務器來緩存數據,這就是代理緩存。它可以位於企業防火牆內部或者外部。
優點:所有能通過代理緩存服務器獲取資源的客戶端都能共享緩存在其中的資源表述。
缺點:增加了額外的部署設施
- 反向代理緩存。反向代理又稱web服務加速器,位於Web服務器前端,充當Web服務器內容緩存服務器。
它針對的是web服務器而不是瀏覽器用戶,所以成為反向代理,一般位於Web服務器前面。它是根據
由Web源服務器返回的HTTP頭信息來對信息進行緩存。常用的反向代理實現有Squid等。
緩存的HTTP響應頭信息
有兩種主要的HTTP響應頭信息用於控制緩存的行為:Expires、Cached-Control。這就回到上圖中HTTP的響應
信息了。那么這兩個指令是對緩存有何作用?
- Expires
Expires用於控制緩存表述的絕對到期時間,過了這個時間表述就失效了,客戶端的請求必須發送到服務端,由服務
端重新計算並發出請求相應的響應。如果Expires的值和Date的值相等或者Expires=0,那么表述立刻過期。若想將
表述設置為永不過期,那么服務可以將Expires設置為一年。
- Cached-Control
Cached-Control與Expires不同,它可以被用來請求和響應上來控制緩存行為。它的值可以由多段指令組成,每個
指令之間用分號(;)隔開。這些指令決定緩存在哪,緩存期多長。
小結:如果我們可以確定一個表述的到期時間,那么可以使用Expires。如果指明緩存離開源服務器后緩存多久更
合適的話,可以使用Cached-Control的max-age或s-maxage (max-age、s-maxage單位都為秒)。對於可緩存
的表述,服務端最好都能在響應的HTTP頭信息中包含ETag、Last-Modified信息(如果你對ETag不熟悉,可以參見
上節)。
如果客戶端想要在服務端重新驗證緩存的表述是否過期,可以在請求HTTP頭信息中使用Cached-Control中的
no-cache指令。
另外兩個緩存中重要的HTTP頭位:Last-Modified與Date。前者表述資源最后修改時間,后者表示表述生成時間。
Cached-Control,在HTTP協議規范中,通過標准化Cached-Control指令來控制緩存。
Cached-Control主要指令介紹
- max-age:可以用於請求或者響應中。
在響應中,可同時控制緩存和新鮮度。它使本地緩存和共享緩存(代理緩存與反向代理緩存)將一個表述緩存起
來,由它的值決定表述經過多少時間過期。單位為秒。它的值將覆蓋Expires中的值。響應如:
Cached-Control:max-age=20(如上Cached-ontrol:private(如上圖)
在請求中,表示客戶端能接受生成時間不早於max-age值的被緩存表述。如果客戶端指定max-age=0,則請求
會導致緩存被發送到源服務端進行驗證,從而獲取到最新的表述。也就是說max-age的值是客戶端當前請求時間
與資源在源服務端生成資源的時間比較標准。即當前時間-緩存資源在源服務器端生成的時間.
- no-cache:可以用於請求或者響應中。
在響應中,它要求每個緩存都到服務端去做驗證。它只有在表述被緩存時才起作用,在無緩存時,
有沒有它客戶端都得到服務端獲取資源的表述。響應如:Cached-Control: no-cache
在請求中,它用max-age=0一樣,客戶端獲取到最新資源的表述。與max-age=0不同的是,
no-cache使所有中間代理中所有的被緩存的表述都被更新為最新的表述, 而max-age=0中中間代理
緩存可以被繼續使用
- no-store:可以用於請求或者響應中。
在響應中,它將可以被緩存的表述不被緩存。響應如:Cached-Control: no-store
在請求中,要求資源不被緩存,並且獲取到的都是最新的表述。
- s-maxage:用於響應中。
它與max-age一樣。但是它只能由共享緩存(代理緩存與反向代理緩存)來做,不能使用本地緩存。響應如:
Cached-Control:s-maxage=20 區分 s-maxage與max-age,前者帶有s,意味着 共享(share)。
- public:用於響應中。使本地緩存或者共享緩存(代理緩存與反向代理緩存)將一個響應緩存起來,但是它不像s-
maxage一樣指定一個保險周期。public優先級要高於 Authorization頭信息。一般如果HTTP響應中包含
Authorization,那么表述不會被緩存;但是如果同時又包含了pulic頭信息,那么表述一定可以被緩存。
如果客戶端獲取資源表述時,需要被認證為合法的客戶端,那么在使用public時應該謹慎。因為緩存服務器不會
關心對客戶端的驗證問題.響應如:Cached-Control:public
- private:用於響應中。使表述可以被緩存,並僅僅使用本地緩存,即在客戶端實現。同樣它也不指定新鮮度。
- must-revalidate:用於響應中。它通常將可緩存的表述緩存起來,但客戶端在獲取緩存表述時,它會將
緩存信息發送到服務端做驗證。只有驗證成功,表述才是可用的表述。 對於客戶端的請求,緩存也會被
發送到服務端做驗證,但是相比直接從服務端獲取資源的表述,它是一個相對高效的操作,因為它防止
服務端業務邏輯被引為客戶端的請求而被反復調用。響應如:Cached-Control: must-revalidate
- proxy-revalidate:用於響應中。它與must-revalidate一樣,但是只限於在使用共享代理緩存中。響應
- max-stale:用於請求中。使用形式:max-stale=36。客戶端可以接受max-stale指定值過期范圍內的表述。
如果客戶端忽略此值,表示它可接受任何過期的表述
- min-fresh:用於請求中。使用形式:min-fresh=36。客戶端可以接受資源生成時間加上min-fresh指定值仍然
最新的被緩存表述。
- only-if-cached:用於請求中。客戶端僅返回被緩存的表述。如果緩存中不存在資源的最新表述,則返回504狀碼。
新增的兩條指令:
- stale-while-revalidate:用於響應中。它使緩存在響應客戶端時同時將緩存發到服務端去做驗證
(以異步非阻塞模式發送到服務端做驗證)。它允許緩存能離開響應給客戶端,即使此響應的表述已經過期。
因此,它是以犧牲一致性為代價來響應客戶端,減少了延遲。在它指定的時間(單位為秒)過去之后,如果服務端
對表述的驗證還沒有完成,那么緩存將被丟棄。響應如:stale-while-revalidate=20
- stale-if-error:用於響應中。它允許如果客戶端在與服務器的交互過程中發生錯誤的情況下,返回給客戶端一個
可能過期的表述。《REST in Practice 》中說:如果被緩存的表述超出stale-if-error指定的時間范圍,那么緩
存就不應該被響應給客戶端。stale-if-error值的單位為秒。它的響應形式如:Cache-Control:stale-if-error=20。
我的理解是:如果某個被緩存的表述的如下響應頭:
那么在Date指定時間后20秒,也就是27 Dec 2011 09:24:50 GMT后,此表述就不應該響應給客戶端。
另外還有兩條指令,是為了實現緩存通道而使用的,下面會介紹到。
緩存通道(cache channel)
它是一種實現了延長被緩存資源的保鮮期的技術。解析不了資源響應頭的(即緩存通道協議)的緩存在保鮮期
過了就失效了,而理解資源響應頭的緩存在資源過期后仍將其視為最新的資源,緩存服務器收到過期的通知。
當客戶端采用no-cache指令發送請求,強行要求資源到服務端驗證就可以讓緩存被更新。
緩存通道使用了兩個Cache-Control指令:
- channel:它提供通道擴展的絕對URI路徑。緩存可以訂閱此URI以獲取一個與緩存相關的事件通知
- group:擴展提供了一個能夠用來對多個緩存進行分組的相對URI。
通過max-age=600指定緩存的過期時間后,再次接收到客戶端請求時,緩存必須從源服務器驗證、獲取。
但是在滿足一下條件下,在發回客戶端的路徑上,如能理解緩存通道協議,即理解channel與group的緩存都將
能被延續其保鮮期。
1、緩存持續輪詢,頻率至少比低於通道自己定義的時間間隔
2、無論是同被緩存表述的URI相關聯還是同被緩存表述所屬組的URI相關聯的通道都沒有發出過期信息。
如下圖提要:
precision定義了通道指定的精確度。也就是說,緩存必須至少15秒內輪詢一次通道就可以延長與此通道相關聯
的所有被緩存表述的保鮮期。
lifecycle:指定了此提要中的事件在發布后的至少一個小時內都是可用的
precision與lifecycle並不是FCL中SyndicationFeed對象所支持的屬性,而是擴展的屬性。這也符合緩存通道中:
能理解緩存響的緩存的宗旨
實現以上提要(feed)的代碼如下:
SyndicationFeed feed = new SyndicationFeed { Title = new TextSyndicationContent("feed Tile"), LastUpdatedTime = new DateTimeOffset(DateTime.Now), Description = new TextSyndicationContent("feed Description Content") }; feed.Authors.Add(new SyndicationPerson("aa@gmial.com", "zhanshan", "cnblogs.com")); feed.Language = "us-en"; feed.Generator = "test feed"; feed.Links.Add(new SyndicationLink(new Uri("http://www.cnblogs.com/tyb1222"), "via", "linkTitle", "application/Atom+xml",1000)); feed.Categories.Add(new SyndicationCategory("blog")); feed.Contributors.Add(new SyndicationPerson("aa@gmial.com", "zhanshan", "cnblogs.com")); feed.ElementExtensions.Add(new SyndicationElementExtension("percision","http://tyb1222.cnblogs.com",900)); feed.ElementExtensions.Add(new SyndicationElementExtension("lifecycle", "http://tyb1222.cnblogs.com", 3600)); return feed; 如果提要中包含一個過期的事件項,它的link控件將它與資源表述所屬組的ID相關聯(也就是Cache-Control中group指定
的ID)。通過href的值就能此組的資源關聯起來。接收到這個提要后,緩存停止延長此組資源的緩存保鮮期,客戶端下次通過
Get請求獲取資源時,緩存將重新到服務端進行驗證此過期的表述 。下圖為包含過期事件項的提要:
將帶有過期想的條目(entry)的事件項,只設置SyndicationItem示例的Update屬性。代碼如下:
SyndicationItem item1 = new SyndicationItem("Title1", "Microsoft may have patents, patent applications, trademarks, ...more", new Uri("36",UriKind.Relative)) {Summary = new TextSyndicationContent("sumary"), LastUpdatedTime = DateTime.Now};
當然緩存通道技術的實現並不一定要使用Atom來實現,這里只是使用它做個范例。如果你對Atom協議不清楚可以參見這里