用ASP.NET Core 2.1 建立规范的 REST API -- 缓存和并发
本文所需的一些預(yù)備知識(shí)可以看這里:??用ASP.NET Core 2.0 建立規(guī)范的 REST API -- 預(yù)備知識(shí)?和??用ASP.NET Core 2.0 建立規(guī)范的 REST API -- 預(yù)備知識(shí) (2) + 準(zhǔn)備項(xiàng)目
建立Richardson成熟度2級(jí)的POST、GET、PUT、PATCH、DELETE的RESTful API請(qǐng)看這里:用ASP.NET Core 2.0 建立規(guī)范的 REST API -- GET 和 POST,?用ASP.NET Core 2.0 建立規(guī)范的 REST API -- DELETE, UPDATE, PATCH 和 Log和用ASP.NET Core 2.1 建立規(guī)范的 REST API -- 翻頁/排序/過濾等
HATEOAS:用ASP.NET Core 2.1 建立規(guī)范的 REST API -- HATEOAS
本文介紹緩存和并發(fā),無需看前邊文章也能明白吧。
本文所需的練習(xí)代碼(右鍵另存,后綴改為zip):https://images2018.cnblogs.com/blog/986268/201806/986268-20180611132306164-388387828.jpg
緩存
根據(jù)REST約束:“每個(gè)響應(yīng)都應(yīng)該定義它自己是否可以被緩存”。本文就要介紹如何保證HTTP響應(yīng)是可被緩存的,這里就要用到HTTP緩存的知識(shí),HTTP緩存是HTTP標(biāo)準(zhǔn)的一部分(RFC 2616, RFC 7234)。
"除非性能可以得到很大的提升,否則用緩存是沒啥用的。HTTP/1.1里緩存的目標(biāo)就是在很多場(chǎng)景中可以避免發(fā)送請(qǐng)求,在其他情況下避免返回完整的響應(yīng)"。
針對(duì)避免發(fā)送請(qǐng)求的數(shù)量這一點(diǎn),緩存使用了過期機(jī)制。
針對(duì)避免返回完整響應(yīng)這點(diǎn),緩存采用了驗(yàn)證機(jī)制。
?
緩存是什么?
緩存是一個(gè)獨(dú)立的組件,存在于API和API消費(fèi)者之間。
緩存接收API消費(fèi)者的請(qǐng)求,并把請(qǐng)求發(fā)送給API;
緩存還從API接收響應(yīng)并且如果響應(yīng)是可緩存的就會(huì)把響應(yīng)保存起來,并把響應(yīng)返回給API的消費(fèi)者。如果同一個(gè)請(qǐng)求再次發(fā)送,那么緩存就可能會(huì)吧保存的響應(yīng)返回給API消費(fèi)者。
緩存可以看作是請(qǐng)求--響應(yīng)通訊機(jī)制的中間人。?
HTTP里面有三種緩存:
客戶端緩存/瀏覽器緩存,它存在于客戶端,并且是私有的(因?yàn)樗粫?huì)與其它客戶端共享)。
網(wǎng)關(guān)緩存,它是共享的緩存,位于服務(wù)器端,所有的API消費(fèi)者客戶端都會(huì)共享這個(gè)緩存。它的別名還有反向代理服務(wù)器緩存,HTTP加速器等。
代理緩存,它位于網(wǎng)絡(luò)上,共享的,它既不位于API消費(fèi)者客戶端,也不在API服務(wù)器上,它在網(wǎng)絡(luò)的其它地方。這種緩存經(jīng)常被大型企業(yè)或ISP使用,用來服務(wù)大規(guī)模的用戶。(這個(gè)不介紹了,我不會(huì))
?
過期模型
過期模型讓服務(wù)器可以聲明請(qǐng)求的資源也就是響應(yīng)信息能保持多長時(shí)間是“新鮮”的狀態(tài)。緩存可以存儲(chǔ)這個(gè)響應(yīng),所以后續(xù)的請(qǐng)求可以由緩存來響應(yīng),只要緩存是“新鮮”的。處于這個(gè)目的,需要使用兩個(gè)Response Headers:
Expires?Header,它包含一個(gè)HTTP日期,該日期表述了響應(yīng)會(huì)在什么時(shí)間過期,例如:Expires: Mon, 11 Jun 2018 13:55:41 GMT。但是它可能會(huì)存在一些同步問題,所以要求緩存和服務(wù)器的時(shí)間是保持一致的。它對(duì)響應(yīng)的類型、時(shí)間、地點(diǎn)的控制很有限,因?yàn)檫@些東西都是由cache-control這個(gè)Header來控制和限制的。
Cache-Control?Header,例如Cache-Control: public, max-age=60,這個(gè)Header里包含兩個(gè)指令public和max-age。max-age表明了響應(yīng)可以被緩存60秒,所以時(shí)鐘同步就不是問題了;而public則表示它可以被共享和私有的緩存所緩存。所以說服務(wù)器可以決定響應(yīng)是否允許被網(wǎng)關(guān)緩存或代理緩存所緩存。對(duì)于過期模型,優(yōu)先考慮使用Cache-Control這個(gè)Header。Cache-Control還有很多其它的指令,常見的幾個(gè)可以在ASP.NET Core官網(wǎng)上看:https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-2.1#http-based-response-caching
?
過期模型的工作原理,看下面的例子:
這里的Cache 緩存可以是私有的也可以是共享的。
客戶端程序發(fā)送請(qǐng)求 GET countries,這時(shí)還沒有緩存版本的響應(yīng),所以緩存會(huì)繼續(xù)把請(qǐng)求發(fā)送到API服務(wù)器;然后API返回響應(yīng)給緩存,響應(yīng)里面包含了Cache-Control這個(gè)Header,Cache-Control聲明了響應(yīng)會(huì)保持“新鮮”(或者叫有效)半個(gè)小時(shí),最后緩存把響應(yīng)返回給客戶端,但同時(shí)緩存復(fù)制了一份響應(yīng)保存了起來。
然后比如10分鐘之后,客戶端又發(fā)送了一樣的請(qǐng)求:
這時(shí),緩存里的響應(yīng)還在有效期內(nèi),緩存會(huì)直接返回這個(gè)響應(yīng),響應(yīng)里包含一個(gè)age?Header,針對(duì)這個(gè)例子(10分鐘),age的值就是600(秒)。
這種情況下,對(duì)API服務(wù)器的請(qǐng)求就被避免了,只有在緩存過期(或者叫不新鮮 Stale)的情況下,緩存才會(huì)訪問后端的API服務(wù)器。
?
如果緩存是私有的,例如在web應(yīng)用的localstorage里面,或者手機(jī)設(shè)備上,請(qǐng)求到此就停止了。
如果緩存是共享的,例如緩存在服務(wù)器上,情況就不一樣了。
比如說10分鐘之后另一個(gè)客戶端發(fā)送了同樣的請(qǐng)求,這個(gè)請(qǐng)求肯定首先來到緩存這里,如果緩存還沒有過期,那么緩存會(huì)直接把響應(yīng)返回給客戶端,這次age Header的值就是1200(秒),20分鐘了:
總的來說私有緩存會(huì)減少網(wǎng)絡(luò)帶寬的需求,同時(shí)會(huì)減少從緩存到API的請(qǐng)求。
而共享緩存并不會(huì)節(jié)省緩存到API的網(wǎng)絡(luò)帶寬,但是它會(huì)大幅減少到API的請(qǐng)求。例如同時(shí)10000個(gè)客戶端發(fā)出了同樣請(qǐng)求到API,第一個(gè)到達(dá)的請(qǐng)求會(huì)來到API程序這里,而其它的同樣請(qǐng)求只會(huì)來到緩存,這也意味著代碼的執(zhí)行量會(huì)大大減少,訪問數(shù)據(jù)庫的次數(shù)也會(huì)大大減少,等等。
所以組合使用私有緩存和共享緩存(客戶端緩存和公共/網(wǎng)關(guān)緩存)還是不錯(cuò)的。但是這種緩存還是更適用于比較靜態(tài)的資源,例如圖片、內(nèi)容網(wǎng)頁;而對(duì)于數(shù)據(jù)經(jīng)常變化的API并不太合適。如果API添加了一條數(shù)據(jù),那么針對(duì)這10000個(gè)客戶端,所緩存的數(shù)據(jù)就不對(duì)了,針對(duì)這個(gè)例子有可能半個(gè)小時(shí)都會(huì)返回不正確的數(shù)據(jù),這時(shí)就需要用到驗(yàn)證模型了。
?
驗(yàn)證模型
驗(yàn)證模型用于驗(yàn)證緩存的響應(yīng)數(shù)據(jù)是否是保持最新的。
這種情況下,當(dāng)被緩存的數(shù)據(jù)將要成為客戶端請(qǐng)求的響應(yīng)的時(shí)候,它首先會(huì)檢查一下源服務(wù)器或者擁有最新數(shù)據(jù)的中間緩存,看看它所緩存的數(shù)據(jù)是否仍然最新。這里就要用到驗(yàn)證器。
驗(yàn)證器
驗(yàn)證器分為兩種:強(qiáng)驗(yàn)證器,弱驗(yàn)證器。
強(qiáng)驗(yàn)證器:如果響應(yīng)的body或者h(yuǎn)eader發(fā)生了變化,強(qiáng)驗(yàn)證器就會(huì)變化。典型的例子就是ETag(Entity Tag)響應(yīng)header,例如:ETag: "12345678",ETag是由Web服務(wù)器或者API發(fā)配的不透明標(biāo)識(shí),它代表著某個(gè)資源的特定版本。強(qiáng)驗(yàn)證器可以在任意帶有緩存的上下文中使用,在更新資源的時(shí)候強(qiáng)驗(yàn)證器可以用來做并發(fā)檢查。
弱驗(yàn)證器:當(dāng)響應(yīng)變化的時(shí)候,弱驗(yàn)證器通常不一定會(huì)變化,由服務(wù)器來決定什么時(shí)候變化,通常的做法有“只有在重要變化發(fā)生的時(shí)候才變化”。一個(gè)典型的例子就是Last-Modified(最后修改時(shí)間)這個(gè)Header ,例如:Mon, 11 Jun 2018 13:55:41 GMT,它里面包含著資源最后修改的時(shí)間,這個(gè)就有點(diǎn)弱,因?yàn)樗_到秒,因?yàn)橛锌赡芤幻雰?nèi)對(duì)資源進(jìn)行兩次以上的更新。但即使針對(duì)弱驗(yàn)證器,時(shí)鐘也必須同步,所以它和expires header有同樣的問題,所以ETag是更好的選擇。
還有一種弱ETag,它以w/開頭,例如ETag: "w/123456789",它被當(dāng)作弱驗(yàn)證器來對(duì)待,但是還是由服務(wù)器來決定其程度。當(dāng)ETag是這種格式的時(shí)候,如果響應(yīng)有變化,它不一定就變化。
弱驗(yàn)證器只有在允許等價(jià)(大致相等)的情況下可已使用,而在要求完全相等的需求下是不可以使用的。
HTTP標(biāo)準(zhǔn)建議如果可能的話最好還是同時(shí)發(fā)送ETag和Last-Modified這兩個(gè)Header。
?
下面看看其工作原理。客戶端第一次請(qǐng)求的時(shí)候,請(qǐng)求到達(dá)緩存后發(fā)現(xiàn)緩存里沒有,然后緩存把請(qǐng)求發(fā)送到API;API返回響應(yīng),這個(gè)響應(yīng)包含ETag和Last-Modified 這兩個(gè)Header,響應(yīng)被發(fā)送到緩存,然后緩存再把它發(fā)送給客戶端,與此同時(shí)緩存保存了這個(gè)響應(yīng)的一個(gè)副本。
10分鐘后,客戶端再次發(fā)送了同樣的請(qǐng)求,請(qǐng)求來到緩存,但是無法保證緩存的響應(yīng)是“新鮮”的,這個(gè)例子里并沒有使用Cache-Control Header,所以緩存就必須到服務(wù)器的API去做檢查。這時(shí)它會(huì)添加兩個(gè)Headers:If-None-Match,它被設(shè)為已緩存響應(yīng)數(shù)據(jù)的ETag的值;If-Modified-Since,它被設(shè)為已緩存響應(yīng)數(shù)據(jù)的Last-Modified的值。現(xiàn)在這個(gè)請(qǐng)求就是根據(jù)情況而定的了,服務(wù)器接收到這個(gè)請(qǐng)求并會(huì)根據(jù)證器來比較這些header或者生成響應(yīng)。
如果檢查合格,服務(wù)器就不需要生成響應(yīng)了,它會(huì)返回304 Not Modified,然后緩存會(huì)返回緩存的響應(yīng),這個(gè)響應(yīng)還包含了一個(gè)最新的Last-Modified Header(如果支持Last-Modifed的話);
而如果響應(yīng)的資源發(fā)生變化了,API就會(huì)生成新的響應(yīng)。
如果是私有緩存,那就請(qǐng)求就會(huì)停在這。
但如果是共享緩存的話,假如10分鐘之后另一個(gè)客戶端發(fā)送了請(qǐng)求,這個(gè)請(qǐng)求也會(huì)到達(dá)緩存,然后跟上面一樣的流程:
?
總的來說就是,同樣的響應(yīng)只會(huì)被生成一次。
對(duì)比一下:
私有緩存:后續(xù)的請(qǐng)求會(huì)節(jié)省網(wǎng)絡(luò)帶寬,我們需要與API進(jìn)行通信,但是API不需要把完整的響應(yīng)返回來,如果資源沒有變化的話只需要返回304即可。
共享緩存:會(huì)節(jié)省緩存和API之間的帶寬,如果驗(yàn)證通過的話,API不需要重新生成響應(yīng)然后重新發(fā)送回來。
?
過期模型和驗(yàn)證模型還是經(jīng)常被組合使用的。
組合使用過期模型和驗(yàn)證模型
可以這樣做:
如果使用私有緩存,這時(shí)只要響應(yīng)沒有過期,那么響應(yīng)直接會(huì)從私有緩存返回。這樣做的好處就是減少了與API之間的通信,也減少了API生成響應(yīng)的工作,減輕了帶寬需求。而如果私有緩存過期了,那還是會(huì)訪問到API的。如果只有過期(模型)檢查的話,這就意味著如果過期了API就得重新生成響應(yīng)。但是如果使用驗(yàn)證(模型)檢查的話,我們可能就會(huì)避免這種情況。因?yàn)榫彺娴捻憫?yīng)過期了并不代表緩存的響應(yīng)就不是有效的了,API會(huì)檢查驗(yàn)證器,如果響應(yīng)依然有效,就會(huì)返回304。這樣網(wǎng)絡(luò)帶寬和響應(yīng)的生成動(dòng)作都有可能被大幅度減少了。
如果是共享緩存,緩存的響應(yīng)只要沒過期就會(huì)一直被返回,這樣雖然不會(huì)節(jié)省客戶端和緩存之間的網(wǎng)絡(luò)帶寬,但是會(huì)節(jié)省緩存和API之間的網(wǎng)絡(luò)帶寬,同時(shí)也大幅度減少了到API的請(qǐng)求次數(shù),這個(gè)要比私有緩存幅度大,因?yàn)楣蚕砭彺媸枪蚕砼c可能是所有的客戶端的。如果緩存的響應(yīng)過期了,緩存就必須與API通信,但這也不一定就意味著響應(yīng)必須被重新生成。如果驗(yàn)證成功,就會(huì)返回304,沒有響應(yīng)body,這就有可能減少了緩存和API之間的網(wǎng)絡(luò)帶寬需求,響應(yīng)還是從緩存返回到客戶端的。
所以綜上,客戶端配備私有緩存,服務(wù)器級(jí)別配備共享緩存就應(yīng)該是最佳的實(shí)踐。
?
Cache-Control的指令
先看一下響應(yīng)的Cache-Control常用指令:
新鮮度:?
max-age定義了響應(yīng)的生命期, 超過了這個(gè)值, 緩存的響應(yīng)就過期了, 它的單位是秒.
s-maxage對(duì)于共享緩存來說它會(huì)覆蓋max-age的值. 所以在私有緩存和共享緩存里響應(yīng)的過期時(shí)間可能會(huì)不同.
存儲(chǔ)地點(diǎn):
public, 它表示響應(yīng)可以被任何一個(gè)緩存器所緩存, 私有或者共享的都可以.
private, 它表示整個(gè)或部分響應(yīng)的信息是為某一個(gè)用戶所準(zhǔn)備的, 并且不可以被共享的緩存器所緩存.
驗(yàn)證:
no-cache, 它表示在沒有和源服務(wù)器重新驗(yàn)證之前, 響應(yīng)不可以被后續(xù)的請(qǐng)求所使用.
must-revalidate, 使用它服務(wù)器可以聲明響應(yīng)是否已經(jīng)不新鮮了(過期了), 那么就需要進(jìn)行重新驗(yàn)證. 這就允許服務(wù)器強(qiáng)制讓緩存進(jìn)行重新驗(yàn)證, 即使客戶端認(rèn)為過期的響應(yīng)也是可以的.
proxy-revalidate, 他和must-revalidate差不多, 但不適用于私有緩存.
其它:
no-store, 它表示緩存不允許存儲(chǔ)消息的任何部分.
no-transform, 它表示緩存不可以對(duì)響應(yīng)body的媒體類型進(jìn)行轉(zhuǎn)換.
上面這些都是由服務(wù)器決定的, 但是客戶端可以覆蓋其中的一些設(shè)定.
請(qǐng)求的Cache-Control常用指令:
新鮮度:
max-age, 它表示客戶端不想要接收已經(jīng)超過這個(gè)值的有效期的響應(yīng)
min-fresh, 它表示客戶端可以接受到這樣的響應(yīng), 它的有效期不小于它當(dāng)前的年齡加上這個(gè)設(shè)定的值(秒), 也就是說客戶端想要響應(yīng)還可以在指定的時(shí)間內(nèi)保持新鮮.
max-stale, 它表示客戶端可以接收過期的響應(yīng).
驗(yàn)證:
no-cache, 它表示緩存不可以用存儲(chǔ)的響應(yīng)來滿足請(qǐng)求. 源服務(wù)器需要重新驗(yàn)證成功并生成響應(yīng).
其他:
no-store, 和響應(yīng)的一樣.
no-transform,?和響應(yīng)的一樣.
only-if-cached, 它表示客戶端只想要緩存的響應(yīng), 并且不要和源服務(wù)器進(jìn)行重新驗(yàn)證和生成. 這個(gè)比較適用于網(wǎng)絡(luò)狀態(tài)非常差的狀態(tài).
?到目前也介紹了幾個(gè)指令了, 其實(shí)大多數(shù)情況下使用max-age和public, private即可...
更多指令請(qǐng)查看:?https://tools.ietf.org/html/rfc7234#section-5.2
?
Cache Headers
根據(jù)REST的約束, 為了支持HTTP緩存, 我們需要一個(gè)可以生成正確的響應(yīng)Header的組件, 并且可以檢查發(fā)送的請(qǐng)求的Header, 所以我們可以返回304 Not Modified或者412 Preconditioned Failed.
這個(gè)組件應(yīng)該位于緩存的后端, ASP.NET Core里有個(gè)自帶的屬性標(biāo)簽?[ResponseCache]?(https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-2.1#responsecache-attribute), 它可以應(yīng)用于Controller的Actions. 為設(shè)定適當(dāng)響應(yīng)緩存Header它可以指定所需的參數(shù). 它只能做這些, 無法在緩存里存儲(chǔ)響應(yīng), 它并不是緩存存儲(chǔ). 而且因?yàn)樗孟癫恢С諩Tag, 所以暫時(shí)先不使用這個(gè).
可以考慮CacheCow,它可以生成ETag,也支持.NET Core,但是它并沒有內(nèi)置中間件來返回304。所以我這里使用的是Marvin.Cache.Headers。
安裝:?
Startup的ConfigureServices方法里配置:
?
這里還可以配置Header的生成選項(xiàng),但暫時(shí)先使用默認(rèn)的配置。?
然后在Configure方法里,把這個(gè)中間件添加在app.useMvc()之前:
這里就是處理并返回304的邏輯。
還需要設(shè)置一下Postman, 要保證Send no-cache header這一項(xiàng)是off的:
發(fā)送請(qǐng)求測(cè)試:
這是第一次訪問,會(huì)執(zhí)行Action方法,然后返回響應(yīng)。響應(yīng)的Header如上圖所示,里面包含了緩存相關(guān)的Header。
默認(rèn)的Cache-Control是public,max-age是60秒。Expires header也反映了過期的時(shí)間,也就是1分鐘之后。
用于驗(yàn)證的ETag和Last-Modified也被生成和添加了,Last-Modified就是現(xiàn)在的時(shí)間。
ETag的生成邏輯并不是標(biāo)準(zhǔn)的一部分,這個(gè)可以由我們自己來決定。當(dāng)讓響應(yīng)是等價(jià)的還是完全相等的也是由我們來決定。
默認(rèn)情況下,這個(gè)中間件會(huì)考慮到請(qǐng)求路徑、Accept、Accept-language 這些Header以及響應(yīng)的body。
再次發(fā)送該請(qǐng)求,由于已經(jīng)超過了1分鐘,所以還是會(huì)走Action方法的:
?
?
然后在1分鐘之內(nèi)再次發(fā)送請(qǐng)求:
還是走了這個(gè)Action方法!!
Header還是有變化的。
?
這個(gè)現(xiàn)象是沒有問題的,因?yàn)檫@個(gè)庫只是負(fù)責(zé)生成Header和驗(yàn)證,它并不是緩存存儲(chǔ)器。
想要緩存數(shù)據(jù),那就需要一個(gè)緩存存儲(chǔ)器了,可以是私有、公共的也可以是兩者兼顧的。這個(gè)一會(huì)再說。
先來看看驗(yàn)證,如果一個(gè)響應(yīng)是不新鮮的(過期的),我們知道這樣話緩存必須進(jìn)行重新驗(yàn)證,最好是用ETag進(jìn)行驗(yàn)證,他會(huì)把ETag的值賦給If-None-Match這個(gè)Header:
這時(shí)就會(huì)返回304 Not Modified,而Action方法也不會(huì)執(zhí)行。
?
下面測(cè)試一下PUT動(dòng)作:
?
更新數(shù)據(jù)之后,我再發(fā)送一次之前的GET請(qǐng)求:
這次Action方法又被執(zhí)行了,這說明驗(yàn)證失敗了,因?yàn)镋Tag已經(jīng)不一致了,當(dāng)我發(fā)送PUT請(qǐng)求的時(shí)候,生成了一個(gè)新的ETag。
?
我們也可以對(duì)如何生成Header進(jìn)行配置,打開Startup的ConfigureServices方法:
配置參數(shù)還是很多的,這里我分別為過期模型和驗(yàn)證模型修改了一個(gè)參數(shù)。
過期模型的max-age設(shè)為600秒。驗(yàn)證模型為Cache-Control添加了must-revalidate指令,也就是說如果緩存的響應(yīng)過期了,那么必須進(jìn)行重新驗(yàn)證。
?
再次發(fā)送那個(gè)GET請(qǐng)求:
重新執(zhí)行了Action方法,也可以看到響應(yīng)Header的變化。
?
緩存存儲(chǔ)
之前只是生成了緩存相關(guān)的Header,還沒有進(jìn)行真正的存儲(chǔ),現(xiàn)在就介紹存儲(chǔ)這部分。
緩存有私有的、共享的等。
私有的不在我們討論的范圍內(nèi),因?yàn)樗诳蛻舳恕?/p>
私有和共享緩存,有一些緩存是兩者的混合,根據(jù)你在哪使用它來決定給其類型。例如CacheCow。
微軟提供了一個(gè)共享緩存,支持.NET Core:ResponseCaching中間件(https://docs.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-2.1)。
這個(gè)中間件會(huì)檢查Marvin.Cache.Headers這個(gè)中間件生成的Header,并把響應(yīng)放到緩存并根據(jù)Header把它們服務(wù)給客戶端,但是ResponseCaching中間件它自己并不會(huì)生成這些Header。
?
在ConfigureServices里注冊(cè):
然后在Configure方法里,把這個(gè)緩存存儲(chǔ)添加到管道:
注意順序,要保證它在UseHttpCacheHeaders()之前。
測(cè)試,發(fā)送GET請(qǐng)求:
這次會(huì)執(zhí)行Action方法,返回響應(yīng)。
再次發(fā)送GET請(qǐng)求:
這次沒有走進(jìn)Action方法里,而是從緩存返回的,這里還多了一個(gè)Age header,它告訴了我響應(yīng)的”年齡“,他已經(jīng)活了123秒了。
再次請(qǐng)求:
年齡變成了243秒,還是小于600秒。很顯然這提高了應(yīng)用的性能。。。
?
到目前我們可以生成Cache-Control和Etag的Headers了,但是還沒有用到ETag的另一個(gè)功能:
?
并發(fā)控制
看下面這個(gè)情況,很常見:
兩個(gè)客戶端1和2,客戶1先獲取了id為1的Country資源,隨后客戶2也獲取了這個(gè)資源;然后客戶2對(duì)資源進(jìn)行了修改,先進(jìn)行了PUT動(dòng)作進(jìn)行更新,然后客戶1才修改好Country然后PUT到服務(wù)器。
這時(shí)客戶1就會(huì)把客戶2的更改完全覆蓋掉,這是個(gè)常見問題。
針對(duì)這樣的問題,我們需要使用一些處理并發(fā)沖突的策略:悲觀并發(fā)控制和樂觀并發(fā)控制。
悲觀并發(fā)控制意味著資源是為客戶1鎖定的,只要資源處于鎖定的狀態(tài),別人就不能修改它,只有客戶1可以修改它。但是悲觀并發(fā)控制是無法在REST下實(shí)現(xiàn)的,因?yàn)镽EST有個(gè)無狀態(tài)約束。
樂觀并發(fā)控制,這就意味著客戶1會(huì)得到一個(gè)Token,并允許他更新資源,只要Token是合理有效的,那么客戶1就一直可以更新該資源。在REST里這是可以實(shí)現(xiàn)的,而這個(gè)Token就是個(gè)驗(yàn)證器,而且要求是強(qiáng)驗(yàn)證器,所以我們可以用ETag。
回到例子:
客戶1發(fā)送GET請(qǐng)求,返回響應(yīng)并帶著ETag Header。然后客戶2發(fā)送同樣的請(qǐng)求,返回同樣的響應(yīng)和Etag。
客戶2先進(jìn)行更新,并把Etag的值賦給了If-Match Header,API檢查這個(gè)Header并和它為這個(gè)響應(yīng)所保存的ETag值進(jìn)行比較,這時(shí)針對(duì)這個(gè)響應(yīng)會(huì)生成新的ETag,響應(yīng)包含著這個(gè)新的ETag。
然后客戶1進(jìn)行PUT更新操作,它的If-Match Header的值是客戶1之前得到的ETag的值,在到達(dá)API之后,API就知道這個(gè)和資源最新的ETag的值不一樣,所以API會(huì)返回412 Precondition Failed。
所以客戶1的更新沒有成功,因?yàn)樗褂玫氖抢习姹镜馁Y源。這就是樂觀并發(fā)控制的工作原理。
?
下面看測(cè)試,
客戶1先GET:
客戶2GET:
注意他們兩個(gè)的ETag是一樣的。
然后客戶2先更新:
最后客戶1再更新(使用的是老的ETag):
返回412。
?
本文比較短,一些關(guān)于緩存技術(shù)的內(nèi)容并沒有寫,距離REST的主題有點(diǎn)遠(yuǎn)。
ASP.NET Core關(guān)于緩存部分的文檔在這里: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
相關(guān)文章:
用ASP.NET Core 2.0 建立規(guī)范的 REST API -- 預(yù)備知識(shí)
用ASP.NET Core 2.0 建立規(guī)范的 REST API -- 預(yù)備知識(shí) (2) + 準(zhǔn)備項(xiàng)目
用ASP.NET Core 2.0 建立規(guī)范的 REST API -- GET 和 POST
用ASP.NET Core 2.0 建立規(guī)范的 REST API -- DELETE, UPDATE, PATCH 和 Log
用ASP.NET Core 2.1 建立規(guī)范的 REST API -- 翻頁/排序/過濾等
用ASP.NET Core 2.1 建立規(guī)范的 REST API -- HATEOAS
原文地址: https://www.cnblogs.com/cgzl/p/9165388.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號(hào)文章匯總 http://www.csharpkit.com
總結(jié)
以上是生活随笔為你收集整理的用ASP.NET Core 2.1 建立规范的 REST API -- 缓存和并发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [译]RabbitMQ教程C#版 - 发
- 下一篇: 基于 Consul 实现 MagicOn