翼度科技»论坛 编程开发 .net 查看内容

ASP.NET Core - 缓存之内存缓存(上)

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
1. 缓存

缓存指的是在软件应用运行过程中,将一些数据生成副本直接进行存取,而不是从原始源(数据库,业务逻辑计算等)读取数据,减少生成内容所需的工作,从而显著提高应用的性能和可伸缩性,使用好缓存技术,有利于提高我们提升用户体验性。
对于缓存的使用有以下一些注意点:

  • 缓存最适用于不常更改且生成成本很高的数据。
  • 代码应始终具有回退选项,以提取数据,而不依赖于可用的缓存值。
    我们应该以从不依赖于缓存数据的方式编写和测试应用。缓存是会失效的,我们在进行应用开发时应该考虑到缓存失效的情况,提供缓存失效时按照正常逻辑获取相关数据的方式。
  • 缓存使用短缺资源:内存。 我们应该限制缓存增长:

    • 不要将外部输入插入到缓存中。 例如,不建议使用用户提供的任意输入作为缓存键,因为输入可能会消耗不可预测的内存量。
    • 使用过期限制缓存增长。
    • 应当限制缓存的大小,避免缓存过度增长

软件开发中对缓存的使用一般有两种情况,一种是内存缓存,一种是分布式缓存。
2. NET Core 的内存缓存

内存缓存是最简单的一种缓存方式,就是使用应用所在的服务器的内存来保存一些数据副本,利用内存读写比磁盘、网络请求快的特点来提供应用性能。内存缓存一般应用于单机应用,一旦应用重启,内存缓存中的数据就会丢失。
如果是在服务器场(多个服务器)中运行的应用使用内存缓存,应确保在使用内存中缓存时会话是粘滞的。 粘滞会话可确保来自客户端的请求都转到同一服务器。
2. 1 内存缓存启用

.NET Core 框架下对于内存缓存的使用是通过 IMemoryCache ,可以通过将其注册到容器中,之后在需要的地方注入使用。对于大多数应用,在 Program.cs 中调用许多其他 Add{Service} 方法可以启用 IMemoryCache,例如 AddMvc、AddControllersWithViews、AddRazorPages、AddMvcCore().AddRazorViewEngine 等 。
如果应用中没有使用到上述的这些方法,我们也可以自行引入  Microsoft.Extensions.Caching.Memory Nuget 包,通过 AddMemoryCache 方法启动内存缓存。为了方便演示,以下示例使用 .NET 6 控制台应用。
  1. using Microsoft.Extensions.DependencyInjection;
  2. using Microsoft.Extensions.Hosting;
  3. Host.CreateDefaultBuilder(args)
  4.         .ConfigureServices(services =>
  5.         {
  6.                 services.AddMemoryCache();
  7.         })
  8.         .Build().Run();
复制代码
2. 2 内存缓存基本用法

内存缓存的使用,只需要将 IMemoryCache 服务注入到类中进行使用即可。我们可以通过 TryGetValue 方法尝试从缓存中获取数据,通过 Set 方法向缓存中添加数据。
  1. public interface ICacheService
  2. {
  3.         public void PrintDateTimeNow();
  4. }
  5. public class CacheService : ICacheService
  6. {
  7.         public const string CacheKey = "CacheTime";
  8.         private readonly IMemoryCache _cache;
  9.         public CacheService(IMemoryCache memoryCache)
  10.         {
  11.                 _cache = memoryCache;
  12.         }
  13.         public void PrintDateTimeNow()
  14.         {
  15.                 var time = DateTime.Now;
  16.                 if(!_cache.TryGetValue(CacheKey, out DateTime cacheValue))
  17.                 {
  18.                         cacheValue = time;
  19.                         _cache.Set(CacheKey, cacheValue);
  20.                 }
  21.                 time = cacheValue;
  22.                 Console.WriteLine("缓存时间:" + time.ToString("yyyy-MM-dd HH:mm:ss"));
  23.                 Console.WriteLine("当前时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  24.         }
  25. }
  26. using CacheSample;
  27. using Microsoft.Extensions.DependencyInjection;
  28. using Microsoft.Extensions.Hosting;
  29. var host = Host.CreateDefaultBuilder(args)
  30.         .ConfigureServices(services =>
  31.         {
  32.                 services.AddMemoryCache();
  33.                 services.AddTransient<ICacheService, CacheService>();
  34.         })
  35.         .Build();
  36. var service = host.Services.GetRequiredService<ICacheService>();
  37. service.PrintDateTimeNow();
  38. Task.Delay(TimeSpan.FromSeconds(2)).Wait();
  39. service.PrintDateTimeNow();
  40. host.Run();
复制代码
通过控制台打印结果,可以看到,当前时间已经改变,但是缓存的时间是之前的数据。

缓存系统将缓存项存储为键值对,内存缓存中键和值都可以是任意类型,不过一般情况下我们会将字符串作为键。之后会讲到的分布式缓存中,则要求值必须是 byte[] 类型。
除此之外,还可以通过 GetOrCreate 或 GetOrCreateAsync 将获取和添加的操作结合。
  1. public class CacheService : ICacheService
  2. {
  3.         public const string CacheKey = "CacheTime";
  4.         private readonly IMemoryCache _cache;
  5.         public CacheService(IMemoryCache memoryCache)
  6.         {
  7.                 _cache = memoryCache;
  8.         }
  9.         public void PrintDateTimeNow()
  10.         {
  11.                 //var time = DateTime.Now;
  12.                 //if(!_cache.TryGetValue(CacheKey, out DateTime cacheValue))
  13.                 //{
  14.                 //    cacheValue = time;
  15.                 //    _cache.Set(CacheKey, cacheValue);
  16.                 //}
  17.                 //time = cacheValue;
  18.                 var time = _cache.GetOrCreate(CacheKey, cacheEntry =>
  19.                 {
  20.                         return DateTime.Now;
  21.                 });
  22.                 Console.WriteLine("缓存时间:" + time.ToString("yyyy-MM-dd HH:mm:ss"));
  23.                 Console.WriteLine("当前时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  24.         }
  25. }
复制代码

除了 TryGetValue 方法外,如果你确定缓存中一定存在相应的数据,还可以通过 Get 方法获取数据,Get 方法支持泛型,可以直接进行类型转换,但是如果缓存中不存在该缓存项,则会返回对应类型的默认值。
  1. var timeCache = _cache.Get<DateTime>(CacheKey);![image]
复制代码
2.3 缓存过期设置

一般情况下,我们会对缓存数据设置过期时间,一个是为将一些长期未被访问的缓存条目移除,避免缓存过度增长,一方面是为了更新数据,避免长时间的数据副本和源数据不一致。
.NET Core 下内存缓存的过期时间设置可以通过以下的方式:
(1) 通过 Set 方法设置
  1. var time = DateTime.Now;
  2. if (!_cache.TryGetValue(CacheKey, out DateTime cacheValue))
  3. {
  4.         cacheValue = time;
  5.         // 设置绝对过期时间
  6.         // 两种实现的功能是一样的,只是时间设置的方式不同而已
  7.         // 传入的是 AbsoluteExpirationRelativeToNow, 相对当前的绝对过期时间,传入时间差,会根据当前时间算出绝对过期时间
  8.         _cache.Set(CacheKey, cacheValue, TimeSpan.FromSeconds(2));
  9.         // 传入的是 AbsoluteExpiration,绝对过期时间,传入一个DateTimeOffset对象,需要明确的指定具体的时间
  10.         // _cache.Set(CacheKey, cacheValue, DateTimeOffset.Now.AddSeconds(2));
  11. }
  12. time = cacheValue;
复制代码
调整一下入口文件的代码,如下:
  1. var service = host.Services.GetRequiredService<ICacheService>();
  2. service.PrintDateTimeNow();
  3. Task.Delay(TimeSpan.FromSeconds(1)).Wait();
  4. service.PrintDateTimeNow();
  5. Task.Delay(TimeSpan.FromSeconds(2)).Wait();
  6. service.PrintDateTimeNow();
复制代码
可以看到,在第二次输出的时候,缓存没过期,时间是不变的,第三次的时候缓存过期了,时间改变了。

对于缓存过期时间的设置,除了绝对过期时间,还有缓动过期时间。滑动过期时间是指,如果在规定的过期时间内缓存有被再一次调用,过期时间就会重新更新,从头开始计算,每次被调用都会重新开始。
Set 方法没有直接的参数设置滑动过期时间,只能通过 MemoryCacheEntryOptions 对象设置,当然相对过期时间等其他配置也可以通过该对象设置。
  1. var memoryCacheEntryOption = new MemoryCacheEntryOptions();
  2. // 滑动过期时间是一个相对时间
  3. memoryCacheEntryOption.SlidingExpiration = TimeSpan.FromSeconds(3);
  4. _cache.Set(CacheKey, cacheValue, memoryCacheEntryOption);
复制代码

可以看到,缓存时间一直没有变,因为虽然三次输出时间加起来超过了三秒,但是三次输出之间的间隔都没有超过3秒,而每调用一次缓存都会刷新超时时间,所以缓存一直没有过期。
(2) 通过 CreateEntry 方法设置
  1. var time = DateTime.Now;
  2. if (!_cache.TryGetValue(CacheKey, out DateTime cacheValue))
  3. {
  4.         cacheValue = time;
  5.         var entry = _cache.CreateEntry(CacheKey);
  6.         // 设置绝对过期时间
  7.         // 两种实现的功能是一样的,只是时间设置的方式不同而已
  8.         // 传入的是 AbsoluteExpirationRelativeToNow, 相对当前的绝对过期时间,传入时间差,会根据当前时间算出绝对过期时间
  9.         entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(2);
  10.         // 传入的是 AbsoluteExpiration,绝对过期时间,传入一个DateTimeOffset对象,需要明确的指定具体的时间
  11.         entry.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(2);
  12.         entry.Value = cacheValue;
  13. }
  14. time = cacheValue;
复制代码
(3) 通过  GetOrCreate 或 GetOrCreateAsync 方法设置
  1. var time = _cache.GetOrCreate(CacheKey, cacheEntry =>
  2. {
  3.         // 两种实现的功能是一样的,只是时间设置的方式不同而已
  4.         // 相对当前的绝对过期时间,传入时间差,会根据当前时间算出绝对过期时间
  5.         cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3);
  6.         // 绝对过期时间,传入一个DateTimeOffset对象,需要明确的指定具体的时间
  7.         // cacheEntry.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(2);
  8.         // 滑动过期时间是一个相对时间
  9.         cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
  10.         return DateTime.Now;
  11. });
复制代码
GetOrCreateGetOrCreateAsync 方法的回调参数其实就是实现了 ICacheEntry 接口的对象。
这里同时设置了绝对过期时间和滑动过期时间,对于一个缓存项,仅具有滑动过期时间的缓存项集有永不过期的风险。 如果在滑动过期间隔内重复访问缓存项,则该项永远不会过期。 将滑动过期与绝对过期相结合,以确保项目过期。 绝对过期时间设置一个上限,即在滑动过期间隔内未请求该项时,仍允许该项提前过期的时间。 如果经过了可调到期间隔或绝对到期时间,则会从缓存中逐出项。
可以看到输出结果如下:

第三次调用的时候,时间改变了,这是因为输入滑动过期时间一直在更新,但是绝对过期时间超过了,所以缓存失效了。
这一篇就先到这里,后面还有内容,但是考虑到如果全塞在一篇里面的话,这篇文章就太长了,大家的阅读体验会不大好,所以就拆成两篇了。


参考文章:
ASP.NET Core 中的内存中缓存


ASP.NET Core 系列:
目录:ASP.NET Core 系列总结
上一篇:ASP.NET Core - 选项系统之源码介绍

来源:https://www.cnblogs.com/wewant/archive/2023/04/03/17114036.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具