使用.NET 6开发TodoList应用(22)——实现缓存
系列导航及源代码
需求
有的时候为了减少客户端请求相同资源的逻辑重复执行,我们会考虑使用一些缓存的方式,在.NET 6中,我们可以借助框架提供的中间件来实现请求资源的缓存。
目标
实现请求结果的缓存。
原理与思路
对于在.NET6中实现缓存,我们可以使用响应缓存中间件ResponseCaching
来实现,同时可以使用Marvin.Cache.Headers
来为我们提供更多的缓存相关的属性。
实现
使用原生ResponseCaching实现缓存
既然是中间件,我们便在Program中引入:
Program.cs
// 省略其他...
// 配置缓存中间件
builder.Services.AddResponseCaching();
builder.Services.AddControllers();
// ...
// 使用缓存中间件
app.UseResponseCaching();
app.MapControllers();
在使用方法上,有几种方式可以实现配置:1)进行全局的配置,应用于所有添加了相同ProfileName
的ResponseCache
的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会在响应头中添加Etag
和Last-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
返回:
一点扩展
但是如果我们仔细观察和思考就会发现,框架在实现缓存校验上存在两个问题:
If-None-Match
头字段是我们手动添加模拟的,这本应该由缓存中间件来完成;- 在响应
304
的情况下,实际上是没有返回响应体的,即缓存中未修改的资源没有返回;
这两个问题是由框架内建的ResponseCaching
库导致的,可以认为它没有正确地实现缓存校验机制。为此我们有一些替代方案可供参考:
• Varnish
• Apache Traffic Server
• Squid
当然使用专门的CDN
来做缓存也是可以的。
总结
在本文中我们主要演示了如何借助框架的缓存机制来实现请求资源的缓存,尽管在缓存校验的实现上,官方提供的库目前来看并没有能很好地完成功能以外,对于我们基本的使用场景来说已经够用了。下一篇文章我们来看怎么实现接口的限流。
参考资料
使用.NET 6开发TodoList应用(22)——实现缓存的更多相关文章
- 使用.NET 6开发TodoList应用(3)——引入第三方日志库
需求 在我们项目开发的过程中,使用.NET 6自带的日志系统有时是不能满足实际需求的,比如有的时候我们需要将日志输出到第三方平台上,最典型的应用就是在各种云平台上,为了集中管理日志和查询日志,通常会选 ...
- 使用.NET 6开发TodoList应用(1)——系列背景
前言 想到要写这样一个系列博客,初衷有两个:一是希望通过一个实践项目,将.NET 6 WebAPI开发的基础知识串联起来,帮助那些想要入门.NET 6服务端开发的朋友们快速上手,对使用.NET 6开发 ...
- 使用.NET 6开发TodoList应用(2)——项目结构搭建
为了不影响阅读的体验,我把系列导航放到文章最后了,有需要的小伙伴可以直接通过导航跳转到对应的文章 : P TodoList需求简介 首先明确一下我们即将开发的这个TodoList应用都需要完成什么功能 ...
- 使用.NET 6开发TodoList应用(4)——引入数据存储
需求 作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件.对我们的TodoList项目来说,自然也需要配置数据存储.目前的需求很简单: 需要能持久化TodoList对象并对其 ...
- 使用.NET 6开发TodoList应用(5)——领域实体创建
需求 上一篇文章中我们完成了数据存储服务的接入,从这一篇开始将正式进入业务逻辑部分的开发. 首先要定义和解决的问题是,根据TodoList项目的需求,我们应该设计怎样的数据实体,如何去进行操作? 长文 ...
- 使用.NET 6开发TodoList应用(5.1)——实现Repository模式
需求 经常写CRUD程序的小伙伴们可能都经历过定义很多Repository接口,分别做对应的实现,依赖注入并使用的场景.有的时候会发现,很多分散的XXXXRepository的逻辑都是基本一致的,于是 ...
- 使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求
需求 需求很简单:如何创建新的TodoList和TodoItem并持久化. 初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法:实用传入的请求参数new一个数据库实 ...
- 使用.NET 6开发TodoList应用文章索引
系列导航 使用.NET 6开发TodoList应用(1)--系列背景 使用.NET 6开发TodoList应用(2)--项目结构搭建 使用.NET 6开发TodoList应用(3)--引入第三方日志 ...
- iOS移动开发周报-第22期
iOS移动开发周报-第22期 [摘要]:本期iOS移动开发周报带来如下内容:苹果股价创新高,iOS8自动调整UITableView布局,Swift学习心得等. 新闻 <苹果股价创新高 市值全球第 ...
随机推荐
- 实时数据同步inotify+rsync
inotify inotify是一个实时监控服务,他能实时监控服务器中的目录的变化,发现目录中变化后,在配合rsync服务推送到备份服务器上 inotify要求内核要在2.6.13或之上,通过noti ...
- js 时间戳转换为年月日时分秒的格式
<script type="text/javascript"> var strDate = ''; $(function(){ // 获取时间戳 var nowDate ...
- 简单的Spring Boot项目——实现连接Mysql数据库
一.创建Spring Boot项目 参考:使用IntelliJ IDEA创建简单的Spring Boot项目 二.数据库.表的创建 三.项目开发 3.1 pom.xml文件配置 <?xml ve ...
- c3p0的使用步骤
//1.导入c3p0的连个包,和mysql的驱动包//2.配置c3p0.xml的配置文件 <c3p0-config> <!-- 使用默认的配置读取连接池对象 --> <d ...
- linux 让.net 控制台后台运行
命令 nohup 你的shell命令 & 例如 nohup dotnet MQTTClient.dll & 输入完成后,终端会有提示 这时再按下回车 回到shell命 ...
- 关于og4j漏洞修复解决方案及源码编译
最近log4j爆出重大漏洞,程序员要赶紧修复了!文末提供已经编译好的jar包. 建议最好修复到log4j-2.15.0-rc2版本,临时解决方案还是存在jndi漏洞. 打开log4j官网https:/ ...
- Excel字符串函数公式大全
一.Excel字符串的操作 1.1.Excel根据字节截取对应字符串(注:一个中文汉字对应两个字节) =LEFTB(A3,7) 从左边开始截取7个字节 =RIGHTB(A10,10) 从右边开始截取 ...
- vue在某页面监听键盘输入事件
需求:在某一网页,通过上下左右键控制一些操作 实现: 1.基本代码: 因为没有绑定特定的元素.所以我们将事件绑定到document上. //当前页面监视键盘输入 document.onkeydown ...
- 转:addChildViewController实现网易新闻首页切换
本来只是打算介绍一下addChildViewController这个方法的,正好今天朋友去换工作面试问到网易新闻标签栏效果的实现,就结合它,用个小Demo实例介绍一下:(具体解释都写在了Demo里面的 ...
- [BUUCTF]PWN——ciscn_2019_ne_5
ciscn_2019_ne_5 题目附件 步骤: 例行检查,32位,开启了nx保护 试运行一下程序,看一下程序的大概执行情况 32位ida载入,shift+f12查看程序里的字符串,发现了flag字符 ...