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 存储在缓存表中的数据