缓存
- 缓存是一种通过存储资源的备份,在请求时返回资源备份的技术。使用缓存能够减少服务器的压力和网络带宽,并减少客户端延迟。AspNetCore支持多种形式的缓存,Http的、内存的、分布式的,还提供了响应缓存中间件。
过期模型
验证模型
Http缓存
缓存类型
- Http有三种缓存:
- 客户端/浏览器缓存,存在于客户端,并且是私有的。
- 网关缓存,它是共享的缓存,位于服务器端,所有的客户端都会共享这个缓存,它的别名还有反向代理服务器缓存,HTTP加速器等。
- 代理缓存,位于网络上,是共享的,它既不位于客户端,也不位于服务器,类似于云存储或CDN。这种缓存经常被大型企业或ISP使用,用来服务大规模的用户。
缓存相关的消息头
HTTP1.1 200 OK
Cache-Control:public, max-age=60
- Expires消息头也可以用来指定缓存的有效时间,但它的值是一个绝对时间
缓存是否有效
- 上面则表示了该响应支持缓存,并且在任何地方都可以存储该响应,有效时间为60s;当缓存的响应失效后,此时当客户端再次使用资源时,就需要请求服务器,已验证缓存的响应是否有效(是否被改变)。如果有效,则服务器返回304Not Modified状态码,响应中也不会包含任何响应体;如果失效,服务器则返回200OK状态码以及最新资源。
如何验证缓存是否有效
- 验证缓存资源的方式有两种,一种是Last-Modified,另一种是ETag
- Last-Modified的值是资源最后更新的时间,在验证时,需要在请求消息中添加If-Modified-Since消息头,这个值是客户端最近一次收到该资源响应中Last-Modified的值,服务器就可以拿If-Modified-Since的值跟资源最后的更新时间进行比较,如果相同则缓存有效,不同则缓存无效。
- ETag可以看作是由服务器为当前资源生成的唯一标识,当客户端在请求验证时需要在消息头中添加If-None-Match,它的值为该资源最近异常从服务器中获得的ETag值。当服务器中资源发生改变时,它的ETag值会更改,所以服务器可以用它来识别缓存的响应是否和资源的最新状态一致。
- 建议使用ETag,因为Last-Modified是以秒为单位来进行验证的,有可能在一秒内对api进行了多次修改请求,Last-Modified的验证就会失效了。
服务器
HTTP1.1 200OK
Cache-Control:public, max-age=60
ETag: “ead145f”
客户端
GET /api/authors
If-None-Match: “ead145f”
服务器
HTTP1.1 304 NotModified
Cache-Control:public, max-age=60
ETag: “ead145f”
- 注意,只有请求方法为GET或HEAD,并且服务器返回200时的响应才支持缓存。
让WebApi支持Http缓存
- 在AspNetCore中实现Http缓存需要提供两样东西:
- 表明资源的缓存信息,即响应的消息头,它由
ResponseCache
特性来支持,它只设置了响应消息头项的信息,并没有缓存任何的数据。
- 缓存的存储器,它可以由response cache中间件来支持,也可以使用其他的存储器,比如redis等等。
- 在想要被缓存的Action上添加
ResponseCache
特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
[HttpGet("{authorId}", Name = nameof(GetAuthorAsync))] [ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)] public async Task<ActionResult<AuthorDto>> GetAuthorAsync(Guid authorId) { var author = await _repositoryWrapper.Author.GetByIdAsync(authorId); if (author==null) { return NotFound(); } var authorDto = _mapper.Map<AuthorDto>(author); return Ok(authorDto); }
|
- 然后在Startup.cs添加缓存的中间件,在ConfigureServices方法添加以下代码
1
| services.AddResponseCaching();
|
1 2
| app.UseResponseCaching();
|
添加缓存的Profile(属性的模板)
1 2 3 4 5 6 7 8 9
| services.AddControllers(config => { config.ReturnHttpNotAcceptable = true; config.CacheProfiles.Add("120sCacheProfile", new CacheProfile { Duration = 120 }); });
|
- 在AuthorController添加全局的Profile,除了上面GetAuthorAsync方法外,其他方法的缓存有效期就都是120s
1 2 3 4
| [Route("api/authors")] [ApiController] [ResponseCache(CacheProfileName = "120sCacheProfile")] public class AuthorController:ControllerBase{...}
|
缓存请求响应的Cache-Control的指令
让WebApi支持ETag
- 一般WebApi不会只对过期模型进行配置,还会对验证模型进行配置,验证模型分为强验证器(ETag)和弱验证器(Last-Modified);实现ETag需要添加一个依赖
Marvin.Cache.Headers
。
- 在Startup.cs添加全局的过期模型和验证模型的配置:
1
| services.AddHttpCacheHeaders();
|
- 然后在Configure方法中添加
app.UseHttpCacheHeaders();
。
- 使用Postman进行请求后,可以看到Header如下:HttpCacheHeaders帮我们自动加上了强验证器和弱验证器。
全局配置
- 可以在AddHttpCacheHeaders方法参数中对过期模型和验证模型进行配置
1 2 3 4 5 6 7 8 9 10
|
services.AddHttpCacheHeaders(expires => { expires.CacheLocation = CacheLocation.Private; expires.MaxAge = 60; }, validation => { validation.MustRevalidate = true; });
|
- 请求结果是这样的,由于缓存的类型为private,Api并不会帮我们保存缓存,所以就没有Age这个项。
- Vary项的含义是:当请求的Accept的媒体类型和缓存中的数据类型不一致的时候,响应并不会从缓存返回,而会去请求Api的数据。
在Action或Controller级别上的配置
- 在GetAuthorsAsync这个Action上添加配置
1 2 3 4 5 6 7
| [HttpGet(Name = nameof(GetAuthorsAsync))]
[HttpCacheExpiration(CacheLocation = CacheLocation.Private, MaxAge = 120)]
[HttpCacheValidation(MustRevalidate = false)] public async Task<ActionResult<IEnumerable<AuthorDto>>> GetAuthorsAsync ([FromQuery] AuthorResourceParameters parameters){...}
|
测试ETag(验证器)
- 先请求一次,然后可以在响应头中获取到ETag,然后在请求头中添加If-None-Match,值为ETag的值,请求。返回结果可以看到304状态码,没有响应体,同时带回最新的ETag。
- 再用第一次的Get请求加If-None-Match项请求相同的数据,返回了200OK,并且返回了最新的ETag。
学习资源:B站杨旭