這里的Cache 緩存可以是私有的也可以是共享的。

客戶端程序發送請求 GET countries,這時還沒有緩存版本的響應,所以緩存會繼續把請求發送到API服務器;然后API返回響應給緩存,響應里面包含了Cache-Control這個Header,Cache-Control聲明了響應會保持“新鮮”(或者叫有效)半個小時,最后緩存把響應返回給客戶端,但同時緩存復制了一份響應保存了起來。

然后比如10分鐘之后,客戶端又發送了一樣的請求:

這時,緩存里的響應還在有效期內,緩存會直接返回這個響應,響應里包含一個age Header,針對這個例子(10分鐘),age的值就是600(秒)。

這種情況下,對API服務器的請求就被避免了,只有在緩存過期(或者叫不新鮮 Stale)的情況下,緩存才會訪問后端的API服務器。

如果緩存是私有的,例如在web應用的localstorage里面,或者手機設備上,請求到此就停止了。

如果緩存是共享的,例如緩存在服務器上,情況就不一樣了。

比如說10分鐘之后另一個客戶端發送了同樣的請求,這個請求肯定首先來到緩存這里,如果緩存還沒有過期,那么緩存會直接把響應返回給客戶端,這次age Header的值就是1200(秒),20分鐘了:

總的來說私有緩存會減少網絡帶寬的需求,同時會減少從緩存到API的請求

共享緩存并不會節省緩存到API的網絡帶寬,但是它會大幅減少到API的請求。例如同時10000個客戶端發出了同樣請求到API,第一個到達的請求會來到API程序這里,而其它的同樣請求只會來到緩存,這也意味著代碼的執行量會大大減少,訪問數據庫的次數也會大大減少,等等。

所以組合使用私有緩存和共享緩存(客戶端緩存和公共/網關緩存)還是不錯的。但是這種緩存還是更適用于比較靜態的資源,例如圖片、內容網頁;而對于數據經常變化的API并不太合適。如果API添加了一條數據,那么針對這10000個客戶端,所緩存的數據就不對了,針對這個例子有可能半個小時都會返回不正確的數據,這時就需要用到驗證模型了。

驗證模型

驗證模型用于驗證緩存的響應數據是否是保持最新的。

這種情況下,當被緩存的數據將要成為客戶端請求的響應的時候,它首先會檢查一下源服務器或者擁有最新數據的中間緩存,看看它所緩存的數據是否仍然最新。這里就要用到驗證器

驗證器

驗證器分為兩種:強驗證器弱驗證器

強驗證器如果響應的body或者header發生了變化,強驗證器就會變化。典型的例子就是ETag(Entity Tag)響應header,例如:ETag: “12345678”,ETag是由Web服務器或者API發配的不透明標識,它代表著某個資源的特定版本。強驗證器可以在任意帶有緩存的上下文中使用,在更新資源的時候強驗證器可以用來做并發檢查

弱驗證器當響應變化的時候,弱驗證器通常不一定會變化,由服務器來決定什么時候變化,通常的做法有“只有在重要變化發生的時候才變化”。一個典型的例子就是Last-Modified(最后修改時間)這個Header ,例如:Mon, 11 Jun 2018 13:55:41 GMT,它里面包含著資源最后修改的時間,這個就有點弱,因為它精確到秒,因為有可能一秒內對資源進行兩次以上的更新。但即使針對弱驗證器,時鐘也必須同步,所以它和expires header有同樣的問題,所以ETag是更好的選擇。

還有一種弱ETag,它以w/開頭,例如ETag: “w/123456789″,它被當作弱驗證器來對待,但是還是由服務器來決定其程度。當ETag是這種格式的時候,如果響應有變化,它不一定就變化。

弱驗證器只有在允許等價(大致相等)的情況下可已使用,而在要求完全相等的需求下是不可以使用的

HTTP標準建議如果可能的話最好還是同時發送ETag和Last-Modified這兩個Header。

下面看看其工作原理。客戶端第一次請求的時候,請求到達緩存后發現緩存里沒有,然后緩存把請求發送到API;API返回響應,這個響應包含ETag和Last-Modified 這兩個Header,響應被發送到緩存,然后緩存再把它發送給客戶端,與此同時緩存保存了這個響應的一個副本。

10分鐘后,客戶端再次發送了同樣的請求,請求來到緩存,但是無法保證緩存的響應是“新鮮”的,這個例子里并沒有使用Cache-Control Header,所以緩存就必須到服務器的API去做檢查。這時它會添加兩個Headers:If-None-Match,它被設為已緩存響應數據的ETag的值;If-Modified-Since,它被設為已緩存響應數據的Last-Modified的值。現在這個請求就是根據情況而定的了,服務器接收到這個請求并會根據證器來比較這些header或者生成響應。

如果檢查合格,服務器就不需要生成響應了,它會返回304 Not Modified,然后緩存會返回緩存的響應,這個響應還包含了一個最新的Last-Modified Header(如果支持Last-Modifed的話);

而如果響應的資源發生變化了,API就會生成新的響應。

如果是私有緩存,那就請求就會停在這。

但如果是共享緩存的話,假如10分鐘之后另一個客戶端發送了請求,這個請求也會到達緩存,然后跟上面一樣的流程:

總的來說就是,同樣的響應只會被生成一次。

對比一下:

私有緩存:后續的請求會節省網絡帶寬,我們需要與API進行通信,但是API不需要把完整的響應返回來,如果資源沒有變化的話只需要返回304即可。

共享緩存:會節省緩存和API之間的帶寬,如果驗證通過的話,API不需要重新生成響應然后重新發送回來。

過期模型和驗證模型還是經常被組合使用的

組合使用過期模型和驗證模型

可以這樣做:

如果使用私有緩存,這時只要響應沒有過期,那么響應直接會從私有緩存返回。這樣做的好處就是減少了與API之間的通信,也減少了API生成響應的工作,減輕了帶寬需求。而如果私有緩存過期了,那還是會訪問到API的。如果只有過期(模型)檢查的話,這就意味著如果過期了API就得重新生成響應。但是如果使用驗證(模型)檢查的話,我們可能就會避免這種情況。因為緩存的響應過期了并不代表緩存的響應就不是有效的了,API會檢查驗證器,如果響應依然有效,就會返回304。這樣網絡帶寬和響應的生成動作都有可能被大幅度減少了。

如果是共享緩存,緩存的響應只要沒過期就會一直被返回,這樣雖然不會節省客戶端和緩存之間的網絡帶寬,但是會節省緩存和API之間的網絡帶寬,同時也大幅度減少了到API的請求次數,這個要比私有緩存幅度大,因為共享緩存是共享與可能是所有的客戶端的。如果緩存的響應過期了,緩存就必須與API通信,但這也不一定就意味著響應必須被重新生成。如果驗證成功,就會返回304,沒有響應body,這就有可能減少了緩存和API之間的網絡帶寬需求,響應還是從緩存返回到客戶端的。

所以綜上,客戶端配備私有緩存,服務器級別配備共享緩存就應該是最佳的實踐

Cache-Control的指令

先看一下響應的Cache-Control常用指令:

上面這些都是由服務器決定的, 但是客戶端可以覆蓋其中的一些設定.

請求的Cache-Control常用指令:

 到目前也介紹了幾個指令了, 其實大多數情況下使用max-age和public, private即可…

更多指令請查看: https://tools.ietf.org/html/rfc7234#section-5.2

Cache Headers

根據REST的約束, 為了支持HTTP緩存, 我們需要一個可以生成正確的響應Header的組件, 并且可以檢查發送的請求的Header, 所以我們可以返回304 Not Modified或者412 Preconditioned Failed.

這個組件應該位于緩存的后端, ASP.NET Core里有個自帶的屬性標簽 [ResponseCache] (https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-2.1#responsecache-attribute), 它可以應用于Controller的Actions. 為設定適當響應緩存Header它可以指定所需的參數. 它只能做這些, 無法在緩存里存儲響應, 它并不是緩存存儲. 而且因為它好像不支持ETag, 所以暫時先不使用這個.

可以考慮CacheCow,它可以生成ETag,也支持.NET Core,但是它并沒有內置中間件來返回304。所以我這里使用的是Marvin.Cache.Headers

安裝: 

Startup的ConfigureServices方法里配置:

這里還可以配置Header的生成選項,但暫時先使用默認的配置。 

然后在Configure方法里,把這個中間件添加在app.useMvc()之前:

這里就是處理并返回304的邏輯。

還需要設置一下Postman, 要保證Send no-cache header這一項是off的:

發送請求測試:

這是第一次訪問,會執行Action方法,然后返回響應。響應的Header如上圖所示,里面包含了緩存相關的Header。

默認的Cache-Control是public,max-age是60秒。Expires header也反映了過期的時間,也就是1分鐘之后。

用于驗證的ETag和Last-Modified也被生成和添加了,Last-Modified就是現在的時間。

ETag的生成邏輯并不是標準的一部分,這個可以由我們自己來決定。當讓響應是等價的還是完全相等的也是由我們來決定。

默認情況下,這個中間件會考慮到請求路徑、Accept、Accept-language 這些Header以及響應的body。

再次發送該請求,由于已經超過了1分鐘,所以還是會走Action方法的:

還是走了這個Action方法!!

Header還是有變化的。

這個現象是沒有問題的,因為這個庫只是負責生成Header和驗證,它并不是緩存存儲器。

想要緩存數據,那就需要一個緩存存儲器了,可以是私有、公共的也可以是兩者兼顧的。這個一會再說。

先來看看驗證,如果一個響應是不新鮮的(過期的),我們知道這樣話緩存必須進行重新驗證,最好是用ETag進行驗證,他會把ETag的值賦給If-None-Match這個Header:

這時就會返回304 Not Modified,而Action方法也不會執行。

下面測試一下PUT動作:

更新數據之后,我再發送一次之前的GET請求:

這次Action方法又被執行了,這說明驗證失敗了,因為ETag已經不一致了,當我發送PUT請求的時候,生成了一個新的ETag。

我們也可以對如何生成Header進行配置,打開Startup的ConfigureServices方法:

配置參數還是很多的,這里我分別為過期模型和驗證模型修改了一個參數。

過期模型的max-age設為600秒。驗證模型為Cache-Control添加了must-revalidate指令,也就是說如果緩存的響應過期了,那么必須進行重新驗證。

再次發送那個GET請求:

重新執行了Action方法,也可以看到響應Header的變化。

緩存存儲

之前只是生成了緩存相關的Header,還沒有進行真正的存儲,現在就介紹存儲這部分。

緩存有私有的、共享的等。

私有的不在我們討論的范圍內,因為它在客戶端。

私有和共享緩存,有一些緩存是兩者的混合,根據你在哪使用它來決定給其類型。例如CacheCow

微軟提供了一個共享緩存,支持.NET Core:ResponseCaching中間件(https://docs.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-2.1)。

這個中間件會檢查Marvin.Cache.Headers這個中間件生成的Header,并把響應放到緩存并根據Header把它們服務給客戶端,但是ResponseCaching中間件它自己并不會生成這些Header

在ConfigureServices里注冊:

然后在Configure方法里,把這個緩存存儲添加到管道:

注意順序,要保證它在UseHttpCacheHeaders()之前。

測試,發送GET請求:

這次會執行Action方法,返回響應。

再次發送GET請求:

這次沒有走進Action方法里,而是從緩存返回的,這里還多了一個Age header,它告訴了我響應的”年齡“,他已經活了123秒了。

再次請求:

年齡變成了243秒,還是小于600秒。很顯然這提高了應用的性能。。。

到目前我們可以生成Cache-Control和Etag的Headers了,但是還沒有用到ETag的另一個功能:

并發控制

看下面這個情況,很常見:

兩個客戶端1和2,客戶1先獲取了id為1的Country資源,隨后客戶2也獲取了這個資源;然后客戶2對資源進行了修改,先進行了PUT動作進行更新,然后客戶1才修改好Country然后PUT到服務器。

這時客戶1就會把客戶2的更改完全覆蓋掉,這是個常見問題。

針對這樣的問題,我們需要使用一些處理并發沖突的策略:悲觀并發控制樂觀并發控制

悲觀并發控制意味著資源是為客戶1鎖定的,只要資源處于鎖定的狀態,別人就不能修改它,只有客戶1可以修改它。但是悲觀并發控制是無法在REST下實現的,因為REST有個無狀態約束。

樂觀并發控制這就意味著客戶1會得到一個Token,并允許他更新資源,只要Token是合理有效的,那么客戶1就一直可以更新該資源。在REST里這是可以實現的,而這個Token就是個驗證器,而且要求是強驗證器,所以我們可以用ETag

回到例子:

客戶1發送GET請求,返回響應并帶著ETag Header。然后客戶2發送同樣的請求,返回同樣的響應和Etag。

客戶2先進行更新,并把Etag的值賦給了If-Match Header,API檢查這個Header并和它為這個響應所保存的ETag值進行比較,這時針對這個響應會生成新的ETag,響應包含著這個新的ETag。

然后客戶1進行PUT更新操作,它的If-Match Header的值是客戶1之前得到的ETag的值,在到達API之后,API就知道這個和資源最新的ETag的值不一樣,所以API會返回412 Precondition Failed。

所以客戶1的更新沒有成功,因為它使用的是老版本的資源。這就是樂觀并發控制的工作原理。

下面看測試,

客戶1先GET:

客戶2GET:

注意他們兩個的ETag是一樣的。

然后客戶2先更新:

最后客戶1再更新(使用的是老的ETag):

返回412。

本文比較短,一些關于緩存技術的內容并沒有寫,距離REST的主題有點遠。

ASP.NET Core關于緩存部分的文檔在這里:https://docs.microsoft.com/en-us/aspnet/core/performance/caching/?view=aspnetcore-2.1

本系列的源碼在:https://github.com/solenovex/ASP.NET-Core-2.0-RESTful-API-Tutorial

相關文章:

原文地址: https://www.cnblogs.com/cgzl/p/9165388.html


.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com

本文章轉載微信公眾號@dotNET跨平臺

熱門推薦
一個賬號試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
3000+提示詞助力AI大模型
和專業工程師共享工作效率翻倍的秘密
返回頂部
上一篇
在.NET Core中集成SignalR實現實時通知的API應用
下一篇
用ASP.NET Core 2.1 建立規范的 REST API -- HATEOAS
国内精品久久久久影院日本,日本中文字幕视频,99久久精品99999久久,又粗又大又黄又硬又爽毛片
欧美日韩三级一区| 久久久久国产一区二区三区四区| 激情国产一区二区 | 国产高清精品久久久久| 99久久国产综合精品麻豆| 国产欧美日韩视频在线观看| 精品夜夜嗨av一区二区三区| 国产亚洲精品福利| 一本大道久久精品懂色aⅴ| 久久久久久久久一| 色8久久精品久久久久久蜜| 国产欧美日韩在线| 欧洲av在线精品| 秋霞午夜鲁丝一区二区老狼| 91精品国产91久久久久久最新毛片| 国产亚洲一二三区| 欧美日韩卡一卡二| 久久97超碰国产精品超碰| 国产精品视频在线看| 国产一区二区精品久久91| 91久久香蕉国产日韩欧美9色| 精品福利在线导航| 在线免费av一区| 99国产精品国产精品久久| 国产成人免费9x9x人网站视频| 一区二区三区四区五区视频在线观看| 久久婷婷国产综合精品青草| 久久午夜电影网| 国产免费久久精品| 夜夜夜精品看看| 亚洲va欧美va天堂v国产综合| 亚洲免费观看视频| 老司机精品视频导航| 国产资源精品在线观看| 色香蕉成人二区免费| 欧美性猛片aaaaaaa做受| 久久国产精品99久久人人澡| 国产不卡免费视频| 欧美高清一级片在线| 国产日韩精品一区| 精品中文字幕一区二区| 日本乱人伦一区| 国产高清久久久久| 国产成人综合网站| 国产一区二区不卡| 国内精品伊人久久久久影院对白| 成人av在线资源网站| 蜜臀精品久久久久久蜜臀| 成人一区二区三区视频在线观看| 久久―日本道色综合久久| 亚洲美女屁股眼交3| 97精品视频在线观看自产线路二| 亚洲精品一区二区三区福利| 欧美乱熟臀69xxxxxx| 在线播放91灌醉迷j高跟美女 | 国产黄人亚洲片| 欧美一区二区福利视频| 久久色.com| 成人av免费在线| 亚洲一区自拍偷拍| 欧美三级午夜理伦三级中视频| 91福利资源站| 亚洲成人一区二区| 久久久99精品免费观看不卡| 99久精品国产| 免费高清在线视频一区·| 成人免费高清在线| 欧美日本一道本在线视频| 久久国产精品无码网站| 亚洲精品videosex极品| 精品国产91九色蝌蚪| 97国产一区二区| 久久国产精品区| 91.com在线观看| 欧美精品一级二级| 中文字幕在线一区二区三区| 精品国产电影一区二区| 久久精品国产精品青草| 亚洲六月丁香色婷婷综合久久| 精品一区二区三区免费毛片爱| 激情图片小说一区| 久久综合九色综合久久久精品综合| 中文字幕视频一区| 一本到高清视频免费精品| 1区2区3区国产精品| 日韩一二在线观看| 欧美一级日韩免费不卡| 97国产一区二区| 国产美女视频一区| 精品亚洲国产成人av制服丝袜| 亚洲精品国产一区二区三区四区在线| av激情综合网| 成人福利电影精品一区二区在线观看| 老司机免费视频一区二区三区| 欧美肥妇毛茸茸| 欧美视频精品在线观看| 欧美日韩一级片在线观看| 在线观看三级视频欧美| 色先锋资源久久综合| 色综合咪咪久久| 在线播放一区二区三区| 91精品麻豆日日躁夜夜躁| 欧美成人综合网站| 一区二区三区高清| 国产美女娇喘av呻吟久久| 国产精品一区二区黑丝| 97久久久精品综合88久久| 成人激情小说乱人伦| 97久久精品人人做人人爽| 日韩午夜精品视频| 亚洲欧美日韩国产一区二区三区| 欧美三级日本三级少妇99| av在线综合网| 日韩一区国产二区欧美三区| 亚洲色图视频网站| 国产69精品久久久久777| 91精品国产乱| 亚洲一区二区三区四区五区黄| 激情深爱一区二区| 日韩视频一区二区三区| 亚洲一线二线三线视频| 91免费版在线| 亚洲尤物视频在线| 欧美日韩大陆在线| 麻豆精品一区二区| 欧美电影免费提供在线观看| 午夜不卡在线视频| 日韩欧美中文一区二区| 国产自产视频一区二区三区| 欧美成人伊人久久综合网| 国产盗摄一区二区三区| 成人欧美一区二区三区白人 | 91香蕉视频黄| 日韩二区三区四区| 欧美一级爆毛片| 欧美色成人综合| 国产成人夜色高潮福利影视| 亚洲欧美一区二区三区久本道91 | 国产资源在线一区| 亚洲欧美色一区| 午夜视频在线观看一区| 久久久久久综合| 日韩欧美国产一二三区| 成人性生交大片免费看中文| 2022国产精品视频| 日韩欧美激情在线| 日韩欧美在线123| 欧美美女一区二区在线观看| gogogo免费视频观看亚洲一| 美女www一区二区| 国产一区二区在线视频| 免费欧美在线视频| 亚洲图片自拍偷拍| 亚洲国产精品久久不卡毛片| 亚洲主播在线播放| 奇米四色…亚洲| 久久精品国产网站| 成人免费黄色在线| 7777精品伊人久久久大香线蕉| 91久久线看在观草草青青| 欧美大片国产精品| 一个色在线综合| 五月婷婷综合在线| 成人性生交大片免费看在线播放| 亚洲欧洲日产国产综合网| 一区二区三区四区中文字幕| 亚洲女同女同女同女同女同69| 理论片日本一区| 欧美videos大乳护士334| 亚洲综合在线第一页| 精品一区二区成人精品| 99国产精品久久久久| 欧美日韩国产精选| 亚洲免费资源在线播放| 国产在线精品国自产拍免费| 91亚洲国产成人精品一区二区三| 国产午夜精品在线观看| 国产福利视频一区二区三区| 91 com成人网| 国产精品久99| 国产尤物一区二区| 国产欧美视频一区二区三区| 国产99久久精品| 欧美日韩亚洲国产综合| 亚洲二区在线观看| 欧美人体做爰大胆视频| 日韩电影在线一区| 久久久久久久一区| 国内外成人在线| 欧美日韩免费观看一区三区| 免费在线观看日韩欧美| 国产视频亚洲色图| 3d动漫精品啪啪1区2区免费 | 欧美人伦禁忌dvd放荡欲情| 综合久久综合久久| www.亚洲精品| 国产呦萝稀缺另类资源| 日本亚洲电影天堂| 亚洲色图视频网| 亚洲成人午夜电影|