《ASP.NET Core 6框架揭秘》实例演示[16]:内存缓存与分布式缓存的使用

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

[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,并将返回的时间输出控制台上。由于使用了缓存,所以每次都会输出相同的时间。

f5300b6ef201da66a00f558ec8d3c38a.png
图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开发版。

62cd54effdef3418ecf17e1be7120329.png
图2 安装Redis/Memurai

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

d4fe88e31a7c0538bb7e640ed3fbcb0a.png
图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)。

73aa82390755cc28a787623524a1fecf.png
图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和名称。

7e41c02e060526befee265eb0f87d380.png
图5 dotnet sql-cache create命令的帮助文档

接下来只需要以命令行的形式执行“dotnet sql-cache create”命令就可以在指定的数据库中创建缓存表。对于演示的实例来说,可以按照图6所示的方式执行“dotnet sql-cache create”命令,该命令会在本机一个名为DemoDB的数据库中(数据库需要预先创建好)创建一个名为AspnetCache的缓存表,该表采用dbo作为Schema。

2aa91e827e3dbfe99223dd4d25e74895.png
图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数据库的存储方式类似,与缓存数据的值一并存储的还包括缓存的过期信息。

5be8c04bee3330805b29cbdbe0cff9a0.png
图7 存储在缓存表中的数据

《ASP.NET Core 6框架揭秘》实例演示[01]:编程初体验
《ASP.NET Core 6框架揭秘》实例演示[02]:各种形式的API开发
《ASP.NET Core 6框架揭秘》实例演示[03]:Dapr初体验
《ASP.NET Core 6框架揭秘》实例演示[04]:自定义依赖注入框架
《ASP.NET Core 6框架揭秘》实例演示[05]:依赖注入基本编程模式
《ASP.NET Core 6框架揭秘》实例演示[06]:依赖注入框架设计细节
《ASP.NET Core 6框架揭秘》实例演示[07]:文件系统
《ASP.NET Core 6框架揭秘》实例演示[08]:配置的基本编程模式
《ASP.NET Core 6框架揭秘》实例演示[09]:将配置绑定为对象
《ASP.NET Core 6框架揭秘》实例演示[10]:Options基本编程模式
《ASP.NET Core 6框架揭秘》实例演示[11]:诊断跟踪的几种基本编程方式 
《ASP.NET Core 6框架揭秘》实例演示[13]:日志的基本编程模式
《ASP.NET Core 6框架揭秘》实例演示[14]:日志的进阶用法
《ASP.NET Core 6框架揭秘》实例演示[15]:针对控制台的日志输出

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283213.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人工智能教程007:创建一个卷积神经网络(2)

2019独角兽企业重金招聘Python工程师标准>>> 我们如何对图像应用卷积 当我们在图像上应用卷积时&#xff0c;我们在两个维度上执行卷积——水平和竖直方向。我们混合两桶信息&#xff1a;第一桶是输入的图像&#xff0c;由三个矩阵构成——RGB三通道&#xff0c;其中…

【系统知识点】linux入门基础命令

大概总结了一下一些基础命令&#xff0c;仅仅是帮助基础使用linux文件系统&#xff0c;如果还有相关基础命令&#xff0c;希望大家留言一起补充汇总一下&#xff01;命令的基本格式&#xff1a;格式&#xff1a;command [-options] parameter1 parameter2 …ps&#xff1a;第一…

Blazor University (48)依赖注入 —— Scoped 依赖

原文链接&#xff1a;https://blazor-university.com/dependency-injection/dependency-lifetimes-and-scopes/scoped-dependencies/Scoped 依赖Scoped 依赖项类似于 Singleton 依赖项&#xff0c;因为 Blazor 会将相同的实例注入到依赖它的每个对象中&#xff0c;但不同之处在…

c 连接mysql怎么增删改_C++ API方式连接mysql数据库实现增删改查

这里复制的http://www.bitscn.com/pdb/mysql/201407/226252.html一、环境配置1&#xff0c;装好mysql&#xff0c;新建一个C控制台工程(从最简单的弄起&#xff0c;这个会了&#xff0c;可以往任何c工程移植)&#xff0c;在vs2010中设置&#xff0c;工程--属性--VC目录--包含目…

阿里云如何实现海量短视频的极速分发?答案在这里!

摘要&#xff1a;短视频行业目前比较火热&#xff0c;但是如何快速的实现海量短视频的极速分发&#xff0c;对于短视频业务提供方来讲是一个比较棘手的问题。阿里云技术专家将带领大家从视频的上传、采集、存储和CDN分发等方面为我们介绍阿里云的整体方案&#xff0c;并且重点讲…

GitHub项目管理维护实用教程

GitHub项目维护教程 1&#xff09;注册GitHub账户并登陆&#xff1b; 2&#xff09;在Windows cmd&#xff08;或Ubuntu中的terminal&#xff09;中cd到自己的工作目录&#xff0c;将仓库clone下来&#xff1a; 命令&#xff1a; 1 git clone https://github.com/... #项目地址…

图文详解cacti的安装和使用

简介&#xff1a; 1.cacti介绍2.安装服务端3.安装客户端4.添加监控的设备cacti的介绍Cacti是一套基于PHP,MySQL,SNMP及RRDTool开发的网络流量监测图形分析工具Cacti是通过 snmpget来获取数据&#xff0c;使用 RRDtool绘画图形&#xff0c;而且你完全可以不需要了解RRDtool复杂…

记一次 .NET 某金融企业 WPF 程序卡死分析

一&#xff1a;背景 1. 讲故事前段时间遇到了一个难度比较高的 dump&#xff0c;经过几个小时的探索&#xff0c;终于给找出来了&#xff0c;在这里做一下整理&#xff0c;希望对大家有所帮助&#xff0c;对自己也是一个总结&#xff0c;好了&#xff0c;老规矩&#xff0c;上 …

如何将图片放到mysql_怎么将图片添加到mysql中

将图片添加到mysql中的方法&#xff1a;首先将数据库存储图片的字段类型设置为blob二进制大对象类型&#xff1b;然后将图片流转化为二进制&#xff1b;最后将图片插入数据库即可。正常的图片储存要么放进本地磁盘&#xff0c;要么就存进数据库。存入本地很简单&#xff0c;现在…

Java线程与Linux内核线程的映射关系

http://blog.sina.com.cn/s/blog_605f5b4f010198b5.html Linux从内核2.6開始使用NPTL &#xff08;Native POSIX Thread Library&#xff09;支持&#xff0c;但这时线程本质上还轻量级进程。Java里的线程是由JVM来管理的。它怎样相应到操作系统的线程是由JVM的实现来确定的。L…

YoursLC 有源 低代码 项目介绍

YoursLC 是我们独立研发的一款低代码产品&#xff0c;YoursLC-yours你们的、LC是低代码low-code的缩写&#xff0c;中文名称&#xff1a;有源低代码&#xff0c; 是一套双输出的低代码产品&#xff1a;既能完整输出功能又能100%输出源码。满足用户高效率、低成本和个性化的需求…

stm32电机控制定时器1_STM32通过PWM控制电机速度

做STM32智能小车的实验中会用到定时器PWM输出&#xff0c;来改变直流电机的转速。分享本文了解如何通过PWM实现对电机速度的控制。PWM控制电机速度的基本原理PWM(Pulse Width Modulation)&#xff0c;也就是脉冲宽度调制。PWM中有一个比较重要的概念&#xff0c;占空比&#xf…

走向无后端的系统开发实践:CRUD自动化与强约定的REST接口

2019独角兽企业重金招聘Python工程师标准>>> ttp://mp.weixin.qq.com/s?__bizMzAwMDU1MTE1OQ&idx1&mid2653548079&sn2377b625db58b2ea93c3ef2d87e4c395 转载于:https://my.oschina.net/yunjie/blog/806130

mysql char varchar 性能_Mysql小细节:varchar与char在性能上的特点

varchar与char的一个主要区别是存储方式的不同varchar 是变长存储占用的存储空间 存储内容实际大小 长度记录位char 是定长存储占用的存储空间 字段声明的宽度存储方式对性能是有影响的例如分别使用 varchar(10) 与 varchar(255) 定义一个字段&#xff0c;实际存储的字符串为…

Dubbo源码解析之Zookeeper连接

2019独角兽企业重金招聘Python工程师标准>>> 注&#xff1a;Dubbo的版本是2.5.7。 图1 RegistryProtocol的export时序图 注册中心有Zookeeper、Redis、Dubbo&#xff0c;分别对应ZookeeperRegistry、RedisRegistry、MulticastRegistry。 连接Dubbo的客户端有俩种&am…

SHELL 脚本小技巧

脚本很简单&#xff0c;直接上功能介绍及脚本&#xff0c;可以做模板使用&#xff1a; 记录日志,记录脚本开始执行时间、结束时间usage 函数&#xff0c;脚本需接参数执行&#xff0c;避免误执行&#xff0c;告诉用户&#xff0c;这个脚本的使用方法加锁&#xff0c;创建锁文件…

WinForm(十)项目框架结构

看到下面的项目结构&#xff0c;是否曾经相识&#xff1f;不要笑&#xff0c;这也是一种项目结构&#xff0c;极简主义。项目结构没有对错&#xff0c;合适就好&#xff0c;但也要有几个要求&#xff0c;至少要做到结构明确&#xff0c;清晰&#xff0c;当然上图的结构清晰&…

mysql索引有哪些了解_Mysql索引(简单了解)

Mysql的存储引擎&#xff0c;可以针对不同的表使用不同的存储引擎MyISAM&#xff1a;插入&#xff0c;查血速度快&#xff0c;但是不支持事物&#xff0c;所以适用于数据仓库&#xff0c;Web等InnoDB&#xff1a;支持事物&#xff0c;所以适合于事物型数据库Memory&#xff1a;…

使用Spring发送带附件的电子邮件(站内和站外传送)

1.使用Spring发送带附件的电子邮件 <?xml version"1.0" encoding"UTF-8"?> <beansxmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:p"http://www.spr…

vmware 克隆后Linux没有eth网卡只有lo

想试着在虚拟机上搭下主从&#xff0c;&#xff0c;&#xff0c;结果&#xff0c;克隆出来的虚拟机没有网卡。。。只有lo 于是跟着别人的来添加一个网卡 第一步&#xff1a;打开克隆的虚拟机 第二步&#xff1a; 第三步 第四步&#xff1a; 最后&#xff1a; 之后执行reboot重启…