服务器端响应缓存、内存缓存、分布式缓存
服务器端响应缓存、内存缓存、分布式缓存
Asp.Net Core服务器端响应缓存
浏览器缓存只能作用于自己,服务器端响应缓存可以作用于所有向服务器发起的请求
1:如果ASP.NET Core中安装了“响应缓存中间件” ,那么ASP.NET Core不仅会继续根据[ResponseCache]设置来生成cache-control响应报文头来设置客户端缓存,而且服务器端也会按照[ResponseCache]的设置来对响应进行服务器端缓存。和客户端端缓存的区别?来自多个不同客户端的相同请求。
2:“响应缓存中间件”的好处:对于来自不同客户端的相同请求或者不支持客户端缓存的客户端,能降低服务器端的压力。
3:用法:app.MapControllers()之前加上app.UseResponseCaching()。请确保app.UseCors()写到app.UseResponseCaching()之前。默认不启用
4:请求头如果带有 send control:no-cache 服务器缓存会禁用(很容易无效 鸡肋)无法解决恶意请求给服务器带来的压力。
5:服务器端响应缓存还有很多限制,包括但不限于:响应状态码为200的GET或者HEAD响应才可能被缓存;报文头中不能含有Authorization、Set-Cookie等。
内存缓存
1:把缓存数据放到应用程序的内存。内存缓存中保存的是一系列的键值对,就像Dictionary类型一样。
2:内存缓存的数据保存在当前运行的网站程序的内存中,是和进程相关的。因为在Web服务器中,多个不同网站是运行在不同的进程中的,因此不同网站的内存缓存是不会互相干扰的,而且网站重启后,内存缓存中的所有数据也就都被清空了。
如何使用
Program:
builder.Services.AddMemoryCache();//注入对应的服务
测试Controller
//注入要使用的服务:注入IMemoryCache接口,查看接口的方法:TryGetValue、Remove、Set、GetOrCreate、GetOrCreateAsync
private readonly ILogger<Test1Controller> logger;
private readonly MyDbContext dbCtx;
private readonly IMemoryCache memCache;
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;
}
缓存的过期时间策略:
1:上面的例子中的缓存不会过期,除非重启服务器。
2:解决方法:在数据改变(非查询操作)的时候调用Remove或者Set来删除或者修改缓存(优点:及时);过期时间(只要过期时间比较短,缓存数据不一致的情况也不会持续很长时间。)
3:两种过期时间策略:绝对过期时间、滑动过期时间。它们分别是什么?
绝对过期时间
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执行结束");
总结:
(1):绝对过期时间 是指设置一段时间,超过这个时间,缓存过期例如设置三秒那么这个缓存三秒后就会失效,再请求则是从数据库获取数据。
(2):滑动过期时间:设置三秒钟,如果三秒内请求了,这个缓存则不会失效,并且续命三秒,然后三秒后再请求,此时缓存已经失效,则是从数据库获取数据。(类似奶茶店做活动,十秒钟喝完一杯,可以续杯,可以无限续杯,但是得在这个规定的时间喝完)
(3):混合使用,则是在绝对过期时间之前,可以续命,(类似奶茶店做活动,十秒钟喝完一杯,可以续杯,但是不可以无限续杯,最多只能十分钟内)
(4):一般是使用绝对过期时间方式或者混合使用,因为第二种方式如果数据一直被访问,缓存不会失效
任何事物都有两面性:享受了缓存带来的好处,必然这个技术也有他的缺点:
(1)缓存穿透问题
string cacheKey = "Book" + id;//缓存键
Book? b = memCache.Get<Book?>(cacheKey);
if(b==null)//如果缓存中没有数据
{
//查询数据库,然后写入缓存
b = await dbCtx.Books.FindAsync(id);
memCache.Set(cacheKey, b);
}
上述代码正常逻辑没什么问题,但是如果是恶意请求,那么会一直访问一个不存在的id,那么每次都是直接访问数据库,这个缓存形同虚设。所以当查询这个不存在的id的时候,把它的结果null缓存起来 {key:id,value:null},下次再访问则是从缓存中获取数据。避免了缓存穿透。之前的代码使用的GetOrCreateAsync()方法,默认解决了缓存穿透,所以默认推荐大家使用这个方法
logger.LogInformation("开始执行Demo5");
string cacheKey = "Book" + id;
var book = await memCache.GetOrCreateAsync(cacheKey, async (e) => {
var b = await dbCtx.Books.FindAsync(id);
logger.LogInformation("数据库查询:{0}", b == null ? "为空" : "不为空");
return b;
});
logger.LogInformation("Demo5执行结束:{0}", book == null ? "为空" : "不为空");
return book;
总结:
1:解决方法:把“查不到”也当成一个数据放入缓存。
2:我们用GetOrCreateAsync方法即可,因为它会把null值也当成合法的缓存值。
(2)缓存雪崩
1:缓存项集中过期引起缓存雪崩。
2:解决方法:在基础过期时间之上,再加一个随机的过期时间
缓存的相关问题面试常问热点redis参考:https://blog.csdn.net/qq_41071876/article/details/120076924
分布式缓存
1、常用的分布式缓存服务器有Redis、Memcached等。
2、.NET Core中提供了统一的分布式缓存服务器的操作接口IDistributedCache,用法和内存缓存类似。
3、分布式缓存和内存缓存的区别:缓存值的类型为byte[],需要我们进行类型转换,也提供了一些按照string类型存取缓存值的扩展方法。
4、用SQLServer做缓存性能并不好。
5、Memcached是缓存专用,性能非常高,但是集群、高可用等方面比较弱,而且有“缓存键的最大长度为250字节”等限制。可以安装EnyimMemcachedCore这个第三方NuGet包。
6、Redis不局限于缓存,Redis做缓存服务器比Memcached性能稍差,但是Redis的高可用、集群等方面非常强大,适合在数据量大、高可用性等场合使用
1、NuGet安装Microsoft.Extensions.Caching.StackExchangeRedis
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "demo1_";//避免混乱,让key 具有识别性
});
如何使用:具体的方法看IDistributedCache接口
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);
}
return s;
}
注意事项:
IQueryable、IEnumerable等类型可能存在着延迟加载的问题,如果把这两种类型的变量指向的对象保存到缓存中,在我们把它们取出来再去执行的时候,如果它们延迟加载时候需要的对象已经被释放的话,就会执行失败。因此缓存禁止这两种类型。
本文内容大部分都为杨中科老师《ASP.NET Core技术内幕与项目实战》一书中内容,此文只是做学习记录,如有侵权,联系立马删除。