服务器端响应缓存、内存缓存、分布式缓存

服务器端响应缓存、内存缓存、分布式缓存

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技术内幕与项目实战》一书中内容,此文只是做学习记录,如有侵权,联系立马删除。

posted @ 2022-08-04 00:31  忽如一夜娇妹来  阅读(268)  评论(0编辑  收藏  举报