借鉴的这个教程 ASP.NET Core教程-CSDN博客
写的太全了,大部分是复制的
RFC 7234是HTTP中对缓存进行控制的规范,由cache-control相应报头来控制,如服务器给浏览器的响应头文件中cache-control的值为max-age=60,表示服务器指示浏览器缓存这个响应内容60s。在ASP.Net Core中,只需要给要进行缓存控制的控制器的操作方法添加ResponseCacheAttribute。
[HttpGet]
[ResponseCache(Duration =60)]//缓存60s
public DateTime Now()
{
return DateTime.Now;
}
默认情况下,[ResponseCache]是通过cache-control响应报文头来控制浏览器,如果浏览器不支持缓存这个设置不会生效。
服务器响应缓存*
安装了**“响应缓存中间件”**,ASP.NET Core不仅会根据[ResponseCache]来设置响应报文,还会在服务器对响应进行服务端缓存。当在特定的时间内访问服务器相同的请求地址,服务器不会执行操作方法,而是将缓存的数据直接返回给客户端。
使用方法:
在操作方法上面使用[ResponseCache]
在Program.cs文件中app.MapControllers之前加上app.UseResponseCaching
注意:如果开启了CORS跨域请求,确保app.UseCors在app.UseResponseCaching之前
像chorme浏览器,如果浏览器中设置禁用浏览器缓存,但是启用了服务器的响应缓存中间件,但服务器的缓存仍然会不起作用。这是因为禁用浏览器缓存后,浏览器会向服务器端的请求头文件中加入cache-control:no-cache,这样服务器也会禁用缓存机制。
建议:不启用“响应缓存中间件”,如果需要在客户端进行缓存,则只需要使用[ResponseCache]即可
内存缓存
内存缓存中保存的是一系列的键值对,不同的缓存内容具有不同的缓存键,每个缓存键对应一个缓存值。内存缓存保存在当前运行网站程序的内存中,和进程相关。使用内存缓存,确保在Program.cs中的builder.Build之前添加builder.Services.AddMemoryCache来讲内存缓存的相关服务注册到容器中。使用内存缓存的时候,主要使用IMemoryCache接口
[Route("[controller]/[action]")]
[ApiController]
public class Test1Controller : ControllerBase
{
private readonly ILogger<Test1Controller> logger;
private readonly MyDbContext dbCtx;
private readonly IMemoryCache memCache;//使用依赖注入的形式使用IMemoryCache
public Test1Controller(MyDbContext dbCtx, IMemoryCache memCache, ILogger<Test1Controller> logger)
{
this.dbCtx = dbCtx;
this.memCache = memCache;
this.logger = logger;
}
[HttpGet]
public async Task<Book[]> GetBooks()
{
logger.LogInformation("开始执行GetBooks");
var items = await memCache.GetOrCreateAsync("AllBooks", async (e) =>
{
logger.LogInformation("从数据库中读取数据");
return await dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("把数据返回给调用者");
return items;
}
}
过期策略
- 绝对过期时间
自设置缓存之后的指定时间后,缓存被删除
- 滑动过期时间
自设置缓存之后的指定时间后,如果对缓存数据没有访问,则删除,如果有访问,则缓存项会以最后一次的时间为准自动续期
//绝对过期时间
logger.LogInformation("开始执行Demo1:" + DateTime.Now);
var items = await memCache.GetOrCreateAsync("AllBooks", async (e) => {
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);//设置绝对过期时间
logger.LogInformation("从数据库中读取数据");
return await dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("Demo1执行结束");
//滑动过期时间
logger.LogInformation("开始执行Demo2:" + DateTime.Now);
var items = await memCache.GetOrCreateAsync("AllBooks2", async (e) => {
e.SlidingExpiration = TimeSpan.FromSeconds(10);//活动过期时间
logger.LogInformation("Demo2从数据库中读取数据");
return await dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("Demo2执行结束");
- 混合使用过期策略
一般设置绝对过期时间比滑动时间长,绝对时间到期后,无论滑动时间有没有到期,都会删除缓存
//混合使用过期时间策略
logger.LogInformation("开始执行Demo3:" + DateTime.Now);
var items = await memCache.GetOrCreateAsync("AllBooks3", async (e) => {
e.SlidingExpiration = TimeSpan.FromSeconds(10);//滑动时间
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);//绝对时间
logger.LogInformation("Demo3从数据库中读取数据");
return await dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("Demo3执行结束");
缓存穿透
使用IMemoryCache中的Get方法,会根据key来查找缓存项,如果找不到则返回Null
string cacheKey = "Book" + id;//缓存键
Book? b = memCache.Get<Book?>(cacheKey);
if (b == null)//如果缓存中没有数据 如果有恶意者使用不存在的某个Id大量访问,则会不断的查询数据库,导致服务器崩溃,这叫做缓存穿透
{
//查询数据库,然后写入缓存
b = await dbCtx.Books.FindAsync(id);
memCache.Set(cacheKey, b);
}
缓存穿透的问题是由于将查不到的数据用Null来表示,如果把“查不到”也当数据放到缓存中,即{key:不存在的某个Id,value:null}。日常开发中使用GetOrCreateAsync方法就可以避免缓存穿透,它将null也作为了合法的缓存值。
缓存雪崩
一般会是在网站启动的时候将大量的数据放到缓存以提高响应速度,如果这些缓存设定了相同的过期时间,则会同时过期,如果有访问则会导致大量的数据库访问,这样会同时的访问会将数据库服务器压垮。解决这个问题的办法是在基础过期的时间上增加一个随机的过期时间,这样就不会集中到同一时间了。
注意:设置缓存的时候,IQueryable、IEnumerable等类型可能存在延迟加载,当取出这种类型的变量时去执行时,如果他们所需要的对象已经被释放则会执行失败。因此最好将这两种对象转换为数组或者List类型再放到缓存中。
分布式缓存
在分布式系统中,将缓存数据放到专门的缓存服务器中,所有的Web都通过缓存服务器来进行写入和读取。
.net core中使用IDistributedCache接口来进行操作,分布式缓存中提供了DistributedCacheEntryOptions来配置过期时间。不同类型的缓存服务器支持的缓存键和缓存值不相同,所以IDistributedCache统一将string作为key的类型,将byte[]作为值类型。
推荐使用Redis数据库作为缓存服务器,微软也提供了Redis作为缓存服务器的Nuget包Microsoft.Extensions.Caching.StackExchangeRedis。
- 在Program.cs的builder.Build之前添加代码进行注册Redis缓存
builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost";//redis服务器的连接配置 options.InstanceName = "yzk_";//其他程序也许也在使用redis服务器,为避免冲突,增加yzk_前缀 });
2.读写redis中的缓存数据
-
public class Test1Controller : ControllerBase { private readonly IDistributedCache distCache; public Test1Controller(IDistributedCache distCache) { this.distCache = distCache; } [HttpGet] public string Now() { string s = distCache.GetString("Now"); if (s == null) { s = DateTime.Now.ToString(); var opt = new DistributedCacheEntryOptions(); opt.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30); distCache.SetString("Now", s, opt);//将s存放在yzh_Now键对应的值中 } return s; } }
【待研究】什么样的情况下用到分布式缓存最合适