.Net Core 教程 Part4 – (23)(24)(25)(26) 缓存

Part4 – (23) 程序员的性能优化万金油:缓存

缓存(Caching)是系统优化中简单又有效的工具,投入小收效大。数据库中的索引等简单有效的优化功能本质上都是缓存。

.Net Core 教程 Part4 – (23)(24)(25)(26) 缓存
数据库缓存
.Net Core 教程 Part4 – (23)(24)(25)(26) 缓存
数据库缓存

缓存的几个重要概念:

  • 缓存命中
  • 缓存命中率
  • 缓存数据不一致
.Net Core 教程 Part4 – (23)(24)(25)(26) 缓存
多级缓存

Part4 – (24) 客户端响应缓存

cache-control

  • RFC7324是HTTP协议中对缓存进行控制的规范,其中重要的是cache-control这个响应报文头。服务器如果返回cache-control:max-age=60,则表示服务器指示浏览器端“可以缓存这个响应内容60秒”。
  • 我们只要给需要进行缓存控制的控制器的操作方法添加ResponseCacheAttribute这个Attribute,ASP.NET Core会自动添加cache-control报文头。
  • 验证:编写一个返回当前时间的Action方法。分别加和不加ResponseCacheAttribute看区别。也F12看看Network。
namespace Part4_24.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        [ResponseCache(Duration = 20)]  //服务器端告诉浏览器端 20s之内的请求可以使用缓存
        [HttpGet]
        public DateTime Now()
        { 
            return DateTime.Now;    
        }
    }
}
.Net Core 教程 Part4 – (23)(24)(25)(26) 缓存
浏览器端的磁盘缓存

Part4 – (25) 服务器端响应缓存

.Net Core 教程 Part4 – (23)(24)(25)(26) 缓存
服务器端响应缓存

Response Caching Middleware

1、如果ASP.NET Core中安装了“响应缓存中间件” ,那么ASP.NET Core不仅会继续根据[ResponseCache]设置来生成cache-control响应报文头来设置客户端缓存,而且服务器端也会按照[ResponseCache]的设置来对响应进行服务器端缓存。和客户端端缓存的区别?来自多个不同客户端的相同请求。

2、“响应缓存中间件”的好处:对于来自不同客户端的相同请求或者不支持客户端缓存的客户端,能降低服务器端的压力。

3、用法:app.MapControllers()之前加上app.UseResponseCaching()。请确保app.UseCors()写到app.UseResponseCaching()之前

app.UseCors();  
app.UseResponseCaching();   //只针对服务器端响应缓存
app.MapControllers();

验证效果

1、大部分浏览器都是支持RFC7324规范的,所以不方便用来测试服务器端响应缓存。用默认忽略RFC7324规范的PostMan测试。试一下请求服务器端。

2、可以在浏览器的“开发人员工具”中禁用缓存的,但是和PostMan中不一致,为什么?“cache-control: no-cache”

3、也可以让Postman在请求报文头中加入“cache-control: no-cache”,只要在Postman的设置中开启【Send no-cache headers】

.Net Core 教程 Part4 – (23)(24)(25)(26) 缓存
浏览器端开启禁用缓存后( cache-control: no-cache ) 服务器端也不会再使用服务器端的缓存
.Net Core 教程 Part4 – (23)(24)(25)(26) 缓存
发送请求时header中带上不使用缓存的标识 cache-control: no-cache

服务器端响应缓存很鸡肋

1、无法解决恶意请求给服务器带来的压力。

2、服务器端响应缓存还有很多限制,包括但不限于:响应状态码为200的GET或者HEAD请求才可能被缓存;报文头中不能含有Authorization、Set-Cookie等。

3、不怪他,honor RFC7234. It’s a feature, not a bug.

4、怎么办?采用内存缓存、分布式缓存等。

Part4 – (26) 内存缓存

内存缓存(In-memory cache)

  • 把缓存数据放到应用程序的内存。内存缓存中保存的是一系列的键值对,就像Dictionary类型一样。
  • 内存缓存的数据保存在当前运行的网站程序的内存中,是和进程相关的。因为在Web服务器中,多个不同网站是运行在不同的进程中的,因此不同网站的内存缓存是不会互相干扰的,而且网站重启后,内存缓存中的所有数据也就都被清空了。

内存缓存使用方法

  • 启用:builder.Services.AddMemoryCache()
  • 注入IMemoryCache接口,查看接口的方法:TryGetValue、Remove、Set、GetOrCreate、GetOrCreateAsync
  • 用GetOrCreateAsync
private readonly IMemoryCache memoryCache;
private readonly ILogger<TestController> logger;

public TestController(IMemoryCache memoryCache, ILogger<TestController> logger)
{
    this.memoryCache = memoryCache;
    this.logger = logger;
}

Mock一个数据库的上下文,提供数据能力,如下:

namespace Part4_24
{
    public class MyDbContext
    {

        public static Task<Book?> GetByIdAsync(long id)
        { 
            var res= GetById(id);
            return Task.FromResult(res);    
        }

        public static Book? GetById(long id)
        {
            switch (id)
            {
                case 0:
                    return new Book(0, "Book0");
                case 1:
                    return new Book(1, "Book1");
                case 2:
                    return new Book(1, "Book2");
                default:
                    return null;
            }
        }
    }
}

使用IMemoryCache之前先来看一下里面都有哪些方法。

.Net Core 教程 Part4 – (23)(24)(25)(26) 缓存
IMemoryCache

最后比较一下使用缓存和不使用缓存的写法区别:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace Part4_24.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        private readonly IMemoryCache memoryCache;
        private readonly ILogger<TestController> logger;

        public TestController(IMemoryCache memoryCache, ILogger<TestController> logger)
        {
            this.memoryCache = memoryCache;
            this.logger = logger;
        }

        [HttpGet]
        public ActionResult<Book?> GetBookById(long id)
        {
            Book? result = MyDbContext.GetById(id);
            if (result == null)
            { 
                return NotFound($"找不到id={id}的书");  
            }
            else
            { return result; }
        }

        [HttpGet]
        public async Task<ActionResult<Book?>> GetBookByIdWithCache(long id)
        {
            //GetOrCreateAsync 二合一的方法 1)从缓存取数据  2)从数据源取数据,并返回给调用者并保存到缓存
            Book? b=await memoryCache.GetOrCreateAsync("Book" + id, async (e)=>
            {
                logger.LogInformation("缓存里没找到,从数据库中读取数据");
                return await MyDbContext.GetByIdAsync(id);
            });
            logger.LogInformation($"GetOrCreateAsync结果{b}");
            if (b == null)
            {
                return NotFound($"找不到id={id}的书");
            }
            else
            {
                logger.LogInformation("把数据返回给调用者");
                return b;
            }
        }
    }
}

Part4 – (27) 缓存的过期时间策略

缓存的过期时间

1、上面的例子中的缓存不会过期,除非重启服务器。

2、解决方法:在数据改变的时候调用Remove或者Set来删除或者修改缓存(优点:及时);过期时间(只要过期时间比较短,缓存数据不一致的情况也不会持续很长时间。)

3、两种过期时间策略:绝对过期时间、滑动过期时间。它们分别是什么?

缓存的绝对过期时间

  • GetOrCreateAsync()方法的回调方法中有一个ICacheEntry类型的参数,通过ICacheEntry对当前的缓存项做设置。
  • AbsoluteExpirationRelativeToNow用来设定缓存项的绝对过期时间。
Book? b=await memoryCache.GetOrCreateAsync("Book" + id, async (e)=>
{
    logger.LogInformation("缓存里没找到,从数据库中读取数据");
    e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);//绝对过期时间 10s
    return await MyDbContext.GetByIdAsync(id);
});

缓存的滑动过期时间

  • ICacheEntry的SlidingExpiration属性用来设定缓存项的滑动过期时间。
  • 只要在缓存没过期的时候请求一次,缓存自动续命一段时间。
Book? b=await memoryCache.GetOrCreateAsync("Book" + id, async (e)=>
{
    logger.LogInformation("缓存里没找到,从数据库中读取数据");
    //e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
    e.SlidingExpiration=TimeSpan.FromSeconds(10);
    return await MyDbContext.GetByIdAsync(id); //相对过期时间
});

两种过期时间混用

使用滑动过期时间策略,如果一个缓存项一直被频繁访问,那么这个缓存项就会一直被续期而不过期。可以对一个缓存项同时设定滑动过期时间和绝对过期时间,并且把绝对过期时间设定的比滑动过期时间长,这样缓存项的内容会在绝对过期时间内随着访问被滑动续期,但是一旦超过了绝对过期时间,缓存项就会被删除。

内存缓存的是与非

  • 无论用那种过期时间策略,程序中都会存在缓存数据不一致的情况。部分系统(博客等)无所谓,部分系统不能忍受(比如金融)。
  • 可以通过其他机制获取数据源改变的消息,再通过代码调用IMemoryCache的Set方法更新缓存。

本文版权归个人技术分享站点所有,发布者:chaoqiang,转转请注明出处:http://www.zhengchaoqiang.com/1446.html

chaoqiangchaoqiang
上一篇 2021-12-25 08:57
下一篇 2022-01-07 18:56

相关推荐

近期个人博客正在迁移中,原博客请移步此处,抱歉!