系列导航及源代码

需求

有的时候为了减少客户端请求相同资源的逻辑重复执行,我们会考虑使用一些缓存的方式,在.NET 6中,我们可以借助框架提供的中间件来实现请求资源的缓存。

目标

实现请求结果的缓存。

原理与思路

对于在.NET6中实现缓存,我们可以使用响应缓存中间件ResponseCaching来实现,同时可以使用Marvin.Cache.Headers来为我们提供更多的缓存相关的属性。

实现

使用原生ResponseCaching实现缓存

既然是中间件,我们便在Program中引入:

  • Program.cs
// 省略其他...
// 配置缓存中间件
builder.Services.AddResponseCaching();
builder.Services.AddControllers();
// ...
// 使用缓存中间件
app.UseResponseCaching();
app.MapControllers();

在使用方法上,有几种方式可以实现配置:1)进行全局的配置,应用于所有添加了相同ProfileNameResponseCache的Controller响应;2)对单个Controller/Action进行配置,应用于当前作用的Controller/Action;3)全局配置后,由单个Controller/Action覆盖全局配置。我们会演示1)和3)的场景。

我们准备使用获取所有TodoLists的接口进行演示。

先看如何进行全局配置:

  • Program.cs
// 省略其他...
builder.Services.AddControllers(options =>
{
options.CacheProfiles.Add("60SecondDuration", new CacheProfile { Duration = 60 });
});

验证1: 全局配置Caching

首先给我们要进行验证的Action添加属性:

  • TodoListController.cs
// 省略其他...
[HttpGet]
[ResponseCache(CacheProfileName = "60SecondDuration")]
public async Task<ApiResponse<List<TodoListBriefDto>>> Get()
{
return ApiResponse<List<TodoListBriefDto>>.Success(await _mediator.Send(new GetTodosQuery()));
}

启动Api项目,第一次执行获取TodoLists的请求:

  • 请求

  • 响应



    响应头中多了一个cache-control字段用于指明缓存的类型(public)以及过期时间为60s:



    如果你是使用Postman或者Insomia发送的请求,那么在过期前再次发起相同请求的返回头中会再多出一个Age字段,用于表明该资源当前缓存了多少秒(Hoppscotch我没找到可以在哪里设置,所以下面的截图是来自Insomia,如果有哪位老哥知道的可以教一下):



    同时如果观察日志的话会发现,第二次请求并没有实际执行SQL语句,这也证明了第二次请求的返回来自缓存:



    如果间隔60s以上我们再去发送相同的请求,会发现日志中是这样的:



    可以看到缓存已经失效了,此时需要重新向数据库查询返回数据,并将这次请求结果缓存起来。

验证2: 单个Action覆盖全局配置

我们还是使用这个接口,但是修改一下属性:

  • TodoListController.cs
[HttpGet]
[ResponseCache(Duration = 120)]
public async Task<ApiResponse<List<TodoListBriefDto>>> Get()
{
return ApiResponse<List<TodoListBriefDto>>.Success(await _mediator.Send(new GetTodosQuery()));
}

重新启动Api项目,第一次执行获取TodoLists的请求,请求和验证1相同,我们来看响应中的变化:

  • 响应



    可以看到失效时间已经变为120s了,其他不再一一验证。

使用Marvin.Cache.Headers实现更多缓存功能

在缓存中还有一个问题是,如果判断缓存的数据内容已经变化,就需要去获取最新的响应而不是直接从缓存中取值。这是借助缓存校验来完成的,而常使用的方式是通过Etag实现。示意的过程如下:

如果首次请求资源,API会在响应头中添加EtagLast-Modified字段:

当客户端再次请求资源时,由于缓存自身是不知道资源有没有被修改,所以缓存会携带If-None-Match字段(和客户端收到的Etag值相等)和If-Modified-Since字段(和客户端收到的Last-Modified值相等)到API端,如果校验发现资源没有发生修改,那么API端无需重新获取资源,直接返回304字段(NotModifed)给缓存,缓存给客户端返回值。如果校验发现资源发生了修改,那么API将会返回新的结果。

我们给Api项目添加Nuget包Marvin.Cache.Headers,来实现此功能。

首先向Program中添加服务以及引入中间件:

  • Program.cs
builder.Services.AddResponseCaching();
builder.Services.AddHttpCacheHeaders(
expirationOptions =>
{
expirationOptions.MaxAge = 180;
expirationOptions.CacheLocation = CacheLocation.Private;
},
validateOptions =>
{
validateOptions.MustRevalidate = true;
});
// 省略其他...
app.UseResponseCaching();
app.UseHttpCacheHeaders();

同时我们需要移除之前添加的ResponseCache属性,因为新引入的库已经帮我们完成了,当然我们也可以通过以下方式覆盖全局配置:

[HttpCacheExpiration(CacheLocation = CacheLocation.Public, MaxAge = 60)]
[HttpCacheValidation(MustRevalidate = false)]

覆盖规则和框架内置的规则是一致的,我不会继续演示。

验证3: 缓存校验

请求仍然是获取所有的TodoLists

  • 响应

    我们暂时只关注响应头:



    如果在缓存失效前我们添加了一个新的TodoList,在请求头中添加If-None-Match=53154EEFAE230D733827DBDE49B42AF9再执行获取请求:



    可以看到在失效时间到期之内,Etag值已经发生了变化,校验表明资源已经改变,需要重新获取。

    如果我们再次获取相同的资源,会得到304返回:

一点扩展

但是如果我们仔细观察和思考就会发现,框架在实现缓存校验上存在两个问题:

  1. If-None-Match头字段是我们手动添加模拟的,这本应该由缓存中间件来完成;
  2. 在响应304的情况下,实际上是没有返回响应体的,即缓存中未修改的资源没有返回;

这两个问题是由框架内建的ResponseCaching库导致的,可以认为它没有正确地实现缓存校验机制。为此我们有一些替代方案可供参考:

Varnish

Apache Traffic Server

Squid

当然使用专门的CDN来做缓存也是可以的。

总结

在本文中我们主要演示了如何借助框架的缓存机制来实现请求资源的缓存,尽管在缓存校验的实现上,官方提供的库目前来看并没有能很好地完成功能以外,对于我们基本的使用场景来说已经够用了。下一篇文章我们来看怎么实现接口的限流。

参考资料

  1. Varnish
  2. Apache Traffic Server
  3. Squid

使用.NET 6开发TodoList应用(22)——实现缓存的更多相关文章

  1. 使用.NET 6开发TodoList应用(3)——引入第三方日志库

    需求 在我们项目开发的过程中,使用.NET 6自带的日志系统有时是不能满足实际需求的,比如有的时候我们需要将日志输出到第三方平台上,最典型的应用就是在各种云平台上,为了集中管理日志和查询日志,通常会选 ...

  2. 使用.NET 6开发TodoList应用(1)——系列背景

    前言 想到要写这样一个系列博客,初衷有两个:一是希望通过一个实践项目,将.NET 6 WebAPI开发的基础知识串联起来,帮助那些想要入门.NET 6服务端开发的朋友们快速上手,对使用.NET 6开发 ...

  3. 使用.NET 6开发TodoList应用(2)——项目结构搭建

    为了不影响阅读的体验,我把系列导航放到文章最后了,有需要的小伙伴可以直接通过导航跳转到对应的文章 : P TodoList需求简介 首先明确一下我们即将开发的这个TodoList应用都需要完成什么功能 ...

  4. 使用.NET 6开发TodoList应用(4)——引入数据存储

    需求 作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件.对我们的TodoList项目来说,自然也需要配置数据存储.目前的需求很简单: 需要能持久化TodoList对象并对其 ...

  5. 使用.NET 6开发TodoList应用(5)——领域实体创建

    需求 上一篇文章中我们完成了数据存储服务的接入,从这一篇开始将正式进入业务逻辑部分的开发. 首先要定义和解决的问题是,根据TodoList项目的需求,我们应该设计怎样的数据实体,如何去进行操作? 长文 ...

  6. 使用.NET 6开发TodoList应用(5.1)——实现Repository模式

    需求 经常写CRUD程序的小伙伴们可能都经历过定义很多Repository接口,分别做对应的实现,依赖注入并使用的场景.有的时候会发现,很多分散的XXXXRepository的逻辑都是基本一致的,于是 ...

  7. 使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求

    需求 需求很简单:如何创建新的TodoList和TodoItem并持久化. 初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法:实用传入的请求参数new一个数据库实 ...

  8. 使用.NET 6开发TodoList应用文章索引

    系列导航 使用.NET 6开发TodoList应用(1)--系列背景 使用.NET 6开发TodoList应用(2)--项目结构搭建 使用.NET 6开发TodoList应用(3)--引入第三方日志 ...

  9. iOS移动开发周报-第22期

    iOS移动开发周报-第22期 [摘要]:本期iOS移动开发周报带来如下内容:苹果股价创新高,iOS8自动调整UITableView布局,Swift学习心得等. 新闻 <苹果股价创新高 市值全球第 ...

随机推荐

  1. java foreach循环抛出异常java.util.ConcurrentModificationException

    代码如下: for (Iterator<String> iter = list.iterator(); iter.hasNext(); ) { if (Integer.parseInt(i ...

  2. 【.NET6】gRPC服务端和客户端开发案例,以及minimal API服务、gRPC服务和传统webapi服务的访问效率大对决

    前言:随着.Net6的发布,Minimal API成了当下受人追捧的角儿.而这之前,程序之间通信效率的王者也许可以算得上是gRPC了.那么以下咱们先通过开发一个gRPC服务的教程,然后顺势而为,再接着 ...

  3. shell脚本 批量查看mysql表条目数

    一.简介 源码地址 日期:2018/4/12 介绍:查看mysql的信息,用于比对和查询条目数 效果图: 二.使用 适用:centos6+ 语言:中文 注意:适用于5.7版本,其它版本要更改变量han ...

  4. 自动执行Python脚本

    一.自动执行Python脚本(前提条件是电脑已安装对应的Python程序) 1.1.win+R-输入cmd在输入where python查看Python的安装位置 C:\Users\ASUS\AppD ...

  5. IOS学习路径

    iOS Developer Roadmap Start your journey today! Where Do I Start? Becoming an iOS developer is a lot ...

  6. 十年后回到百年前?(Excel技巧集团)

    在单元格里输入日期,有时可以偷懒,比如明年的日期可以输入至少一位的年+横杠(或斜杠)+至少一位的月+横杠(或斜杠)+至少一位的日,也就是"21-1-1",单元格里就会自动显示&qu ...

  7. Java网络多线程开发:java.io.EOFException

    Java网络多线程开发:java.io.EOFException 在实现韩顺平Java的多用户即使通信系统实战项目中: 对于客户端线程的停止,老韩是向服务器端发送一个消息对象,提示服务器端进行资源释放 ...

  8. Hystrix 监控可视化页面——Dashboard 流监控

    1.什么是Dashboard Hystrix-dashboard 是一款针对 Hystrix 进行实时监控的工具页面,通过 Hystrix Dashboard 我们可以在直观地看到各 Hystrix ...

  9. pymysql-sqlalchemy-orm

    sqlalchemy示例代码 多对多外键 场景:一个作者映射多个书籍,一个书籍有多个作者 作者表: id name email sex 1 Alex alex@.. M 2 Rail rail@.. ...

  10. Java 将PDF转为线性PDF

    线性化PDF文件是PDF文件的一种特殊格式,可以通过Internet更快地进行查看.线性化的PDF,在页面数量很多的情况下,更能突出表现出快速浏览的优势.下面是通过后端Java程序实现将PDF文件转为 ...