缓存可以提高应用程序的响应速度,本篇介绍如何在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. "强制缓存" 和 "协商缓存" 区别:
4.1 强制缓存:在缓存数据未失效的情况下会直接使用浏览器的缓存数据,不会再向服务器发送任何请求。
4.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); } }
设置响应缓存特性的时候使用其中的属性名称就可以了。