4.内存缓存
- 是什么?
除了响应缓存中间件这样自动化的服务器缓存机制之外,ASP.NET Core还提供了允许开发人员手动进行缓存管理的机制,这就是内存缓存!
内存缓存就是一种把缓存数据放到应用程序内存中的机制,这种缓存是在当前运行的程序内存之中,是和进程相关的。在一个Web服务器中,多个不同的程序是运行在不同的进程中的,所以不同的程序之间的内存缓存是不会互相干扰的,也就是相互独立的。
解释完内存缓存概念,来看下ASP.NET Core是如何使用内存缓存服务的
- ASP.NET Core MVC 项目,框架会自动注入内存缓存服务
- ASP.NET Core Web API 等没有自动注入内存缓存服务的项目,需要在Program.cs文件中的builder.Build之前添加
builder.Services.AddMemoryCache
来把内存缓存相关服务注册到依赖注入容器中
- 内存缓存的使用
使用内存缓存时,主要使用IMemoryCache接口,这个接口的方法如下
方法 | 说明 |
---|---|
bool TryGetValue(object key object value) | 尝试获取缓存键为key的缓存值,用value参数获取缓存值。如果缓存中没有缓存键为key的缓存值,方法返回true,否则返回false |
void Remove(object key) | 删除缓存键为key的缓存内容 |
TItem Set<TItem>(object key,TItem value) | 设置缓存键为key的缓存值为value |
TItem GetOrCreate<TItem>(object key,Func<ICacheEntry,TItem> factory) | 尝试获取缓存键为key的缓存值,方法的返回值为获取的缓存值。如果缓存中没有缓存键为key的缓存值,则调用factory指向的回调从数据源获取数据,把获取的数据作为缓存值保存到缓存中,并且把获取的数据作为方法的返回值。 |
TItem GetOrCreateAsync<TItem>(object key,Func<ICacheEntry,Task<TItem>> factory) | 异步版本的GetOrCache方法 |
这里是一运行结果
操作如下
-
先在
Program.cs
中开启内存缓存服务,记得在builder.Build()
builder.Services.AddMemoryCache();
-
创建控制器
TestController
,注入DbConext, IMemoryCache-
[Route("api/[controller]/[action]")] [ApiController] public class TestController : ControllerBase { private readonly IMemoryCache memoryCahe; private readonly ILogger<TestController> logger; public TestController(IMemoryCache memoryCahe, ILogger<TestController> logger, IMemoryCacheHelper memoryCacheHelper, IDistributedCache distributedCache) { this.memoryCahe = memoryCahe; this.logger = logger; this.memoryCacheHelper = memoryCacheHelper; this.distributedCache = distributedCache; } [HttpGet] public async Task<ActionResult<Book>> GetBookById1(long id) { Console.WriteLine($"开始执行GetBookById,id ={id}"); logger.LogDebug($"开始执行GetBookById,id ={id}"); // GetOrCreateAsync.二合一:1)从缓存取数据2)从数据源取数据,并返回给调用者及保存到缓存 Book? b = await memoryCahe.GetOrCreateAsync("Book" + id, async (e) => { Console.WriteLine(($"缓存中没有找到,到数据库中查查,id={id}")); logger.LogDebug($"缓存中没有找到,到数据库中查查,id={id}"); Book? book = await MyDbContext.GetByIdAsync(id); Console.WriteLine(($"从数据库查询的结果是" + (book == null ? "null" : book))); return book; }); Console.WriteLine($"GetOrCreateAsync结果{b}"); logger.LogDebug($"GetOrCreateAsync结果{b}"); if (b == null) { return NotFound($"找不到id={id}的书"); } else { return Ok(b); } } }
-
运行结果表明,由于我们第一次访问这个路径,缓存还没有对应的数据,因此会先从数据库中查询出对应的数据,再把数据返回给调用者
再次执行时,缓存中已经有了数据,就不会从数据库中获取数据了
5.过期时间
- 定义理解
上面的缓存的数据正常情况下是不会过期的,也就是执行了一次数据库查询后,就已将数据存进去了,这种情况有个很大的缺点,当我们更新数据时,缓存中的数据会和数据库中的数据不一致,这种问题需要解决。
我们的解决方案是设置合理的过期时间来及时更新缓存,当过期时间到来后,缓存中数据会自动被清除,下次请求时就会重新从数据库中查询,然后再次存入数据库中,达到更新缓存的效果
过期策略有两种:绝对过期时间,滑动过期时间
绝对过期时间:自设置缓存之后的指定时间后,缓存会被清除
滑动过期时间:自设置缓存之后的指定过期时间后,如果对应的缓存没有被访问,缓存则被清除,而如果在缓存过期时间内,有任何访问缓存时,则缓存的过期时间会自动续期
- 绝对过期时间的使用
// GetOrCreateAsync.二合一:1)从缓存取数据2)从数据源取数据,并返回给调用者及保存到缓存
Book? b = await memoryCahe.GetOrCreateAsync("Book" + id, async (e) =>
{
Console.WriteLine(($"缓存中没有找到,到数据库中查查,id={id}"));
logger.LogDebug($"缓存中没有找到,到数据库中查查,id={id}");
//绝对过期时间
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);//缓存有效期十秒
Book? book = await MyDbContext.GetByIdAsync(id);
Console.WriteLine(($"从数据库查询的结果是"+(book==null?"null":book)));
return book;
});
上面是执行了一次缓存后结果,这是设置了10S的过期时间,在此期间,怎么访问都是缓存
但是在10S之后
- 滑动过期时间的使用
e.SlidingExpiration = TimeSpan.FromSeconds(10);
我们只要在10S内访问一次,就会续期10S,等过了再次访问后
综上,设置绝对过期时间的缓存会在指定时间后过期,这样即使发生缓存数据不一致的情况,只要我们设置的绝对过期时间比较短,这种不一致的情况不会持续很长的时间,但是如果缓存数据量非常大的话,这些数据会占用很大的内存
而使用滑动过期时间策略,我们可以保证经常访问的数据长期保存在缓存中,但是如果一个缓存项一直被访问,就会一直续期,当我们更新数据时,就会产生缓存与数据库不一致问题,而因为设置了滑动过期策略,且一直在被访问,缓存会一直得不到更新的
- 混合使用过期时间策略
针对这些情况,我们可以结合绝对过期时间和滑动过期时间进行使用,比如,设置一个绝对过期时间且比滑动过期时间长,这样缓存会在绝对过期时间内随着访问被滑动续期,但是,过了绝对过期时间,缓存项就会被删除
//滑动过期时间和绝对过期时间混用
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
e.SlidingExpiration = TimeSpan.FromSeconds(10);
这里可以自行测试…