.NET Core針對緩存提供了很好的支持 ,我們不僅可以選擇將數據緩存在應用進程自身的記憶體中,還可以採用分散式的形式將緩存數據存儲在一個“中心資料庫”中。對於分散式緩存,.NET Core提供了針對Redis和SQL Server的原生支持。除了這個獨立的緩存系統之外,ASP.NET Core還借... ...
.NET Core針對緩存提供了很好的支持 ,我們不僅可以選擇將數據緩存在應用進程自身的記憶體中,還可以採用分散式的形式將緩存數據存儲在一個“中心資料庫”中。對於分散式緩存,.NET Core提供了針對Redis和SQL Server的原生支持。除了這個獨立的緩存系統之外,ASP.NET Core還藉助一個中間件實現了“響應緩存”,它會按照HTTP緩存規範對整個響應實施緩存。不過按照慣例,在對緩存進行系統介紹之前,我們還是先通過一些簡單的實例演示感知一下如果在一個ASP.NET Core應用中如何使用緩存。
目錄
一、將數據緩存在記憶體中
二、基於Redis的分散式緩存
三、基於SQL Server的分散式緩存
四、緩存整個HTTP響應
一、將數據緩存在記憶體中
與針對資料庫和遠程服務調用這種IO操作來說,應用針對記憶體的訪問性能將提供不止一個數量級的提升,所以將數據直接緩存在應用進程的內容中自然具有最佳的性能優勢。與基於記憶體的緩存相關的應用編程介面定義在NuGet包“Microsoft.Extensions.Caching.Memory”中,具體的緩存實現在一個名為MemoryCache的服務對象中,後者是我們對所有實現了IMemoryCache介面的所有類型以及對應對象的統稱。由於是將緩存對象直接置於記憶體之中,中間並不涉及持久化存儲的問題,自然也就無需考慮針對緩存對象的序列化問題,所以這種記憶體模式支持任意類型的緩存對象。
針對緩存的操作不外乎對緩存數據的存與取,這兩個基本的操作都由上面介紹的這個MemoryCache對象來完成。如果我們在一個ASP.NET Core應用對MemoryCache服務在啟動時做了註冊,我們就可以在任何地方獲取該服務對象設置和獲取緩存數據,所以針對緩存的編程是非常簡單的。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseKestrel()
7: .ConfigureServices(svcs => svcs.AddMemoryCache())
8: .Configure(app => app.Run(async context =>
9: {
10: IMemoryCache cache = context.RequestServices.GetRequiredService<IMemoryCache>();
11: DateTime currentTime;
12: if (!cache.TryGetValue<DateTime>("CurrentTime", out currentTime))
13: {
14: cache.Set("CurrentTime", currentTime = DateTime.Now);
15: }
16: await context.Response.WriteAsync($"{currentTime}({DateTime.Now})");
17: }))
18: .Build()
19: .Run();
20: }
21: }
在上面這個演示程式中,我們在WebHostBuilder的ConfigureServices方法中通過調用ServiceCollection的擴展方法AddMemoryCache完成了針對MemoryCache的服務註冊。在WebHostBuilder的Configure方法中,我們通過調用ApplicationBuilder的Run方法註冊了一個中間件對請求做了簡單的響應。我們先從當前HttpContext中得到對應的ServiceProvider,並利用後者得到MemoryCache對象。我們接下來調用MemoryCache的Set方法將當前時間緩存起來(如果尚未緩存),並指定一個唯一的Key(“CurrentTime”)。通過指定響應的Key,我們可以調用另一個名為TryGetValue<T>的方法獲取緩存的對象。我們最終寫入的響應內容實際上是緩存的時候和當前實施的時間。由於緩存的是當前時間,所以當我們通過瀏覽器訪問該應用的時候,顯示的時間在緩存過期之前總是不變的
雖然基於記憶體的緩存具有最高的性能,但是由於它實際上是將緩存數據存在承載ASP.NET Core應用的Web服務上,對於部署在集群式伺服器中的應用會出現緩存數據不一致的情況。對於這種部署場景,我們需要將數據緩存在某一個獨立的存儲中心,以便讓所有的Web伺服器共用同一份緩存數據,我們將這種緩存形式稱為“分散式緩存”。ASP.NET Core為分散式緩存提供了兩種原生的存儲形式,一種是基於NoSQL的Redis資料庫,另一種則是微軟自家關係型資料庫SQL Server。
二、基於Redis的分散式緩存
Redis數目前較為流行NoSQL資料庫,很多的編程平臺都將它作為分散式緩存的首選,接下來我們來演示如何在一個ASP.NET Core應用中如何採用基於Redis的分散式緩存。考慮到一些人可能還沒有體驗過Redis,所以我們先來簡單介紹一下如何安裝Redis。Redis最簡單的安裝方式就是採用Chocolatey(https://chocolatey.org/) 命令行,後者是Windows平臺下一款優秀的軟體包管理工具(類似於NPM)。
1: PowerShell prompt :
2: iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex
3:
4: CMD.exe:
5: @powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
我們既可以採用PowerShell (要求版本在V3以上)命令行或者普通CMD.exe命令行來安裝Chocolatey ,具體的命令如上所示。在確保Chocolatey 被本地正常安裝情況下,我們可以執行執行如下的命令安裝或者升級64位的Redis。
1: C:\>choco install redis-64
2: C:\>choco upgrade redis-64
Redis伺服器的啟動也很簡單,我們只需要以命令行的形式執行redis-server命令即可。如果在執行該命名之後看到如下圖所示的輸出,則表示本地的Redis伺服器被正常啟動,輸出的結果會指定伺服器採用的網路監聽埠。
接下來我們會對上面演示的實例進行簡單的修改,將基於記憶體的本地緩存切換到針對Redis資料庫的分散式緩存。針對Redis的分散式緩存實現在NuGet包“Microsoft.Extensions.Caching.Redis”之中,所以我們需要確保該NuGet包被正常安裝。不論採用Redis、SQL Server還是其他的分散式存儲方式,針對分散式緩存的操作都實現在DistributedCache這個服務對象向,該服務對應的介面為IDistributedCache。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseKestrel()
7: .ConfigureServices(svcs => svcs.AddDistributedRedisCache(options =>
8: {
9: options.Configuration = "localhost";
10: options.InstanceName = "Demo";
11: }))
12: .Configure(app => app.Run(async context =>
13: {
14: var cache = context.RequestServices.GetRequiredService<IDistributedCache>();
15: string currentTime = await cache.GetStringAsync("CurrentTime");
16: if (null == currentTime)
17: {
18: currentTime = DateTime.Now.ToString();
19: await cache.SetAsync("CurrentTime", Encoding.UTF8.GetBytes(currentTime));
20: }
21: await context.Response.WriteAsync($"{currentTime}({DateTime.Now})");
22: }))
23: .Build()
24: .Run();
25: }
26: }
從上面的代碼片段可以看出,針對分散式緩存和記憶體緩存在總體編程模式上是一致的,我們需要先註冊針對DistributedCache的服務註冊,但是利用依賴註入機制提供該服務對象來進行緩存數據的設置和緩存。我們調用IServiceCollection的另一個擴展方法AddDistributedRedisCache註冊DistributedCache服務,在調用這個方法的時候藉助於RedisCacheOptions這個對象的Configuration和InstanceName屬性設置Redis資料庫的伺服器和實例名稱。由於採用的是本地的Redis伺服器,所以我們將前者設置為“localhost”。其實Redis資料庫並沒有所為的實例的概念,RedisCacheOptions的InstanceName屬性的目的在於當多個應用共用同一個Redis資料庫的時候,緩存數據可以利用它來區分,當緩存數據被保存到Redis資料庫中的時候,對應的Key會以它為首碼。修改後的應用啟動後(確保Redis伺服器被正常啟動),如果我們利用瀏覽器來訪問它,依然會得到與前面類似的輸出。
對於基於記憶體的本地緩存來說,我們可以將任何類型的數據置於緩存之中,但是對於分散式緩存來說,由於涉及到網路傳輸甚至是持久化存儲,放到緩存中的數據類型只能是位元組數組,所以我們需要自行負責對緩存對象的序列化和反序列化工作。如上面的代碼片段所示,我們先將表示當前時間的DateTime對象轉換成字元串,然後採用UTF-8編碼進一步轉換成位元組數組,最終調用DistributedCache的SetAsync方法將後者緩存起來。實際上我們也可以直接調用另一個擴展方法SetStringAsync,它會負責將字元串編碼為位元組數組。在獲取緩存的時候,我們調用的是DistributedCache的GetStringAsync方法,它會將位元組數組轉換成字元串。
緩存數據在Redis資料庫中是以散列(Hash)的形式存放的,對應的Key會將設置的InstanceName作為首碼(如果進行了設置)。為了查看究竟存放了哪些數據在Redis資料庫中,我們可以按照如圖3所示的形式執行Redis命名來獲取存儲的數據。從下圖呈現的輸出結果我們不難看出,存入的不僅僅包括我們指定的緩存數據(Sub-Key為“data”)之外,還包括其他兩組針對該緩存條目的描述信息,對應的Sub-Key分別為“absexp”和“sldexp”,表示緩存的絕對過期時間(Absolute Expiration Time)和滑動過期時間(Slidding Expiration Time)。
三、基於SQL Server的分散式緩存
除了使用Redis這種主流的NoSQL資料庫來支持分散式緩存,微軟在設計分散式緩存時也沒有忘記自家的關係型資料庫採用SQL Server。針對SQL Server的分散式緩存實現在“Microsoft.Extensions.Caching.SqlServer”這個NuGet包中,我們先得確保該NuGet包被正常裝到演示的應用中。
所謂的針對SQL Server的分散式緩存,實際上就是將標識緩存數據的位元組數組存放在SQL Server資料庫中某個具有固定結構的數據表中,因為我們得先來創建這麼一個緩存表,該表可以藉助一個名為sql-cache 的工具來創建。在執行sql-cache 工具創建緩存表之前,我們需要在project.json文件中按照如下的形式為這個工具添加相應的NuGet包“Microsoft.Extensions.Caching.SqlConfig.Tools”。
1: {
2: …
3: "tools": {
4: "Microsoft.Extensions.Caching.SqlConfig.Tools": "1.1.0-preview4-final"
5: }
6: }
當針對上述這個NuGet包複原(Restore)之後,我們可以執行“dotnet sql-cache create”命令來創建,至於這個執行這個命令應該指定怎樣的參數,我們可以按照如下的形式通過執行“dotnet sql-cache create --help”命令來查看。從下圖可以看出,該命名需要指定三個參數,它們分別表示緩存資料庫的鏈接字元串、緩存表的Schema和名稱。
接下來我們只需要在演示應用所在的項目根目錄(project.json文件所在的目錄)下執行dotnet sql-cache create就可以在指定的資料庫創建緩存表了。對於我們演示的實例來說,我們按照下圖所示的方式執行這dotnet sql-cache create命令行在本機一個名為demodb的資料庫中創建了一個名為AspnetCache的緩存表,該表採用dbo作為Schema。
在所有的準備工作完成之後,我們只需要對上面的程式做如下的修改即可將針對Redis資料庫的緩存切換到針對SQL Server資料庫的緩存。由於採用的同樣是分散式緩存,所以針對緩存數據的設置和提取的代碼不用做任何改變,我們需要修改的地方僅僅是服務註冊部分。如下麵的代碼片段所示,我們在WebHostBuilder的ConfigureServices方法中調用IServiceCollection的擴展方法AddDistributedSqlServerCache完成了對應的服務註冊。在調用這個方法的時候,我們通過設置SqlServerCacheOptions對象的三個屬性的方式指定了緩存資料庫的鏈接字元串和緩存表的Schema和名稱。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseKestrel()
7: .ConfigureServices(svcs => svcs.AddDistributedSqlServerCache(options =>
8: {
9: options.ConnectionString = "server=.;database=demodb;uid=sa;pwd=password";
10: options.SchemaName = "dbo";
11: options.TableName = "AspnetCache";
12: }))
13: .Configure(app => app.Run(async context =>
14: {
15: var cache = context.RequestServices.GetRequiredService<IDistributedCache>();
16: string currentTime = await cache.GetStringAsync("CurrentTime");
17: if (null == currentTime)
18: {
19: currentTime = DateTime.Now.ToString();
20: await cache.SetAsync("CurrentTime", Encoding.UTF8.GetBytes(currentTime));
21: }
22: await context.Response.WriteAsync($"{currentTime}({DateTime.Now})");
23: }))
24: .Build()
25: .Run();
26: }