Redis分散式快取承載於 “Microsoft.Extensions.Caching.Redis”這個NuGet包
Redis分散式快取承載於 “Microsoft.Extensions.Caching.Redis”這個NuGet包中,我們需要手動新增針對該NuGet包的依賴。
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; var cache = new ServiceCollection().AddDistributedRedisCache(options => { options.Configuration = "localhost"; options.InstanceName = "Demo"; }) .BuildServiceProvider() .GetRequiredService<IDistributedCache>(); for (int index = 0; index < 5; index++) { Console.WriteLine(await GetCurrentTimeAsync()); await Task.Delay(1000); } async Task<DateTimeOffset> GetCurrentTimeAsync() { var timeLiteral = await cache.GetStringAsync("CurrentTime"); if (string.IsNullOrEmpty(timeLiteral)) { await cache.SetStringAsync("CurrentTime", timeLiteral = DateTimeOffset.UtcNow.ToString()); } return DateTimeOffset.Parse(timeLiteral); }
從上面的程式碼片段可以看出,分散式快取和記憶體快取在總體程式設計模式上是一致的,我們需要先完成針對IDistributedCache服務的註冊,然後利用依賴注入框架提供該服務物件來進行快取資料的讀和寫。IDistributedCache服務的註冊是通過呼叫IServiceCollection介面的AddDistributedRedisCache方法來完成的。我們在呼叫這個方法時提供了一個RedisCacheOptions物件,並利用它的Configuration和InstanceName屬性設定Redis資料庫的伺服器與例項名稱。
由於採用的是本地的Redis伺服器,所以我們將Configuration屬性設定為localhost。其實Redis資料庫並沒有所謂的例項的概念,RedisCacheOptions型別的InstanceName屬性的目的在於當多個應用共享同一個Redis資料庫時,快取資料可以利用它進行區分。當快取資料被儲存到Redis資料庫中的時候,對應的Key以InstanceName為字首。應用程式啟動後(確保Redis伺服器被正常啟動),如果我們利用瀏覽器來訪問它,依然可以得到與圖1類似的輸出。
對於基於記憶體的本地快取來說,我們可以將任何型別的資料置於快取之中,但是分散式快取涉及網路傳輸和持久化儲存,置於快取中的資料型別只能是位元組陣列,所以我們需要自行負責對快取物件的序列化和反序列化工作。如上面的程式碼片段所示,我們先將表示當前時間的DateTime物件轉換成字串,然後採用UTF-8編碼進一步轉換成位元組陣列。我們呼叫IDistributedCache介面的SetAsync方法快取的資料是最終的位元組陣列。我們也可以直接呼叫SetStringAsync擴充套件方法將字串編碼為位元組陣列。在讀取快取資料時,我們呼叫的是IDistributedCache介面的GetStringAsync方法,它會將位元組陣列轉換成字串。
快取資料在Redis資料庫中是以雜湊(Hash)的形式存放的,對應的Key會將設定的InstanceName屬性作為字首。為了檢視在Redis資料庫中究竟存放了哪些資料,我們可以按照圖4所示的形式執行Redis命令獲取儲存的資料。從輸出結果可以看出存入Redis資料庫的不僅包括指定的快取資料(Sub-Key為data),還包括其他兩組針對該快取條目的描述資訊,對應的Sub-Key分別為absexp和sldexp,表示快取的絕對過期時間(Absolute Expiration Time)和滑動過期時間(Sliding Expiration Time)。
[S1103]基於SQL Server的分散式快取
除了使用Redis這種主流的NoSQL資料庫來支援分散式快取,還可以使用關係型資料庫SQL Server。針對SQL Server的分散式快取實現在NuGet包“Microsoft.Extensions.Caching.SqlServer”中,我們需要先確保該NuGet包被正常安裝到演示的應用程式中。針對SQL Server的分散式快取實際上就是將表示快取資料的位元組陣列存放在SQL Server資料庫的某個具有固定結構的資料表中,所以我們需要先建立這樣一個快取表。該表可以通過dotnet-sql-cache命令列工具進行建立。如果該命令列工具尚未安裝,我們可以執行“dotnet tool install --global dotnet-sql-cache”進行安裝。
具體來說,儲存快取資料的表可以採用命令列的形式執行“dotnet sql-cache create”命令來建立。執行這個命令應該指定的引數可以按照如下形式通過執行“dotnet sql-cache create --help”命令來檢視。從圖5可以看出,該命令需要指定三個引數,它們分別表示快取資料庫的連線字串、快取表的Schema和名稱。
圖5 dotnet sql-cache create命令的幫助文件
接下來只需要以命令列的形式執行“dotnet sql-cache create”命令就可以在指定的資料庫中建立快取表。對於演示的例項來說,可以按照圖6所示的方式執行“dotnet sql-cache create”命令,該命令會在本機一個名為DemoDB的資料庫中(資料庫需要預先建立好)建立一個名為AspnetCache的快取表,該表採用dbo作為Schema。
圖6 執行“dotnet sql-cache create”命令建立快取表
在所有的準備工作完成之後,我們只需要對上面的程式做如下修改就可以將快取儲存方式從Redis資料庫切換到針對SQL Server的資料庫。由於採用的同樣是分散式快取,所以針對快取資料的設定和提取的程式碼不用做任何改變,我們需要修改的地方僅僅是服務註冊部分。如下面的程式碼片段所示,我們呼叫IServiceCollection介面的AddDistributedSqlServerCache擴充套件方法完成了對應的服務註冊。在呼叫這個方法的時候,我們通過設定SqlServerCacheOptions物件三個屬性的方式指定了快取資料庫的連線字串、快取表的Schema和名稱。
public class Program { public static void Main() { Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(builder => builder.ConfigureServices(svcs => svcs.AddDistributedSqlServerCache(options => { options.ConnectionString = "server=.;database=demodb;uid=sa;pwd=password"; options.SchemaName = "dbo"; options.TableName = "AspnetCache"; })) .Configure(app => app.Run(async context => { var cache = context.RequestServices.GetRequiredService<IDistributedCache>(); var currentTime = await cache.GetStringAsync("CurrentTime"); if (null == currentTime) { currentTime = DateTime.Now.ToString(); await cache.SetAsync("CurrentTime", Encoding.UTF8.GetBytes(currentTime)); } await context.Response.WriteAsync($"{currentTime}({DateTime.Now})"); }))) .Build() .Run(); } }
若要檢視最終存入SQL Server資料庫中的快取資料,我們只需要在資料庫中檢視對應的快取表即可。對於演示例項快取的時間戳,它會以圖7所示的形式儲存在我們建立的快取表(AspnetCache)中。與基於Redis資料庫的儲存方式類似,與快取資料的值一併儲存的還包括快取的過期資訊。
圖7 儲存在快取表中的資料
Redis分散式快取承載於 “Microsoft.Extensions.Caching.Redis”這個NuGet包中,我們需要手動新增針對該NuGet包的依賴。
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; var cache = new ServiceCollection().AddDistributedRedisCache(options => { options.Configuration = "localhost"; options.InstanceName = "Demo"; }) .BuildServiceProvider() .GetRequiredService<IDistributedCache>(); for (int index = 0; index < 5; index++) { Console.WriteLine(await GetCurrentTimeAsync()); await Task.Delay(1000); } async Task<DateTimeOffset> GetCurrentTimeAsync() { var timeLiteral = await cache.GetStringAsync("CurrentTime"); if (string.IsNullOrEmpty(timeLiteral)) { await cache.SetStringAsync("CurrentTime", timeLiteral = DateTimeOffset.UtcNow.ToString()); } return DateTimeOffset.Parse(timeLiteral); }
從上面的程式碼片段可以看出,分散式快取和記憶體快取在總體程式設計模式上是一致的,我們需要先完成針對IDistributedCache服務的註冊,然後利用依賴注入框架提供該服務物件來進行快取資料的讀和寫。IDistributedCache服務的註冊是通過呼叫IServiceCollection介面的AddDistributedRedisCache方法來完成的。我們在呼叫這個方法時提供了一個RedisCacheOptions物件,並利用它的Configuration和InstanceName屬性設定Redis資料庫的伺服器與例項名稱。
由於採用的是本地的Redis伺服器,所以我們將Configuration屬性設定為localhost。其實Redis資料庫並沒有所謂的例項的概念,RedisCacheOptions型別的InstanceName屬性的目的在於當多個應用共享同一個Redis資料庫時,快取資料可以利用它進行區分。當快取資料被儲存到Redis資料庫中的時候,對應的Key以InstanceName為字首。應用程式啟動後(確保Redis伺服器被正常啟動),如果我們利用瀏覽器來訪問它,依然可以得到與圖1類似的輸出。
對於基於記憶體的本地快取來說,我們可以將任何型別的資料置於快取之中,但是分散式快取涉及網路傳輸和持久化儲存,置於快取中的資料型別只能是位元組陣列,所以我們需要自行負責對快取物件的序列化和反序列化工作。如上面的程式碼片段所示,我們先將表示當前時間的DateTime物件轉換成字串,然後採用UTF-8編碼進一步轉換成位元組陣列。我們呼叫IDistributedCache介面的SetAsync方法快取的資料是最終的位元組陣列。我們也可以直接呼叫SetStringAsync擴充套件方法將字串編碼為位元組陣列。在讀取快取資料時,我們呼叫的是IDistributedCache介面的GetStringAsync方法,它會將位元組陣列轉換成字串。
快取資料在Redis資料庫中是以雜湊(Hash)的形式存放的,對應的Key會將設定的InstanceName屬性作為字首。為了檢視在Redis資料庫中究竟存放了哪些資料,我們可以按照圖4所示的形式執行Redis命令獲取儲存的資料。從輸出結果可以看出存入Redis資料庫的不僅包括指定的快取資料(Sub-Key為data),還包括其他兩組針對該快取條目的描述資訊,對應的Sub-Key分別為absexp和sldexp,表示快取的絕對過期時間(Absolute Expiration Time)和滑動過期時間(Sliding Expiration Time)。
[S1103]基於SQL Server的分散式快取
除了使用Redis這種主流的NoSQL資料庫來支援分散式快取,還可以使用關係型資料庫SQL Server。針對SQL Server的分散式快取實現在NuGet包“Microsoft.Extensions.Caching.SqlServer”中,我們需要先確保該NuGet包被正常安裝到演示的應用程式中。針對SQL Server的分散式快取實際上就是將表示快取資料的位元組陣列存放在SQL Server資料庫的某個具有固定結構的資料表中,所以我們需要先建立這樣一個快取表。該表可以通過dotnet-sql-cache命令列工具進行建立。如果該命令列工具尚未安裝,我們可以執行“dotnet tool install --global dotnet-sql-cache”進行安裝。
具體來說,儲存快取資料的表可以採用命令列的形式執行“dotnet sql-cache create”命令來建立。執行這個命令應該指定的引數可以按照如下形式通過執行“dotnet sql-cache create --help”命令來檢視。從圖5可以看出,該命令需要指定三個引數,它們分別表示快取資料庫的連線字串、快取表的Schema和名稱。
圖5 dotnet sql-cache create命令的幫助文件
接下來只需要以命令列的形式執行“dotnet sql-cache create”命令就可以在指定的資料庫中建立快取表。對於演示的例項來說,可以按照圖6所示的方式執行“dotnet sql-cache create”命令,該命令會在本機一個名為DemoDB的資料庫中(資料庫需要預先建立好)建立一個名為AspnetCache的快取表,該表採用dbo作為Schema。
圖6 執行“dotnet sql-cache create”命令建立快取表
在所有的準備工作完成之後,我們只需要對上面的程式做如下修改就可以將快取儲存方式從Redis資料庫切換到針對SQL Server的資料庫。由於採用的同樣是分散式快取,所以針對快取資料的設定和提取的程式碼不用做任何改變,我們需要修改的地方僅僅是服務註冊部分。如下面的程式碼片段所示,我們呼叫IServiceCollection介面的AddDistributedSqlServerCache擴充套件方法完成了對應的服務註冊。在呼叫這個方法的時候,我們通過設定SqlServerCacheOptions物件三個屬性的方式指定了快取資料庫的連線字串、快取表的Schema和名稱。
public class Program { public static void Main() { Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(builder => builder.ConfigureServices(svcs => svcs.AddDistributedSqlServerCache(options => { options.ConnectionString = "server=.;database=demodb;uid=sa;pwd=password"; options.SchemaName = "dbo"; options.TableName = "AspnetCache"; })) .Configure(app => app.Run(async context => { var cache = context.RequestServices.GetRequiredService<IDistributedCache>(); var currentTime = await cache.GetStringAsync("CurrentTime"); if (null == currentTime) { currentTime = DateTime.Now.ToString(); await cache.SetAsync("CurrentTime", Encoding.UTF8.GetBytes(currentTime)); } await context.Response.WriteAsync($"{currentTime}({DateTime.Now})"); }))) .Build() .Run(); } }
若要檢視最終存入SQL Server資料庫中的快取資料,我們只需要在資料庫中檢視對應的快取表即可。對於演示例項快取的時間戳,它會以圖7所示的形式儲存在我們建立的快取表(AspnetCache)中。與基於Redis資料庫的儲存方式類似,與快取資料的值一併儲存的還包括快取的過期資訊。
圖7 儲存在快取表中的資料