zoukankan      html  css  js  c++  java
  • AspNetCore缓存技术

    AspNetCore响应缓存

    视频教程

    一、客户端响应缓存(强缓存)

    1. 基本使用

    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [ResponseCache(Duration = 3600)]
        [HttpGet]
        public string Get()
        {
            return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        }
    }
    

    2. 配置文件

    • 定义配置
    services.Configure<MvcOptions>(mvc =>
    {
        mvc.CacheProfiles.Add("default1",new CacheProfile
        {
            Duration=3600
        });
        mvc.CacheProfiles.Add("default2", new CacheProfile
        {
            Duration = 7200
        });
    });
    
    • 引用配置
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        //CacheProfileName 和 Duration 必须配置其中一项,否则报错
        [ResponseCache(CacheProfileName = "default1")]
        [HttpGet]
        public string Get()
        {
            return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        }
    }
    
    
    • 调试注意
    1. 不要直接在浏览器地址栏里请求该资源,浏览器会禁用这一行为的缓存,解决办法是通过xhr去请求。
    2. 不用启用浏览器的禁用缓存
      debug

    二、服务端响应缓存

    简介

    由于客户端缓存数据是保存在客户端的,对于相同的资源不同客户端端都要去请求服务器,而服务器又要请求数据库,对于一些不变的用户共享的资源(比如菜单)
    我们可以通过启用服务端缓存来减轻数据库压力。通过Microsoft.AspNetCore.ResponseCaching组件可以实现服务端响应缓存。需要注意以下几点问题

    1. 该组件依赖IMemoryCache即使用节点的内存缓存,不支持分布式(redis)缓存,考虑多节点多实例的情况下相同的数据每个节点要缓存很多副本(但性能很高,不建议存储太大的资源)
      分布式缓存建议自行缓存,而不是依赖改组件

    2. 一旦启用该组件,带有ResponseCache特性的的端点都将被服务端缓存

    3. 请求头不能带有Authorization标头,请求必须是get或者head。具体参考响应中间件

    4. 测试服务端缓存请通过postman测试或者禁用浏览器缓存(由于浏览器默认会启用客户端缓存),在指定的端点添加一个断点或者日志,来判断端点是否重复调用

    断点

    服务注册

    //注入服务
    services.AddResponseCaching();
    
    //启用中间件:如果还需要配置跨域,则跨域必须写在UseResponseCaching之前,因为UseResponseCaching中间件的缓存被击中就要返回了,就不好经过跨域中间件了
    app.UseResponseCaching();
    
    

    基本使用

    1. 不指定规则
    [HttpGet]
    [ResponseCache(Duration =1000)]
    public string Get()
    {
        return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
    }
    最终存储
    key: GETHTTPSLOCALHOST:5001/API/VALUES
    value: response.Body
    exipresIn:1000
    
    1. 根据请求头:对于同一个资源有的希望通过xml响应,有的希望json,我们可以根据accept-type进行存储
    [HttpGet]
    [ResponseCache(CacheProfileName = "default1",VaryByHeader ="User-Agent")]
    public string Get()
    {
        return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
    }
    key1: GETHTTPSLOCALHOST:5001/API/VALUES
    value: CachedVaryByRules,是一个规则,该规则要求请求头含User-Agent的value
    exipresIn:1000
    
    key2: 000000131S96PHUSER-AGENT=PostmanRuntime/7.28.4
    value: response.Body
    exipresIn:1000
    
    key3: 000000131S96QHUSER-AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 Edg/96.0.1054.43
    value: response.Body
    exipresIn:1000
    
    请求该资源时:
    1. 通过request可以得到key1(自己想啊)->进而保存一个规则CachedVaryByRules(规则要求请求头User-Agent参与key的生成)->通过规则计算key2为“000000131S96PHUSER-AGENT=PostmanRuntime/7.28.4”->通过key2就能拿到响应体了。
    
    2. 不同浏览器的user-agent不同,进而产生多个键值对
    
    3. 了解了这些我们对于存储,性能指标等就有了把控了
    
    1. 根据查询字符串
    //显然q的取值范围有很多种,最终会产生非常多的键值对,导致内存不足
    [HttpGet]
    [ResponseCache(CacheProfileName = "default1", VaryByQueryKeys = new string[] { "q" })]
    public string Get(int q)
    {
        return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
    }
    
    //原理同上
    
    1. 根据查询字符串和请求头
    
    [HttpGet]
    [ResponseCache(CacheProfileName = "default1", VaryByQueryKeys = new string[] { "q" }, VaryByHeader ="User-Agent")]
    public string Get(int q)
    {
        return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
    }
    //原理同上
    
    

    三、ETag响应缓存(弱缓存)

    显然强缓存,即使服务端数据发生了改变,客户端也无法知晓更改,在客户端不清除缓存的前提下,拿到的就是旧的数据。而Etag机制可以解决这一问题,
    具体流程如下:

    1. 浏览器请求sum.js文件
    2. 服务端响应sum.js文件的同时响应一个Etag=md5(sum.js)请求头,值为该文件的md5摘要,假设当前为vff787fa4f545
    3. 浏览器发现服务端响应了Etag会缓存响应(吧sum.js保存起来)
    4. 浏览器第二次请求sum.js文件,在请求头会携带if-none-match=vff787fa4f545
    5. 服务器接收到if-none-match的值并再次计算md5(sum.js)如果相等(文件没改)则返回304状态码(只返回状态码),否则直接返回新的文件和Etag
    6. 浏览器发现了304直接使用本地缓存,减少了带宽特别是对于图片,和一些静态文件,这很有用
      可见对于sum.js资源的请求始终保持和服务器实时对话,服务器可以控制浏览器是否使用本地缓存
      注意强缓存和若缓存没有依赖关系,可以同时启用,但是如果启用了强缓存,只有等强缓存到期之后才会走弱缓存
      示列代码:
      具体可以参考我开发的一个项目:ResponseCacheValidation
    using Microsoft.Extensions.Primitives;
    using Microsoft.Net.Http.Headers;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    
    builder.Services.AddControllers();
    
    builder.Services.AddCors(cors => 
    {
        cors.AddDefaultPolicy(policy => 
        {
            policy.AllowAnyHeader();
            policy.AllowAnyMethod();
            policy.AllowAnyOrigin();
        });
    });
    
    var app = builder.Build();
    app.UseCors();
    // Configure the HTTP request pipeline.
    var cache = new Dictionary<string, object>();
    app.Use(async (context, next) =>
    {
        var path = context.Request.Path.Value;
        if (path != null && path.EndsWith("sum.js"))
        {
            //缓存是否被击中
            if (cache.ContainsKey(path))
            {
                var etag = context.Request.Headers.IfNoneMatch;
                dynamic data = cache[path];
                //数据是否被修改
                if (data.Hash == etag)
                {
                    string file = data.File;
                    //告诉浏览器数据未修改
                    context.Response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
                    return;//结束
                }
            }
            //否则就返回新的
            if (!cache.ContainsKey(path))
            {
                var file = "function sum(a,b){return a+b;}";
                //推荐用md5等计算文件的hash,c#的hashcode应该是不支持分布式环境的
                var etag = file.GetHashCode().ToString();
                var data = new
                {
                    Hash = etag,
                    File = file,
                };
                cache.Add(path, data);
            }
            dynamic data1 = cache[path];
            string file1 = data1.File;
            string etag1 = data1.Hash;
            //context.Response.Headers.Add(HeaderNames.CacheControl,new StringValues("public,max-age=20"));
            context.Response?.Headers.Add(HeaderNames.ETag, new StringValues(etag1));
            context.Response?.WriteAsync(file1);
            return;//结束
        }
        await next();
    });
    
    app.MapControllers();
    
    app.Run();
    
    

    四、内存缓存和分布式缓存

    基本包

    1. 抽象包:Microsoft.Extensions.Caching.Abstractions定义了两个核心接口IMemoryCache和IDistributedCache接口,我们只需要面向这两个接口进行编程就好了

    2. 内存缓存:Microsoft.Extensions.Caching.Memory该包通过内存缓存来实现IMemoryCache接口,通过IMemoryCache来实现IDistributedCache接口。该包基于System.Runtime.Caching包实现源码参考

    基本使用

    1. 注册服务
    
    builder.Services.AddMemoryCache();
    
    //如果启用了分布式内存缓存则不需要启用上面的那个,不过写了也没事
    builder.Services.AddDistributedMemoryCache();
    
    
    1. 使用服务
    
    //注意内存缓存和分布式缓存是两个体系
    public void Get([FromServices]IMemoryCache memoryCache, [FromServices] IDistributedCache distributedCache)
    {
        memoryCache.Set("data1",new { Name ="json"});
        distributedCache.Set("data2", System.Text.Encoding.UTF8.GetBytes("Redis") ,new DistributedCacheEntryOptions 
        {
            AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(10),
        });
    }
    
    
    1. 替换IDistributedCache的实现为redis

    安装:Microsoft.Extensions.Caching.Redis包

    
    //只需要修改服务注册即可,这就是面向接口编程和面向ioc的特性之一
    services.AddDistributedRedisCache(c=>c.Configuration="localhost");
    
  • 相关阅读:
    恐怖如斯
    java在vscode中配置环境的坑
    python的迭代器模块
    一个模仿输入print就有这么多知识点
    30个python常用小技巧
    第一个只出现一次的字符
    UIScrollView属性
    iOS 中UISlider常用知识点
    iOS中UISegmentedControl常用属性
    iOS触摸事件
  • 原文地址:https://www.cnblogs.com/chaeyeon/p/15673108.html
Copyright © 2011-2022 走看看