一、缓存
缓存指在中间层中存储数据的行为,该行为可使后续数据检索更快。 从概念上讲,缓存是一种性能优化策略和设计考虑因素。 缓存可以显著提高应用性能,方法是提高不常更改(或检索成本高)的数据的就绪性。
二、RFC9111
在最新的缓存控制规范文件RFC9111中,详细描述了浏览器缓存和服务器缓存控制的规范,其中有一个最重要的响应报文头Cache-Control
。
该报文头的设置会影响我们的缓存,包括浏览器端和服务端。
RFC911:http://www.zzvips.com/uploads/allimg/b50ttmphw2f
三、网页端缓存
在Cache-Control
中,如果设置max-age=10
,则表示告诉浏览器缓存10s,而为什么浏览器要认这个表示呢,就是上面我们说的前后端都要根据RFC标准规范去实现,就是硬件的统一插口,不然其他生成出来的就用不了。
那么在Asp.net Core 中只需要在接口上打上ResponseCacheAttribute
并设置max-age
的时间即可。
首先建一个Asp.Net Core WebAPI 项目,写一个获取学生的Get
接口。
namespace WebAPI_Cache.Controllers
{
[ApiController]
[Route("[controller]")]
public class CacheController : ControllerBase
{
public CacheController()
{
}
[HttpGet]
public ActionResult<Student> GetStudent()
{
return new Student()
{
Id = 1,
Name = "Test",
Age = Random.Shared.Next(0, 100),
};
}
}
}
namespace WebAPI_Cache.Model
{
public class Student
{
public int Id { get; set; }
public string? Name { get; set; }
public int Age { get; set; }
}
}
在接口中我返回Student
的age
为1-100的随机数。启动项目测试,短时间内两次调用返回的age
不一样
第一次age:
第二次age:
当我在接口方法打上[ResponseCache(Duration = 10)]
,再次调用接口返回的信息可以看到已经有了cache-control: public,max-age=10
的Header。
并且我在10秒内的请求,只有第一次请求过服务器,其他都是从缓存中取的,查看edge浏览器网络访问如下:
四、服务器缓存
网页端缓存是放在浏览器端的,对于单点请求会有用,但是如果是多个不同前端请求呢。这个时候我们可以将缓存放置在后端服务中,在ASP.NET Core 中配置响应缓存中间件。
在 Program.cs中,将响应缓存中间件服务 AddResponseCaching 添加到服务集合,并配置应用,如果使用 CORS 中间件时,必须在 UseResponseCaching 之前调用 UseCors。
如果header包含 Authorization,Set-Cookie 标头,也不会缓存,因为这些用户信息缓存会引起数据混乱。
然后对于我们需要服务器缓存的接口打上ResponseCache
属性,和设置浏览器缓存一样,还有其他参数可设置。我们通过两个进程来测试,一个用浏览器swagger,一个用postman,可以看到两个请求的age都是等于18的。所以可以确定服务器端确实存在缓存。
但是在用postman测试的时候记得在settings里面把Send no-cache header
勾掉,如果不去掉,发送的时候就会在请求头里面包含Cache-Control:no-cache
,这样服务端即便有缓存也不会使用缓存。
对于浏览器端相当于禁用缓存,如果禁用了缓存,发送的请求头也会带上Cache-Control:no-cache
,服务端看到no-cache 后便不会再使用缓存进行响应。
而这个约定就是RFC9111的规范,所以这个后端缓存策略比较鸡肋,如果用户禁用缓存就没用了,因此我们还可以使用内存缓存。
五、内存缓存
内存缓存基于 IMemoryCache。 IMemoryCache 表示存储在 Web 服务器内存中的缓存。
- 首先Nuget安装包
Install-Package Microsoft.Extensions.Caching.Memory
- 在Program.cs中添加依赖
builder.Services.AddMemoryCache();
- 缓存数据
我添加一个Post方法模拟id查询Student
这样我就将数据缓存到了内存,可以设置缓存的绝对过期时间,也可以设置滑动过期,稍后我们会看到过期策略的使用。
六、缓存击穿
缓存击穿是指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,或者是查询了不存在的数据,缓存里面没有,从而大量的请求打到数据库上形成数据库压力。
上面内存缓存中的写法我们可以看到,如果查询缓存等于null
就会再去查询数据(我这里只是模拟,没有去写真的数据库查询),如果这样暴力请求攻击就会有问题。
对于这个问题我们可以使用ImemoryCache
的GetOrCreate
方法,当然它还有异步方式。通过该方法传入缓存的key和func 委托方法返回值来进行查询并缓存,如果没查询到返回的null
也会存储在缓存中,防止恶意查询不存在的数据。
[HttpPost]
public ActionResult<Student> GetStudent2(int id)
{
//查询并创建缓存
var student = _memoryCache.GetOrCreate("student_" + id, t =>
{
t.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(20);
//模拟只有id=1有数据
if (id == 1)
{
return new Student()
{
Id = 1,
Name = "Test",
Age = Random.Shared.Next(0, 100),
};
}
else
{
//其他的返回空,但是空值也会缓存,比如查询 id=2,id=3 都会缓存
return null;
}
});
if (student == null)
{
return NotFound("未找到");
}
else
{
return student;
}
}
七、缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,导致所有请求都会去查数据库,而查询数据量巨大,引起数据库压力过大甚至down机。
对于雪崩情况我们对缓存的策略主要是设置过期时间,部分不重要的站点,比如新闻网站我们将绝对过期时间AbsoluteExpiration设置的久一点。
对于要一定灵活性,能在请求不频繁的时候进行失效以更新数据的,我们可以用滑动过期时间,就是如果频繁请求就一值滑动过期时间。
当然为了避免滑动时间一直不过期,还可以两种方式混合使用。上面的例子,我们设置绝对过期时间是20秒,我们将滑动过期设置5秒,在5秒内有持续访问就一直续命,直到20秒绝对过期。
那么如果没人访问,在5秒后就过期了,这样数据下次访问也能及时查询最新数据。
八、分布式缓存
有了上面的缓存方案,对付一些小的简单业务系统完全够用了,但是如果你是分布式部署服务,那么像内存缓存访问的数据就是单个服务器的缓存。
你可能需要多个服务器的请求之间保持一致、在进行服务器重启和应用部署后仍然有效、不使用本地内存等情况。
这个时候我们可以使用第三方缓存,比如memecache,Redis等。Asp.Net Core 使用 IDistributedCache 接口与缓存进行交互。
- NuGet安装包
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
- 在 Program.cs 中注册 IDistributedCache 实现
Configuration: 为连接配置。
InstanceName: 为存储键前缀。
编写测试方法GetStuden3
IDistributedCache 接受字符串键并以 byte[] 数组的形式添加或检索缓存项,所以数据是以byte[]形式访问,但是扩展了一个string类型的方法可以进行使用,我这里用字符串进行操作。
以上这些就是关于asp.net core 当中使用缓存的重要点和基础使用方法,详细参数和文档可参看官方文档:ASP.NET Core 中的缓存概述