這篇文章主要介紹.NetCore之接口緩存怎么弄,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
成都創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比南票網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式南票網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋南票地區(qū)。費(fèi)用合理售后完善,10年實(shí)體公司更值得信賴。1、問題:我們平時(shí)做開發(fā)的時(shí)候肯定都有用到緩存這個(gè)功能,一般寫法是在需要的業(yè)務(wù)代碼里讀取緩存、判斷是否存在、不存在則讀取數(shù)據(jù)庫再設(shè)置緩存這樣一個(gè)步驟。但是如果我們有很多地方業(yè)務(wù)都有用到緩存,我們就需要在每個(gè)地方都寫關(guān)于緩存的代碼,這樣會造成很多重復(fù)代碼,同時(shí)對業(yè)務(wù)侵入不利于后續(xù)的開發(fā)維護(hù)。
2、一般的解決辦法是將緩存的功能提取出來,然后在需要用到緩存的地方調(diào)用即可。這樣確實(shí)減少了很多重復(fù)代碼,但這樣還是會存在整個(gè)項(xiàng)目通用的緩存功能侵入業(yè)務(wù)代碼,那我們有什么辦法將緩存功能完全提取出來,達(dá)到業(yè)務(wù)代碼零侵入呢?
3、既然我們緩存存的是接口的業(yè)務(wù)數(shù)據(jù),那么為何我們不能直接把整個(gè)接口緩存起來呢,即將整個(gè)接口返回的數(shù)據(jù)緩存?同時(shí)要達(dá)到業(yè)務(wù)零侵入,那我們是不是想到了反射、特性呢?沒錯(cuò),我們使用的就是ActionFilterAttribute,關(guān)于ActionFilterAttribute無非就是OnActionExecuting(執(zhí)行動作方法前觸發(fā))、OnActionExecuted(執(zhí)行動作方法后觸發(fā))、OnResultExecuting(在執(zhí)行操作結(jié)果之前觸發(fā))、OnResultExecuted(在執(zhí)行操作結(jié)果之后觸發(fā))這四個(gè)方法,相信很多小伙伴都用到過,這里就不細(xì)說了。那我們現(xiàn)在的解決方案是:在OnActionExecuting(執(zhí)行動作方法前觸發(fā))里判斷是否存在緩存,如果存在則不去執(zhí)行接口業(yè)務(wù),直接返回?cái)?shù)據(jù)。還有一個(gè)問題,一般接口都會有入?yún)?,入?yún)⒉煌敵龅臄?shù)據(jù)也不同(比如我有一個(gè)分頁的接口,傳的page參數(shù)不同,得到的結(jié)果也不同),這個(gè)怎么解決呢?我們只需要把接口所有參數(shù)拼湊起來,然后MD5加密成一個(gè)字符串,將其作為緩存的key,那么即使同一個(gè)接口、參數(shù)不同也會得到不同的key。
4、廢話不多說,直接上代碼。
public class ApiCache : ActionFilterAttribute { ////// Header是否參與緩存驗(yàn)證 /// public bool SignHeader = false; ////// 緩存有效時(shí)間(分鐘) /// public int CacheMinutes = 5;////// /// /// Header是否參與請求體簽名 /// 緩存有效時(shí)間(分鐘) public ApiCache(bool SignHeader = false, int CacheMinutes = 5) { this.SignHeader = SignHeader; this.CacheMinutes = CacheMinutes; } public override void OnActionExecuting(ActionExecutingContext filterContext) { //請求體簽名 string cacheKey = getKey(filterContext.HttpContext.Request); //根據(jù)簽名查詢緩存 string data = CsRedisHepler.Get(cacheKey); if (!string.IsNullOrWhiteSpace(data)) { //有緩存則設(shè)置返回信息 var content = new Microsoft.AspNetCore.Mvc.ContentResult(); content.Content = data; content.ContentType = "application/json; charset=utf-8"; content.StatusCode = 200; filterContext.HttpContext.Response.Headers.Add("ContentType", "application/json; charset=utf-8"); filterContext.HttpContext.Response.Headers.Add("CacheData", "Redis"); filterContext.Result = content; } } public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); } public override void OnResultExecuting(ResultExecutingContext filterContext) { base.OnResultExecuting(filterContext); } public override void OnResultExecuted(ResultExecutedContext filterContext) { if (filterContext.HttpContext.Response.Headers.ContainsKey("CacheData")) return; //獲取緩存key string cacheKey = getKey(filterContext.HttpContext.Request); var data = JsonSerializer.Serialize((filterContext.Result as Microsoft.AspNetCore.Mvc.ObjectResult).Value); //如果緩存null,則設(shè)置較短過期時(shí)間(此處是防止緩存穿透) var disData = JsonSerializer.Deserialize>(data); if(disData.ContainsKey("data") && disData["data"]==null) { CacheMinutes = 1; } CsRedisHepler.Set(cacheKey, data, TimeSpan.FromMinutes(CacheMinutes)); } /// /// 請求體MDH簽名 /// /// ///private string getKey(HttpRequest request) { var keyContent = request.Host.Value + request.Path.Value + request.QueryString.Value + request.Method + request.ContentType + request.ContentLength; try { if (request.Method.ToUpper() != "DELETE" && request.Method.ToUpper() != "GET" && request.Form.Count > 0) { foreach (var item in request.Form) { keyContent += $"{item.Key}={item.Value.ToString()}"; } } } catch (Exception e) { } if (SignHeader) { var hs = request.Headers.Where(a => !(new string[] { "Postman-Token", "User-Agent" }).Contains(a.Key)).ToDictionary(a => a); foreach (var item in hs) { keyContent += $"{item.Key}={item.Value.ToString()}"; } } //md5加密 return CryptographyHelper.MD5Hash(keyContent); }
這里使用的是redis,也可以選擇其他的,代碼簡單沒有做適配,這樣我們只需要在用到緩存的接口上加上[ApiCache(CacheMinutes =1)]特性就行啦,關(guān)于參數(shù)的話也可以根據(jù)自己的業(yè)務(wù)需求來定制。
5、關(guān)于緩存的三座大山:緩存穿透、緩存擊穿、緩存雪崩,這塊網(wǎng)上有很多的資料可以看,這里只做一個(gè)簡單的介紹跟解決思路。
緩存穿透:訪問一個(gè)不存在的key時(shí),請求會穿過緩存直接請求數(shù)據(jù)庫。比如現(xiàn)在有個(gè)接口是分頁的,然后客戶端請求接口的時(shí)候?qū)ageindex參數(shù)給的很大,大到該接口不可能有這么多頁的數(shù)據(jù)時(shí),每次請求都會穿過緩存去查數(shù)據(jù)庫。如果有人故意攻擊接口就會給數(shù)據(jù)庫造成巨大壓力甚至掛掉。當(dāng)然,這里我們肯定也要做一些業(yè)務(wù)參數(shù)的校驗(yàn),比如每頁條數(shù)不能超過多少之類的,總之不能輕信客戶端傳過來的參數(shù)。
解決方案:最簡單有效的解決方案是當(dāng)在數(shù)據(jù)庫也查不到數(shù)據(jù)的時(shí)候,設(shè)置一個(gè)value為null的緩存值(該值的過期時(shí)間要盡量短),這樣就可以避免惡意攻擊。另外就是使用布隆過濾器。
我們這里使用的解決方案是第一種設(shè)置null值,在上述的代碼中有注釋。不過這里好接口有一個(gè)返回規(guī)范,比如每個(gè)接口返回固定值:message、code、data這幾個(gè)字段, 那么我們只需判斷data是否為空來設(shè)置過期時(shí)間。
緩存擊穿:某一個(gè)訪問量極高的key過期,導(dǎo)致所有請求打在數(shù)據(jù)庫上。
解決方案:將訪問量高德key設(shè)置永不過期、使用互斥鎖。我們這里使用設(shè)置key永不過期就行,具體實(shí)現(xiàn)就是加一個(gè)是否過期的字段從外部傳入,再根據(jù)該字段判斷是否設(shè)置過期時(shí)間。同時(shí)可以寫一個(gè)定時(shí)任務(wù)去更新設(shè)置為永不過期的key值。
緩存雪崩:某一時(shí)刻多個(gè)高訪問量的key同時(shí)過期。
解決方案:在設(shè)置過期時(shí)間的時(shí)候?qū)⒚總€(gè)key的過期時(shí)間設(shè)置分布開來,在上述代碼中CacheMinutes字段改成過期時(shí)間范圍從。。。到。。。,然后key的過期時(shí)間從范圍中取一個(gè)隨機(jī)值。
當(dāng)然這里講到的解決方案也只是個(gè)人常用的,也可以使用其他解決方案。
以上是“.NetCore之接口緩存怎么弄”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!