内存缓存就是一种把缓存数据放到应用程序内存中的机制,内存缓存中保存的是一系列的键值对,就像Dictionary类型一样,每个不同的缓存内容有不同的“缓存键”,每个缓存键对应一个“缓存值”。
我们可以设置缓存的键值对,也可以根据缓存键取出缓存中保存的缓存值。
内存缓存的数据保存在当前运行网站程序的内存中,是和进程相关的。
对于ASP.NET Core MVC项目,框架会自动地注入内存缓存服务,而对于ASP.NET Core WebApi等没有自动注入内存缓存服务的项目,我们需要手动将内存缓存相关服务注册到依赖注入容器中。
下面以ASP.NET Core WebApi项目为例,演示内存缓存的使用方法。
1.创建ASP.NET Core WebApi项目
2.项目添加nuget包的引用
Microsoft.Extensions.Caching.Memory
3.在Startup.cs中对MemoryCache服务注册到依赖注入容器中
4. 创建控制器CacheTestController
在构造方法中,对MemoryCache服务进行注入
控制器中新建两个action:
一个是常规的没有缓存功能,直接获取数据并返回的方法(Test);
另一个是有缓存功能的方法(CacheTest),如果要查询的数据在缓存中存在,则直接返回,如果不存在则将数据查询后存入缓存并返回;
控制器完成代码:
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.Extensions.Caching.Memory;
- using System;
-
- namespace NetCoreWebApi.Controllers
- {
- [Route("api/[controller]/[action]")]
- [ApiController]
- public class CacheTestController : ControllerBase
- {
- private readonly IMemoryCache _memoryCache;
- public CacheTestController(IMemoryCache memoryCache)
- {
- _memoryCache = memoryCache;
- }
-
-
-
- ///
- /// 没有缓存功能的获取数据方法
- ///
- ///
- ///
- [HttpGet]
- public IActionResult Test(int bookId)
- {
- Console.WriteLine();
- Console.WriteLine($"开始执行Test方法,bookId={bookId}");
- return DataClass.GetData(bookId);
- }
-
-
- ///
- /// 有缓存功能的获取数据方法
- ///
- ///
- ///
- [HttpGet]
- public IActionResult CacheTest(int bookId)
- {
- Console.WriteLine();
- Console.WriteLine($"开始执行CacheTest方法,bookId={bookId}");
- var items = _memoryCache.GetOrCreate("Book" + bookId, (e) =>
- {
- return DataClass.GetData(bookId);
- });
-
- Console.WriteLine("把数据返回");
- return items;
- }
-
-
- }
- }
DataClass类,模拟从数据库查询数据的操作:
- using Microsoft.AspNetCore.Mvc;
- using System;
-
- namespace NetCoreWebApi
- {
- public class DataClass
- {
-
- public static JsonResult GetData(int bookId)
- {
- Console.WriteLine("数据库被访问");
-
- //模拟从数据库查询数据
- return new JsonResult(new
- {
- bookId = bookId,
- bookName = BookName(bookId)
- });
- }
-
- public static string BookName(int bookId)
- {
- switch (bookId)
- {
- case 1:
- return "书籍1";
- case 2:
- return "书籍2";
- case 3:
- return "书籍3";
- default:
- return "没有查询到书籍名称";
- }
- }
- }
- }
5.在页面对webapi接口进行调用
调用没有缓存的方法,入参为1,调用两次,两次都查询了数据库。
调用带有缓存的方法,入参为1,调用两次,只有第一次查询数据库,后面再调用就不会查询数据库,因为缓存中有值,直接从缓存把数据返回。
这时候如果把入参改为2,又会去数据库中查询,因为缓存中只有1的记录,没有2的记录。
根据上面的方式,设置了缓存后,数据是不会自动过期的,除非重启服务器。
但是在实际的应用中,数据是可能会被改变的。那么就有两种解决方法:
1.在数据改变时,调用Remove或者Set来删除或者修改缓存(优点:及时)。
2.设置过期时间,数据在一段时间内是被缓存的,到期时数据会失效,再次访问,数据又会被缓存起来,也可以保证数据的时效性。
下面介绍方法2,设置数据的缓存过期时间。
1.绝对过期时间
ICacheEntry的AbsoluteExpirationRelativeToNow属性,用来设置缓存项的绝对过期时间。
调用结果:
绝对过期就是在一段时间内缓存数据一直有效,过来这个时间段,缓存就失效。
2.滑动过期时间
只要在缓存没过期的时候被访问,缓存有效期自动更新为设置的时间长度。
例如,缓存的滑动过期时间为10秒钟,在第9秒时被访问,那么缓存的有效期又更新为10秒,只要缓存一直被访问,那就永不过期;超过10秒仍未被访问,那么缓存就失效了。
ICacheEntry的SlidingExpiration属性,用来设置缓存的滑动过期时间。
调用结果:
缓存穿透问题指的是访问的资源不存在,数据为null,导致所有访问都会去查询数据库,从而增加数据库压力的问题。
下面这种写法会引起缓存穿透:
因为当数据库中不存在查询的数据时,变量b为null,再次访问,又会去访问数据库 。
使用GetOrCreate的写法对数据进行缓存,不会导致缓存穿透,因为查询结果虽然为null,但也会被放入缓存中。
缓存项集中过期,引起请求在一段时间内同时访问数据库,导致数据库压力增大,这样的现象称为缓存雪崩。
解决缓存雪崩问题,可以在缓存的过期时间之上,增加一个随机的过期时间,避免在同一时间内大量缓存项同时失效。