.NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存。前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一个独立的“中心数据库”。对于分布式缓存,.NET提供了针对Redis和SQL Server的原生支持。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)

[S1101]基于内存的本地缓存(源代码

[S1102]基于Redis的分布式缓存(源代码

[S1103]基于SQL Server的分布式缓存(源代码

[S1101]基于内存的本地缓存

相较于针对数据库和远程服务调用这种IO操作来说,针对内存的访问在性能上将获得不只一个数量级的提升,所以将数据对象直接缓存在应用进程的内存中具有最佳的性能优势。基于内存的缓存框架实现在NuGet包“Microsoft.Extensions.Caching.Memory”中,具体的缓存功能由IMemoryCache对象提供。由于缓存的数据直接存放在内存中,所以无须考虑序列化问题,对缓存数据的类型也就没有任何限制。

缓存的操作主要是对缓存数据的读和写,这两个基本操作都是由上面介绍的IMemoryCache对象来完成的。对于像ASP.NET这种支持依赖注入应用开发框架来说,采用注入的方式来使用IMemoryCache对象是推荐的编程方式。在如下所示的演示程序中,我们通过调用AddMemoryCache扩展方法将针对内存缓存的服务注册添加到创建的ServiceCollection对象中,最终利用构建的IServiceProvider对象得到我们所需的IMemoryCache对象。

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; var cache = new ServiceCollection()
.AddMemoryCache()
.BuildServiceProvider()
.GetRequiredService<IMemoryCache>(); for (int index = 0; index < 5; index++)
{
Console.WriteLine(GetCurrentTime());
await Task.Delay(1000);
} DateTimeOffset GetCurrentTime()
{
if (!cache.TryGetValue<DateTimeOffset>("CurrentTime", out var currentTime))
{
cache.Set("CurrentTime", currentTime = DateTimeOffset.UtcNow);
}
return currentTime;
}

为了展现缓存的效果,我们将当前时间缓存起来。如上面的代码片段所示,用于返回当前时间的GetCurrentTime方法在执行的时候会调用IMemoryCache对象的TryGetValue<T>方法,该方法根据指定的Key(“CurrentTime”)提取缓存的时间。如果通过该方法的返回值确定时间尚未被缓存,它会调用Set方法对当前时间予以缓存。我们的演示程序会以一秒的间隔五次调用这个GetCurrentTime,并将返回的时间输出控制台上。由于使用了缓存,所以每次都会输出相同的时间。


图1 缓存在内存中的时间

[S1102]基于Redis的分布式缓存

虽然采用基于本地内存缓存可以获得最高的性能优势,但对于部署在集群的应用程序无法确保缓存内容的一致性。为了解决这个问题,我们可以选择将数据缓存在某个独立的存储中心,以便让所有的应用实例共享同一份缓存数据,我们将这种缓存形式称为分布式缓存。 .NET为分布式缓存提供了Redis和SQL Server这两种原生的存储形式。

Redis是目前较为流行的NoSQL数据库,很多编程平台都将其作为分布式缓存的首选。由于演示程序运行在Windows系统下,所以我们使用与之完全兼容的Memurai来代替Redis。考虑到有的读者可能没有在Windows环境下体验过Redis/Memurai,所以我们先简单介绍Redis/Memurai如何安装。Redis/Memurai最简单的安装方式就是采用Chocolatey命令行(Chocolatey是Windows平台下一款优秀的软件包管理工具),Chocolatey的官方站点(https://chocolatey.org/install)提供了各种安装方式。在确保Chocolatey被正常安装的情况下,我们可以执行“choco install redis-64”命令安装或者升级64位的Redis,从图11-2可以看出我们真正安装的是用来代替Redis的Memurai开发版。


图2 安装Redis/Memurai

Redis/Memurai服务器的启动也很简单,我们只需要以命令行的形式执行“memurai”命令即可。如果在执行该命令之后看到图11-3所示的输出,则表示本地的Redis/Memurai服务器被正常启动,输出的结果会指明服务器采用的网络监听端口(默认6379)和进程号。


图3 以命令行的形式启动Memurai服务器

我们接下来对上面演示的实例进行简单的修改,将基于内存的本地缓存切换到针对Redis数据库的分布式缓存。不论采用Redis、SQL Server还是其他的分布式存储方式,缓存的读和写都是通过IDistributedCache对象完成的。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)。


图4 查看Redis数据库中存放的数据

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

ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[07]:文件系统

    ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...

  2. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  3. ASP.NET Core 6框架揭秘实例演示[09]:配置绑定

    我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...

  4. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  5. ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式

    在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...

  6. ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法

    一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...

  7. ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]

    <诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...

  8. ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法

    为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...

  9. ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出

    针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...

随机推荐

  1. java代码实现调用短信接口,发送短信验证。

    一.代码示例 package com.aaa.zxf.login; import org.apache.commons.httpclient.HttpClient; import org.apache ...

  2. IntelliJ IDEA 中文官方文档

    目录 认识IntelliJ IDEA IntelliJ IDEA 安装和设置 IntelliJ IDEA如何使用 IntelliJ IDEA中不容错过的快捷键 IntelliJ IDEA专业的使用技巧 ...

  3. Java 高效编程(Effective Java)中文第三版(补档)

    来源:sjsdfg/effective-java-3rd-chinese <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过, ...

  4. vivo直播应用技术实践与探索

    一.概述 2019年vivo直播平台立项,初期与优秀的顶部直播平台进行联运直播开发,进行市场,产品和技术的初步探索:再到后来为了丰富直播的内容和形式,开始自己独立探索:之后,我们结合vivo现阶段的直 ...

  5. HTTPS的基本使用

    1.https简单说明 HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的 ...

  6. Android WebView组件 访问部分网页崩溃问题【已解决】

    最近刚接触Android,在测试WebView组件时发现总是出现崩溃现像: 提示:ERR_CLEARTEXT_NOT_PERMITTED 当时以为是权限问题,查找自己的AndroidManifest文 ...

  7. CSS样式表的书写位置

    行内式(内联样式) 是通过标签的style属性来设置元素的样式,其基本语法格式如下: <标签名 style="属性1:属性值1; 属性2:属性值2; 属性3:属性值3;"&g ...

  8. 浅谈php web安全

    首先,笔记不是web安全的专家,所以这不是web安全方面专家级文章,而是学习笔记.细心总结文章,里面有些是我们phper不易发现或者说不重视的东西.所以笔者写下来方便以后查阅.在大公司肯定有专门的we ...

  9. yum本地源仓库安装报错 被锁定如何解决?

    yum安装报错被锁定如何解决 上图!!!!!如图的报错 解决方法: 是不是很简单 智商在地上摩擦~~~

  10. shell脚本命令(sotr/unip/tr/cut/eval)与正则表达式

    shell脚本命令(sotr/unip/tr/cut/eval)与正则表达式 1.sort命令 概述: Linux sort命令用于将文本文件内容加以排序. sort命令可针对文本文件的内容,以行为单 ...