.net5 core webapi进阶之七:缓存的使用及HTTP缓存的工作原理

缓存可以提高应用程序的响应速度,本篇介绍如何在webapi中进行缓存。

如果将缓存的方式或介质做一个分类,可以分为如下3类:

一、本地内存(可存储任何对象)

二、分布式存储(需序列化成字节数组)

 2.1 基于NOSQL(如Redis数据库)

 2.2 基于SQL(如SQL Server数据库)

三、响应缓存(浏览器缓存数据)

 

一、 本地内存缓存(可存储任何对象)

1. 还是以上篇中的项目 webapidemo3 来演示,使用内存进行缓存步骤如下:

第1步:安装Microsoft.Extensions.Caching.Memory包。


第2步:在Startup类的ConfigureServices()方法中配置依赖注入服务(见红色部分代码)。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<EfDemoContext>(options => options.UseMySQL(Configuration.GetConnectionString("MySQL")));

            services.AddMemoryCache();

            services.AddControllers();
        }    

第3步:新建控制器 CachesController,引用 using Microsoft.Extensions.Caching.Memory;

并在构造函数中注入 IMemoryCache 实例。

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

namespace webapidemo3 { [Route("api/[controller]")] [ApiController] public class CachesController : ControllerBase { private IMemoryCache _cache; public CachesController(IMemoryCache cache) { _cache = cache; }
//...... } }

第4步,编码,新建终结点 Demo1( )用于缓存一个GUID、Demo2( )用于获取这个GUID,代码如下:

        [HttpGet]
        [Route("demo1")]
        public string Demo1()
        {
            _cache.Set("guid",Guid.NewGuid().ToString());

            return _cache.Get<string>("guid");
        }

        [HttpGet]
        [Route("demo2")]
        public string Demo2()
        {
            return _cache.Get<string>("guid");
        }

第5步,测试,编译整个项目后运行,在浏览器访问网址 http://localhost:51630/api/caches/demo1 得到如下结果:

在浏览器中开启一个新tab,访问网址 http://localhost:51630/api/caches/demo2,结果如下:

访问终结点 Demo1()Demo2() 得到了相同的结果 。

 

2. 对内存缓存做更多细粒度的设置和操作。

如何设置缓存过期?缓存的过期策略可以是绝对时间(Absolute Time)或滑动时间(Sliding Time),

通过 MemoryCacheEntryOptions 来设置,然后作为 Set( )方法的参数传递就可以了, 如下:

        [HttpGet]
        [Route("demo1")]
        public string Demo1()
        {
            MemoryCacheEntryOptions options = new MemoryCacheEntryOptions() {
                //10分钟内没有访问就过期
                //SlidingExpiration = TimeSpan.FromMinutes(10),

                //30分钟后不管什么情况都过期
                AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(30)
            };
                
            _cache.Set("guid", Guid.NewGuid().ToString(), options); 

            return _cache.Get<string>("guid");
        }

 

二、基于Redis数据库的分布式缓存。

Redis数据库是一种非关系型数据库(即 Not Only SQL,简称NoSQL),

它是以键值对(Key-Value)形式来存储数据的,非常适合做分布式缓存,

.net core提供了对Redis的原生支持,使用起来和内存缓存非常相似,其配置步骤如下:

第1步:下载并安装 Redis。

github下载地址 :https://github.com/MicrosoftArchive/redis/releases

下载 Redis-x64-3.2.100.zip 解压缩后的文件清单如下,将文件夹copy到C:\下。

第2步:启动Redis服务。

打开cmd.exe工具,将目录切换到Redis文件夹,如下:

输入命令redis-server.exe redis.windows.conf 可以看到 Redis 服务启动成功的画面:

第3步:客户端连接。

不要关闭第2步中的cmd窗口(作Redis服务器使用),再开一个 cmd.exe窗口,

将目录切换到redis文件夹目录,然后使用redis-cli.exe命令连接redis服务器,如下:

redis-cli.exe -h 127.0.0.1 -p 6379,-h 接服务器IP , -p 接端口号。

可以看到已经成功连接了

第4步:使用Redis数据服务。

用set命令设置一个key-value,然后用 get命令取出:

Redis运行良好,至此Redis数据库服务配置完成。

第5步:安装 Microsoft.Extensions.Caching.Redis 包。

第6步:在Startup类的ConfigureServices()方法中配置依赖注入服务(见红色部分代码)。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<EfDemoContext>(options => options.UseMySQL(Configuration.GetConnectionString("MySQL")));
 
            services.AddDistributedRedisCache(
                options =>
                {
                    options.Configuration = "localhost"; //Redis数据库连接信息

            //Redis数据库实例名称,如果设置了多个实例需要在这里指定
//options.InstanceName = "RedisCache";

});
services.AddControllers(); }

第7步:在控制器中注入实例。新建RedissController 控制器类,如下:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using System;

namespace webapidemo3
{
    [Route("api/[controller]")]
    [ApiController]
    public class RedissController : ControllerBase
    {
        private IDistributedCache _cache;
        public RedissController(IDistributedCache cache)
        {
            _cache = cache;
        }
    }
}

第8步:使用Redis分布式缓存。新建终结点Demo1( ),如下:

        [HttpGet]
        [Route("demo1")]
        public string Demo1()
        {
            _cache.Set("mykey", Encoding.UTF8.GetBytes("dotnet core webapi demo."));
return Encoding.UTF8.GetString(_cache.Get("mykey")); }

访问网址 :http://localhost:51630/api/Rediss/demo1,结果如下:

 

三、响应缓存

响应缓存是指浏览器对Web服务器提供的HTTP响应报文进行缓存,.net core是完全按照标准的HTTP规范来操作缓存的,

(通过设置和缓存有关的HTTP头),在使用前有必要先了解HTTP规范(HTTP/1.1)及HTTP规范中对缓存的描述。

1. HTTP定义

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,
是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议,
它基于TCP/IP通信协议来传递数据(包括但不限于HTML 文件、 图片、音频、视频、文档、查询结果等)。

2. HTTP请求/响应消息格式

如果我们访问 www.baidu.com,用浏览器上的探测工具就可以看到请求和响应的报文信息,如下:

3. 响应缓存的约束条件

  • 在GET和HEAD请求时有效
  • 只发生在第一次请求之后

4. 缓存的工作过程:

浏览器第一次请求数据时,服务器会将缓存标识(Cache-Control、ETag、Last-Modify等)与数据一起返回,浏览器将二者备份起来。
再次请求数据时,客户端先判断备份中的缓存标识,决定是使用 "强制缓存" 还是 "协商缓存"

5.  "强制缓存" 和 "协商缓存" 区别:

    5.1 强制缓存:在缓存数据未失效的情况下会直接使用浏览器的缓存数据,不会再向服务器发送任何请求。
    5.2 协商缓存:不走强制缓存时浏览器就会与服务器进行协商,由服务器端对比判断资源是否进行了修改更新。
                            如果服务器端的资源没有修改,那么就返回304状态码,告诉浏览器可以使用缓存中的数据,
                            如果有更新就会返回200状态码,服务器就会返回更新后的数据并且将缓存规则一起返回。

6. 强制缓存的判断标识

由响应header中的 Pragma、 Expires 、 Cache-Control 这三个字段来标识,取值如下:

7. 判断缓存是否过期的规则。

用户发起了一个http请求后,如果所请求的资源有缓存,就开始检查缓存是否过期,步骤如下:

查看缓存是否有Cache-Control的max-age(s-maxage)指令,

若有,则使用响应报文生成时间Date + s-maxage/max-age获得过期时间,再与当前时间进行对比;

若没有,则比较Expires中的过期时间与当前时间(Expires是一个绝对时间)。

8. 协商缓存的使用条件。

当浏览器发现缓存过期后,缓存并不一定不能使用了,因为服务器端的资源可能仍然没有改变,

所以需要与服务器协商,让服务器判断本地缓存是否还能使用。

相关的header头属性有(ETag/If-Not-Match 、Last-Modified/If-Modified-Since),请求头和响应头需成对出现 。

它们的取值如下:

9. 协商缓存判断本地缓存是否可用的规则:

浏览器先判断缓存中是否有 ETag 或 Last-Modified 字段,如果没有,则发起一个http请求,服务器根据请求返回资源;

如果有这两个字段,则在请求头中添加If-None-Match字段(有ETag字段的话添加)、If-Modified-Since字段(有Last-Modified字段的话添加)。

如果同时发送If-None-Match 、If-Modified-Since字段,服务器只要比较If-None-Match和ETag的内容是否一致即可(因为优先级更高);

如果内容一致,服务器认为缓存仍然可用,则返回状态码304,浏览器直接读取本地缓存,这就完成了协商缓存的过程。

如果内容不一致,则视情况返回其他状态码,并返回所请求资源。

 

四、响应缓存的使用。

.net core使用 ResponseCachingMiddleware中间件处理HTTP响应缓存,步骤如下:

第1步:安装 NuGet 包 Microsoft.AspNetCore.ResponseCaching。

第2步:在 Startup 的 ConfigureServices(IServiceCollection services) 方法中注册服务 。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<EfDemoContext>(options => options.UseMySQL(Configuration.GetConnectionString("MySQL")));

            services.AddResponseCaching();
  
            services.AddControllers();
        }

第3步:在 Startup 的 Configure( ) 方法中启用响应缓存服务

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseResponseCaching();

            app.UseRouting();

            app.UseAuthorization();

            app.UseImageRequestMiddleware();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

第4步:新建控制器 ResponseCachesController,在终结点中使用响应缓存特性就可以了。

using Microsoft.AspNetCore.Mvc;
using System;

namespace webapidemo3
{
    [Route("api/[controller]")]
    [ApiController]
    public class ResponseCachesController : ControllerBase
    {
        
        [HttpGet]
        [Route("demo1")]
        [ResponseCache(Duration = 127)]
        public string Demo1()
        {
            //Duration = 127 对应 Cache-control: public, max-age=127
            return Guid.NewGuid().ToString();
        }
    }
}

第5步,编译项目后用 POSTMAN 访问网址 http://localhost:51630/api/ResponseCaches/demo1 ,结果如下:

在响应Header中增加了 Cache-Control 的 key ,其值是 public, max-age=127。

其他Demo:缓存设置2:

        [HttpGet]
        [Route("demo2")]
        [ResponseCache(Location = ResponseCacheLocation.Client, Duration = 35)] 
        public string Demo2()
        {
            //ResponseCache对应  Cache-Control: private, max-age=35
            return Guid.NewGuid().ToString();
        }

缓存设置3:

1         [HttpGet]
2         [Route("demo3")]
3         [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
4         public string Demo3()
5         {
6             //ResponseCache对应  Cache-Control: no-store,no-cache
7             return Guid.NewGuid().ToString();
8         }

缓存设置4:

        [HttpGet]
        [Route("demo4")]
        [ResponseCache(Duration = 735, VaryByQueryKeys = new string[] { "pageindex","pagesize" })]
        public string Demo4()
        {
            //根据查询参数 pageindex,pagesize 缓存
            return Guid.NewGuid().ToString();
        }

最后,附上 ResponseCacheAttribute 的定义,如下:

    namespace Microsoft.AspNetCore.Mvc
    {       
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
        public class ResponseCacheAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter
        {
            public ResponseCacheAttribute();
            public int Duration { get; set; }    
            public ResponseCacheLocation Location { get; set; } 
            public bool NoStore { get; set; }
            public string VaryByHeader { get; set; }      
            public string[] VaryByQueryKeys { get; set; }
            public string CacheProfileName { get; set; }
            public int Order { get; set; }
            public bool IsReusable { get; }

            public IFilterMetadata CreateInstance(IServiceProvider serviceProvider);
            public CacheProfile GetCacheProfile(MvcOptions options);
        }
    }

设置响应缓存特性的时候使用其中的属性名称就可以了。

 

posted @ 2021-03-03 13:28  屏风马  阅读(1933)  评论(0编辑  收藏  举报