使用.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)进行全局的配置,应用于所有添加了相同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的请求:

  • 请求
    image

  • 响应
    image
    响应头中多了一个cache-control字段用于指明缓存的类型(public)以及过期时间为60s:
    image
    如果你是使用Postman或者Insomia发送的请求,那么在过期前再次发起相同请求的返回头中会再多出一个Age字段,用于表明该资源当前缓存了多少秒(Hoppscotch我没找到可以在哪里设置,所以下面的截图是来自Insomia,如果有哪位老哥知道的可以教一下):
    image
    同时如果观察日志的话会发现,第二次请求并没有实际执行SQL语句,这也证明了第二次请求的返回来自缓存:
    image
    如果间隔60s以上我们再去发送相同的请求,会发现日志中是这样的:
    image
    可以看到缓存已经失效了,此时需要重新向数据库查询返回数据,并将这次请求结果缓存起来。

验证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相同,我们来看响应中的变化:

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

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

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

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

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

我们给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

  • 响应
    我们暂时只关注响应头:
    image
    如果在缓存失效前我们添加了一个新的TodoList,在请求头中添加If-None-Match=53154EEFAE230D733827DBDE49B42AF9再执行获取请求:
    image
    可以看到在失效时间到期之内,Etag值已经发生了变化,校验表明资源已经改变,需要重新获取。
    如果我们再次获取相同的资源,会得到304返回:
    image

一点扩展

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

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

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

Varnish
Apache Traffic Server
Squid

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

总结

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

参考资料

  1. Varnish
  2. Apache Traffic Server
  3. Squid
文章来源:https://www.cnblogs.com/code4nothing/p/build-todolist-22.html

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
管理员
上一篇:使用.NET 6开发TodoList应用(23)——实现请求限流
下一篇:.NET C#教程初级篇 1-1 基本数据类型及其存储方式
评论列表

发表评论

评论内容
昵称:
验证码:
验证码
关联文章

使用.NET 6开发TodoList应用(22)——实现缓存
使用.NET 6开发TodoList应用(12)——实现ActionFilter
使用.NET 6开发TodoList应用(16)——实现查询排序
使用.NET 6开发TodoList应用(15)——实现查询搜索
使用.NET 6开发TodoList应用(9)——实现PUT请求
使用.NET 6开发TodoList应用(25)——实现RefreshToken
使用.NET 6开发TodoList应用(14)——实现查询过滤
使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求
使用.NET 6开发TodoList应用(23)——实现请求限流
使用.NET 6开发TodoList应用(26)——实现Configuration和Option的强类型绑定
使用.NET 6开发TodoList应用(21)——实现API版本控制
使用.NET 6开发TodoList应用(7)——使用AutoMapper实现GET请求
使用.NET 6开发TodoList应用(28)——实现应用程序健康检查
使用.NET 6开发TodoList应用(31)——实现基于Github Actions和ACI的CI/CD
使用.NET 6开发TodoList应用(11)——使用FluentValidation和MediatR实现接口请求验证
使用.NET 6开发TodoList应用(30)——实现Docker打包和部署
使用.NET 6开发TodoList应用(13)——实现查询分页
使用.NET 6开发TodoList应用(8)——实现全局异常处理
使用.NET 6开发TodoList应用(填坑1)——实现当前登录用户获取
使用.NET 6开发TodoList应用(29)——实现静态字符串本地化功能

热门标签
.NET Core .NET Reactor ag-grid AI发布 api安全 ASP.NET Core C#DLL加密 C#播放声音 C#代码混淆 C#代码加密 ChromeDriver Codex DateTime DBeaver devexpress devTool DLL混淆 edge.js EF EFCore Electron element-ui el-form el-table excel FastReport FileStream FolderBrowerDialog FolderSelectDialog form提交 git gridcontrol gridview input javascript json字符串 JS转换对象JSON jwt JWT授权 linq log Math MCP mitmproxy MVC MySQL Navicat netstat nginx node_modules NSwag Nuget Nuget镜像 number PowerShell pyinstaller python pythoncom python爬虫 python抓包 pywin32 redis Requests-html RestSharp Selenium sql SQL Server Swagger to-cms Visual Studio VSCode vue VueRouter vue路由 VUE页面通讯 Webpack Windows Windows服务 winform wmi xlrd yaml YESCMS YESWEB开发框架 白象 表单提交 播放声音 打开URL 代码混淆 弹窗提醒 端口占用 对象转换 分布式 公共字典 机器码 进程排查 静态资源 开发指南 路由参数 密钥 配置教程 配置文件 权限 人工智能 任务 任务调度 日期间隔 日志 日志记录 省市区 授权验证 数据库 四舍五入 文案 文件读取 文件夹选择 文件目录选择 问题排查 行政区域数据 页面通讯 中间件 CSharp 事务锁 工单系统 并发控制 重复提交 CMS Markdig Markdown markdown-it marked 技术选型 VS Code 开发工具 源代码管理 版本控制 Docker PostgreSQL 时区 部署排查 CMS架构 EF Core 主题系统 二次开发 插件系统 容器 运维命令 镜像清理 Linux NAS 远程挂载 飞牛 fnOS S/4HANA SAP GUI SAP HANA SAP R/3 SAP入门 SAP版本 ERP SAP SAP MM 库存管理 物料管理 采购管理 入门教程 SAP S/4HANA SPRO 企业结构 采购组织 MM01 物料主数据 物料类型 BP分组 业务伙伴 供应商主数据 ME41 RFQ 库存物料 采购流程 ME51 消耗性物料 科目分配 采购申请 AC03 ML81N 外部服务 服务主数据 Business Partner SAP培训 ME51N MM模块 Lean Services MM-SRV 外部服务采购 PIR 供应来源 采购主数据 采购信息记录 ME31K 框架协议 计划协议 采购合同 ME01 供应来源确定 货源清单 MEQ1 供应源确定 配额安排 配额评分 MD04 MD21 MRP 计划文件 需求计划 批量程序 MD01N MD02 MRP Live MD05 MM 物料计划 优化采购 供应源 采购订单 ME2A 供应商确认 采购监控 Flexible Workflow 凭证释放 采购审批 释放策略 实地盘点 物料凭证 货物移动 MIGO 收货 移动类型 已撤回 供应商退货 货物发出 STO 库存转储 转移过账 生产订单 预留 GR/IR MIRO 供应商发票 物流发票校验 OMR2 税码 FI PP SD 实操教程 MRBR OMR6 发票差异 交货成本 后续借记 MI01 实物盘点 盘点差异 公司代码 工厂 组织结构 OMS2 主数据定制 自动科目确定 BP角色 CVI 伙伴确定 编号范围 凭证类型 字段选择 FBN1 OMBT OMC2 会计凭证 OMJJ BOM 委外加工 项目类别L MRKO 供应商寄售 特殊库存K MRKON PIPE Pipeline 特殊库存P ERS MRIS 发票计划 周期性结算 里程碑付款 变更追踪 版本管理 采购凭证 SFTP WebDAV 网盘 飞牛fnOS AMPL HERS MPN 中文教程 库存确定 可用性检查 缺件检查 Output Management 消息确定 输出确定 分割评估 库存计价 评估类别 评估类型 PB00 RM0000 条件技术 采购定价 MM-FI集成 OBYC 库存估价 文本类型 文本采用 EFB EVO MSV SU3 用户参数 发票校验 合同参照 履约保留款 特别总账 预付款 Fiori Launchpad SAP Fiori 应用导航 用户体验 LSMW LTMC Migration Cockpit 数据迁移 BRFplus OPD Output Control My Inbox 审批流程 灵活工作流 SAP PP 外部加工 SAP QM 检验批 质量信息记录 采购收货 SAP PM 维护BOM 维护订单 SAP SD SAP Service 端到端流程
联系我们
联系电话:15090125178(微信同号)
电子邮箱:garson_zhang@163.com
站长微信二维码
微信二维码