7.4缓存

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 缓存是一个用来保存数据的区域,从缓存中读取数据要比从数据源读取数据的速度快很多。如果可以从缓存中获取要获取的数据则称之为“缓存命中”,多次请求命中的请求占全部请求的百分比叫做“命中率”,如果数据源中的数据保存到了缓存后,发生了变化则称之为“缓存数据不一致”

7.4缓存

缓存是一个用来保存数据的区域,从缓存中读取数据要比从数据源读取数据的速度快很多。如果可以从缓存中获取要获取的数据则称之为“缓存命中”,多次请求命中的请求占全部请求的百分比叫做“命中率”,如果数据源中的数据保存到了缓存后,发生了变化则称之为“缓存数据不一致”。

客户端缓存

RFC 7234是HTTP中对缓存进行控制的规范,由cache-control相应报头来控制,如服务器给浏览器的响应头文件中cache-control的值为max-age=60,表示服务器指示浏览器缓存这个响应内容60s。在ASP.Net Core中,只需要给要进行缓存控制的控制器的操作方法添加ResponseCacheAttribute

[HttpGet]

[ResponseCache(Duration=60)]//缓存60s

publicDateTimeNow()

{

   returnDateTime.Now;

}

默认情况下,[ResponseCache]是通过cache-control响应报文头来控制浏览器,如果浏览器不支持缓存这个设置不会生效。

服务器响应缓存*

安装了“响应缓存中间件”,ASP.NET Core不仅会根据[ResponseCache]来设置响应报文,还会在服务器对响应进行服务端缓存。当在特定的时间内访问服务器相同的请求地址,服务器不会执行操作方法,而是将缓存的数据直接返回给客户端。

使用方法:

  1. 在操作方法上面使用[ResponseCache]
  2. 在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接口

方法 说明
bool TryGetValue(object key, out object value) 尝试获取键为key的缓存值,如果有,则返回true否则返回false
void Remove(object key) 删除键为key的缓存值
T Set<T>(object key,T value) 设置缓存键key的缓存值为value
T GetOrCreate<T>(object key,Func<ICacheEntry,T> factory) 获取键为key的缓存值,如果缓存中没有,则调用factory指向的函数从数据源获取数据,创建缓存并返回值
Task<T> GetOrCreateAsync<T>(object key,Func<ICacheEntry,Task<T>> factory) 异步的GetOrCreate方法,ICacheEntry可以对缓存项进行详细的设置,比如缓存项被清除的回调、缓存项的优先级

[Route("[controller]/[action]")]

[ApiController]

publicclassTest1Controller : ControllerBase

{

   privatereadonlyILogger<Test1Controller>logger;

   privatereadonlyMyDbContextdbCtx;

   privatereadonlyIMemoryCachememCache;//使用依赖注入的形式使用IMemoryCache

   publicTest1Controller(MyDbContextdbCtx, IMemoryCachememCache, ILogger<Test1Controller>logger)

   {

       this.dbCtx=dbCtx;

       this.memCache=memCache;

       this.logger=logger;

   }

   [HttpGet]

   publicasyncTask<Book[]>GetBooks()

   {

       logger.LogInformation("开始执行GetBooks");

       varitems=awaitmemCache.GetOrCreateAsync("AllBooks", async (e) =>

       {

           logger.LogInformation("从数据库中读取数据");

           returnawaitdbCtx.Books.ToArrayAsync();

       });

       logger.LogInformation("把数据返回给调用者");

       returnitems;

   }

}

过期策略

  • 绝对过期时间

自设置缓存之后的指定时间后,缓存被删除

  • 滑动过期时间

自设置缓存之后的指定时间后,如果对缓存数据没有访问,则删除,如果有访问,则缓存项会以最后一次的时间为准自动续期

//绝对过期时间

logger.LogInformation("开始执行Demo1:"+DateTime.Now);

varitems=awaitmemCache.GetOrCreateAsync("AllBooks", async (e) => {

   e.AbsoluteExpirationRelativeToNow=TimeSpan.FromSeconds(10);//设置绝对过期时间

   logger.LogInformation("从数据库中读取数据");

   returnawaitdbCtx.Books.ToArrayAsync();

});

logger.LogInformation("Demo1执行结束");

//滑动过期时间

logger.LogInformation("开始执行Demo2:"+DateTime.Now);

varitems=awaitmemCache.GetOrCreateAsync("AllBooks2", async (e) => {

   e.SlidingExpiration=TimeSpan.FromSeconds(10);//活动过期时间

   logger.LogInformation("Demo2从数据库中读取数据");

   returnawaitdbCtx.Books.ToArrayAsync();

});

logger.LogInformation("Demo2执行结束");

  • 混合使用过期策略

一般设置绝对过期时间比滑动时间长,绝对时间到期后,无论滑动时间有没有到期,都会删除缓存

//混合使用过期时间策略

logger.LogInformation("开始执行Demo3:"+DateTime.Now);

varitems=awaitmemCache.GetOrCreateAsync("AllBooks3", async (e) => {

   e.SlidingExpiration=TimeSpan.FromSeconds(10);//滑动时间

   e.AbsoluteExpirationRelativeToNow=TimeSpan.FromSeconds(30);//绝对时间

   logger.LogInformation("Demo3从数据库中读取数据");

   returnawaitdbCtx.Books.ToArrayAsync();

});

logger.LogInformation("Demo3执行结束");

混合使用过期策略可以实现不经常被访问的数据不会长时间占内存,而频繁被访问的数据会避免数据不一致的问题

所有的缓存都会出现数据不一致的情况,对应不允许数据不一致的情况,可以直接不用缓存。

缓存穿透

使用IMemoryCache中的Get方法,会根据key来查找缓存项,如果找不到则返回Null

stringcacheKey="Book"+id;//缓存键

Book?b=memCache.Get<Book?>(cacheKey);

if (b==null)//如果缓存中没有数据 如果有恶意者使用不存在的某个Id大量访问,则会不断的查询数据库,导致服务器崩溃,这叫做缓存穿透

{

   //查询数据库,然后写入缓存

   b=awaitdbCtx.Books.FindAsync(id);

   memCache.Set(cacheKey, b);

}

缓存穿透的问题是由于将查不到的数据用Null来表示,如果把“查不到”也当数据放到缓存中,即{key:不存在的某个Id,value:null}。日常开发中使用GetOrCreateAsync方法就可以避免缓存穿透,它将null也作为了合法的缓存值。

logger.LogInformation("开始执行Demo5");

stringcacheKey="Book"+id;

varbook=awaitmemCache.GetOrCreateAsync(cacheKey, async (e) => {

   varb=awaitdbCtx.Books.FindAsync(id);

   logger.LogInformation("数据库查询:{0}", b==null?"为空" : "不为空");

   returnb;

});

logger.LogInformation("Demo5执行结束:{0}", book==null?"为空" : "不为空");

returnbook;

缓存雪崩

一般会是在网站启动的时候将大量的数据放到缓存以提高响应速度,如果这些缓存设定了相同的过期时间,则会同时过期,如果有访问则会导致大量的数据库访问,这样会同时的访问会将数据库服务器压垮。解决这个问题的办法是在基础过期的时间上增加一个随机的过期时间,这样就不会集中到同一时间了。

注意:设置缓存的时候,IQueryable、IEnumerable等类型可能存在延迟加载,当取出这种类型的变量时去执行时,如果他们所需要的对象已经被释放则会执行失败。因此最好将这两种对象转换为数组或者List类型再放到缓存中。

分布式缓存

在分布式系统中,将缓存数据放到专门的缓存服务器中,所有的Web都通过缓存服务器来进行写入和读取。

.net core中使用IDistributedCache接口来进行操作,分布式缓存中提供了DistributedCacheEntryOptions来配置过期时间。不同类型的缓存服务器支持的缓存键和缓存值不相同,所以IDistributedCache统一将string作为key的类型,将byte[]作为值类型。

推荐使用Redis数据库作为缓存服务器,微软也提供了Redis作为缓存服务器的Nuget包Microsoft.Extensions.Caching.StackExchangeRedis。

  1. 在Program.cs的builder.Build之前添加代码进行注册Redis缓存

builder.Services.AddStackExchangeRedisCache(options=>

{

   options.Configuration="localhost";//redis服务器的连接配置

   options.InstanceName="yzk_";//其他程序也许也在使用redis服务器,为避免冲突,增加yzk_前缀

});

  1. 读写redis中的缓存数据

publicclassTest1Controller : ControllerBase

{

   privatereadonlyIDistributedCachedistCache;

   publicTest1Controller(IDistributedCachedistCache)

   {

       this.distCache=distCache;

   }

   [HttpGet]

   publicstringNow()

   {

       strings=distCache.GetString("Now");

       if (s==null)

       {

           s=DateTime.Now.ToString();

           varopt=newDistributedCacheEntryOptions();

           opt.AbsoluteExpirationRelativeToNow=TimeSpan.FromSeconds(30);

           distCache.SetString("Now", s, opt);//将s存放在yzh_Now键对应的值中

       }

       returns;

   }

}

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
7月前
|
缓存
你了解缓存吗?
你了解缓存吗?
你了解缓存吗?
|
3月前
|
存储 缓存 NoSQL
|
6月前
|
存储 缓存 对象存储
合理地处理不需要的缓存
【6月更文挑战第8天】本文介绍了管理缓存数据过期的重要性,以避免内存浪费和过时信息的使用。缓存系统通常允许设置默认过期策略或为每个对象指定绝对或滑动过期时间。缓存服务常使用LRU策略进行逐出,但过度使用可能导致内存超出异常。
67 10
合理地处理不需要的缓存
|
6月前
|
存储 缓存 NoSQL
在应用中使用缓存服务
【6月更文挑战第24天】本文介绍redis缓存的基本知识和使用。Redis超越简单的键值存储,Redis查询直接针对键,不支持复杂查询,适合特定场景的高性能缓存。用于减少数据库交互,优化性能。并提供练习源码查阅。
95 1
|
存储 缓存 API
缓存 #23
缓存 #23
53 0
|
存储 缓存 NoSQL
聊聊缓存
拿破仑说:胜利属于坚持到最后的人。 而正巧,咱们今天就是要聊一个,关于怎么让系统在狂轰乱炸甚至泰山压顶的情况下,都屹立不倒并坚持到最后的话题:缓存。
184 0
|
存储 缓存 前端开发
缓存的认识
缓存是架构设计中一个重要的手段。缓存的主要特点是技术比较简单,同时对性能提升的效果又很显著,所以缓存在很多业务场景中被使用到。
136 0
|
存储 缓存 算法
聊聊缓存那些事
说到缓存,作为技术同学想必大家都不会陌生,平常工作中或多或少也用到过。但是要结构化的说清楚缓存到底是什么,怎么用,用了有问题怎么解,也不是一件简单的事。所以这篇文章也是站在服务端研发的视角,对自己过去经验的一些总结,希望对大家有哪怕一丁点的帮助,也就值得了。 本篇文章计划分为两个章节来写: ● 缓存基础篇:讲一下缓存的基本原理、特性等。 ● 缓存进阶篇:讲一下缓存的实战场景,疑难问题的解决方案等。
167 0
|
存储 缓存 运维
常用缓存技巧
在项目中,大家经常会遇到处理高并发的情况,缓存是应对高并发的有效手段之一。这篇文章简单介绍一下常用的缓存手段。
|
存储 缓存 JSON
缓存的注意点
设计缓存的考虑