緩存可以提高應用程序的響應速度,本篇介紹如何在webapi中進行緩存。
如果將緩存的方式或介質做一個分類,可以分為如下3類:
一、本地內存(可存儲任何對象)
二、分布式存儲(需序列化成字節數組)
2.1 基於NOSQL(如Redis數據庫)
2.2 基於SQL(如SQL Server數據庫)
三、響應緩存(瀏覽器緩存數據)
一、 本地內存緩存(可存儲任何對象)
1. 還是以上篇中的項目 webapidemo3 來演示,使用內存進行緩存步驟如下:
第1步:安裝Microsoft.Extensions.Caching.Memory包。
第2步:在Startup類的ConfigureServices()方法中配置依賴注入服務(見紅色部分代碼)。
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<EfDemoContext>(options => options.UseMySQL(Configuration.GetConnectionString("MySQL"))); services.AddMemoryCache(); services.AddControllers(); }
第3步:新建控制器 CachesController,引用 using Microsoft.Extensions.Caching.Memory;
並在構造函數中注入 IMemoryCache 實例。
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; using System;
namespace webapidemo3 { [Route("api/[controller]")] [ApiController] public class CachesController : ControllerBase { private IMemoryCache _cache; public CachesController(IMemoryCache cache) { _cache = cache; }
//...... } }
第4步,編碼,新建終結點 Demo1( )用於緩存一個GUID、Demo2( )用於獲取這個GUID,代碼如下:
[HttpGet] [Route("demo1")] public string Demo1() { _cache.Set("guid",Guid.NewGuid().ToString()); return _cache.Get<string>("guid"); } [HttpGet] [Route("demo2")] public string Demo2() { return _cache.Get<string>("guid"); }
第5步,測試,編譯整個項目后運行,在瀏覽器訪問網址 http://localhost:51630/api/caches/demo1 得到如下結果:
在瀏覽器中開啟一個新tab,訪問網址 http://localhost:51630/api/caches/demo2,結果如下:
訪問終結點 Demo1() 和 Demo2() 得到了相同的結果 。
2. 對內存緩存做更多細粒度的設置和操作。
如何設置緩存過期?緩存的過期策略可以是絕對時間(Absolute Time)或滑動時間(Sliding Time),
通過 MemoryCacheEntryOptions 來設置,然后作為 Set( )方法的參數傳遞就可以了, 如下:
[HttpGet] [Route("demo1")] public string Demo1() { MemoryCacheEntryOptions options = new MemoryCacheEntryOptions() { //10分鍾內沒有訪問就過期 //SlidingExpiration = TimeSpan.FromMinutes(10), //30分鍾后不管什么情況都過期 AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(30) }; _cache.Set("guid", Guid.NewGuid().ToString(), options); return _cache.Get<string>("guid"); }
二、基於Redis數據庫的分布式緩存。
Redis數據庫是一種非關系型數據庫(即 Not Only SQL,簡稱NoSQL),
它是以鍵值對(Key-Value)形式來存儲數據的,非常適合做分布式緩存,
.net core提供了對Redis的原生支持,使用起來和內存緩存非常相似,其配置步驟如下:
第1步:下載並安裝 Redis。
github下載地址 :https://github.com/MicrosoftArchive/redis/releases
下載 Redis-x64-3.2.100.zip 解壓縮后的文件清單如下,將文件夾copy到C:\下。
第2步:啟動Redis服務。
打開cmd.exe工具,將目錄切換到Redis文件夾,如下:
輸入命令redis-server.exe redis.windows.conf 可以看到 Redis 服務啟動成功的畫面:
第3步:客戶端連接。
不要關閉第2步中的cmd窗口(作Redis服務器使用),再開一個 cmd.exe窗口,
將目錄切換到redis文件夾目錄,然后使用redis-cli.exe命令連接redis服務器,如下:
redis-cli.exe -h 127.0.0.1 -p 6379,-h 接服務器IP , -p 接端口號。
可以看到已經成功連接了
第4步:使用Redis數據服務。
用set命令設置一個key-value,然后用 get命令取出:
Redis運行良好,至此Redis數據庫服務配置完成。
第5步:安裝 Microsoft.Extensions.Caching.Redis 包。
第6步:在Startup類的ConfigureServices()方法中配置依賴注入服務(見紅色部分代碼)。
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<EfDemoContext>(options => options.UseMySQL(Configuration.GetConnectionString("MySQL"))); services.AddDistributedRedisCache( options => { options.Configuration = "localhost"; //Redis數據庫連接信息
//Redis數據庫實例名稱,如果設置了多個實例需要在這里指定 //options.InstanceName = "RedisCache";
}); services.AddControllers(); }
第7步:在控制器中注入實例。新建RedissController 控制器類,如下:
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Distributed; using System; namespace webapidemo3 { [Route("api/[controller]")] [ApiController] public class RedissController : ControllerBase { private IDistributedCache _cache; public RedissController(IDistributedCache cache) { _cache = cache; } } }
第8步:使用Redis分布式緩存。新建終結點Demo1( ),如下:
[HttpGet] [Route("demo1")] public string Demo1() { _cache.Set("mykey", Encoding.UTF8.GetBytes("dotnet core webapi demo."));
return Encoding.UTF8.GetString(_cache.Get("mykey")); }
訪問網址 :http://localhost:51630/api/Rediss/demo1,結果如下:
三、響應緩存
響應緩存是指瀏覽器對Web服務器提供的HTTP響應報文進行緩存,.net core是完全按照標准的HTTP規范來操作緩存的,
(通過設置和緩存有關的HTTP頭),在使用前有必要先了解HTTP規范(HTTP/1.1)及HTTP規范中對緩存的描述。
1. HTTP定義
HTTP協議是Hyper Text Transfer Protocol(超文本傳輸協議)的縮寫,
是用於從萬維網(WWW:World Wide Web )服務器傳輸超文本到本地瀏覽器的傳送協議,
它基於TCP/IP通信協議來傳遞數據(包括但不限於HTML 文件、 圖片、音頻、視頻、文檔、查詢結果等)。
2. HTTP請求/響應消息格式
如果我們訪問 www.baidu.com,用瀏覽器上的探測工具就可以看到請求和響應的報文信息,如下:
3. 響應緩存的約束條件
- 在GET和HEAD請求時有效
- 只發生在第一次請求之后
4. 緩存的工作過程:
瀏覽器第一次請求數據時,服務器會將緩存標識(Cache-Control、ETag、Last-Modify等)與數據一起返回,瀏覽器將二者備份起來。
再次請求數據時,客戶端先判斷備份中的緩存標識,決定是使用 "強制緩存" 還是 "協商緩存" 。
5. "強制緩存" 和 "協商緩存" 區別:
5.1 強制緩存:在緩存數據未失效的情況下會直接使用瀏覽器的緩存數據,不會再向服務器發送任何請求。
5.2 協商緩存:不走強制緩存時瀏覽器就會與服務器進行協商,由服務器端對比判斷資源是否進行了修改更新。
如果服務器端的資源沒有修改,那么就返回304狀態碼,告訴瀏覽器可以使用緩存中的數據,
如果有更新就會返回200狀態碼,服務器就會返回更新后的數據並且將緩存規則一起返回。
6. 強制緩存的判斷標識:
由響應header中的 Pragma、 Expires 、 Cache-Control 這三個字段來標識,取值如下:
7. 判斷緩存是否過期的規則。
用戶發起了一個http請求后,如果所請求的資源有緩存,就開始檢查緩存是否過期,步驟如下:
查看緩存是否有Cache-Control的max-age(或s-maxage)指令,
若有,則使用響應報文生成時間Date + s-maxage/max-age獲得過期時間,再與當前時間進行對比;
若沒有,則比較Expires中的過期時間與當前時間(Expires是一個絕對時間)。
8. 協商緩存的使用條件。
當瀏覽器發現緩存過期后,緩存並不一定不能使用了,因為服務器端的資源可能仍然沒有改變,
所以需要與服務器協商,讓服務器判斷本地緩存是否還能使用。
相關的header頭屬性有(ETag/If-Not-Match 、Last-Modified/If-Modified-Since),請求頭和響應頭需成對出現 。
它們的取值如下:
9. 協商緩存判斷本地緩存是否可用的規則:
瀏覽器先判斷緩存中是否有 ETag 或 Last-Modified 字段,如果沒有,則發起一個http請求,服務器根據請求返回資源;
如果有這兩個字段,則在請求頭中添加If-None-Match字段(有ETag字段的話添加)、If-Modified-Since字段(有Last-Modified字段的話添加)。
如果同時發送If-None-Match 、If-Modified-Since字段,服務器只要比較If-None-Match和ETag的內容是否一致即可(因為優先級更高);
如果內容一致,服務器認為緩存仍然可用,則返回狀態碼304,瀏覽器直接讀取本地緩存,這就完成了協商緩存的過程。
如果內容不一致,則視情況返回其他狀態碼,並返回所請求資源。
四、響應緩存的使用。
.net core使用 ResponseCachingMiddleware中間件處理HTTP響應緩存,步驟如下:
第1步:安裝 NuGet 包 Microsoft.AspNetCore.ResponseCaching。
第2步:在 Startup 的 ConfigureServices(IServiceCollection services) 方法中注冊服務 。
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<EfDemoContext>(options => options.UseMySQL(Configuration.GetConnectionString("MySQL"))); services.AddResponseCaching(); services.AddControllers(); }
第3步:在 Startup 的 Configure( ) 方法中啟用響應緩存服務
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseResponseCaching(); app.UseRouting(); app.UseAuthorization(); app.UseImageRequestMiddleware(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
第4步:新建控制器 ResponseCachesController,在終結點中使用響應緩存特性就可以了。
using Microsoft.AspNetCore.Mvc; using System; namespace webapidemo3 { [Route("api/[controller]")] [ApiController] public class ResponseCachesController : ControllerBase { [HttpGet] [Route("demo1")] [ResponseCache(Duration = 127)] public string Demo1() { //Duration = 127 對應 Cache-control: public, max-age=127 return Guid.NewGuid().ToString(); } } }
第5步,編譯項目后用 POSTMAN 訪問網址 http://localhost:51630/api/ResponseCaches/demo1 ,結果如下:
在響應Header中增加了 Cache-Control 的 key ,其值是 public, max-age=127。
其他Demo:緩存設置2:
[HttpGet] [Route("demo2")] [ResponseCache(Location = ResponseCacheLocation.Client, Duration = 35)] public string Demo2() { //ResponseCache對應 Cache-Control: private, max-age=35 return Guid.NewGuid().ToString(); }
緩存設置3:
1 [HttpGet] 2 [Route("demo3")] 3 [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] 4 public string Demo3() 5 { 6 //ResponseCache對應 Cache-Control: no-store,no-cache 7 return Guid.NewGuid().ToString(); 8 }
緩存設置4:
[HttpGet] [Route("demo4")] [ResponseCache(Duration = 735, VaryByQueryKeys = new string[] { "pageindex","pagesize" })] public string Demo4() { //根據查詢參數 pageindex,pagesize 緩存 return Guid.NewGuid().ToString(); }
最后,附上 ResponseCacheAttribute 的定義,如下:
namespace Microsoft.AspNetCore.Mvc { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class ResponseCacheAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter { public ResponseCacheAttribute(); public int Duration { get; set; } public ResponseCacheLocation Location { get; set; } public bool NoStore { get; set; } public string VaryByHeader { get; set; } public string[] VaryByQueryKeys { get; set; } public string CacheProfileName { get; set; } public int Order { get; set; } public bool IsReusable { get; } public IFilterMetadata CreateInstance(IServiceProvider serviceProvider); public CacheProfile GetCacheProfile(MvcOptions options); } }
設置響應緩存特性的時候使用其中的屬性名稱就可以了。