.NET Core缓存

news2025/1/30 11:56:34

目录

缓存的概念

客户端响应缓存

cache-control

服务器端响应缓存

内存缓存(In-memory cache)

用法

GetOrCreateAsync

缓存过期时间策略

缓存的过期时间

解决方法:

两种过期时间策略:

绝对过期时间

滑动过期时间

两种过期时间混用

总结


缓存的概念

缓存(Caching)是用来保存数据的区域,从缓存区域读取数据的速度比从数据源读取数据的速度快很多,从数据源获取数据后,我们可以把数据保存到缓存中,下次再需要获取同样数据时,可以直接从缓存中获取之前保存的数据,是系统优化中简单又有效的工具,投入小收效大。数据库中的索引等简单有效的优化功能本质上都是缓存。

  1. 缓存命中:从缓存中获取了要获取的数据
  2. 缓存命中率:多次请求中,命中的请求占全部请求的百分比
  3. 缓存数据不一致:数据源中数据保存到缓存后,发生了变化
  4. 多级缓存:在Web开发中,存在多级缓存,浏览器存在“浏览器端缓存”,网关节电服务器存在“节点缓存”,,Web服务器上可能存在“服务器端缓存”,只要在任何一个节点上命中缓存,请求就会直接返回,而不会继续向后传递。

客户端响应缓存

cache-control

  1. RFC7324是HTTP协议中对缓存进行控制的规范,其中重要的是cache-control这个响应报文头。服务器如果返回cache-control:max-age=60,则表示服务器指示浏览器端“可以缓存这个响应内容60秒”。
  2. 我们只要给需要进行缓存控制的控制器的操作方法添加ResponseCacheAttribute这个Attribute,ASP.NET Core会自动添加cache-control报文头。
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
    [HttpGet]
    [ResponseCache(Duration = 20)]
    public DateTime Now()
    {
        return DateTime.Now;
    }
}

服务器端响应缓存

  1. 如果ASP.NET Core中安装了“响应缓存中间件” ,那么ASP.NET Core不仅会继续根据[ResponseCache]设置来生成cache-control响应报文头来设置客户端缓存,而且服务器端也会按照[ResponseCache]的设置来对响应进行服务器端缓存。和客户端端缓存的区别?来自多个不同客户端的相同请求。
  2. “响应缓存中间件”的好处:对于来自不同客户端的相同请求或者不支持客户端缓存的客户端,能降低服务器端的压力。
  3. 用法:app.MapControllers()之前加上app.UseResponseCaching()。请确保app.UseCors()写到app.UseResponseCaching()之前。

缺点:

  1. 无法解决恶意请求给服务器带来的压力。
  2. 服务器端响应缓存还有很多限制,包括但不限于:响应状态码为200的GET或者HEAD响应才可能被缓存;报文头中不能含有Authorization、Set-Cookie等。
  3. 可以采用内存缓存、分布式缓存等。

由于服务器端响应缓存开发调试的麻烦以及过于苛刻的限制,因此除非开发人员能够灵活掌握并应用,否则不建议启用“响应缓存中间件”。对于只需要进行客户端响应缓存处理的操作方法,标注ResponseCache即可,如果还需要在服务器端进行缓存处理,建议采用ASP.NET Core提供的内存缓存、分布式缓存等机制来编写程序。

内存缓存(In-memory cache)

把缓存数据放到应用程序的内存。内存缓存中保存的是一系列的键值对,就像Dictionary类型一样。

内存缓存的数据保存在当前运行的网站程序的内存中,是和进程相关的。因为在Web服务器中,多个不同网站是运行在不同的进程中的,因此不同网站的内存缓存是不会互相干扰的,而且网站重启后,内存缓存中的所有数据也就都被清空了。

用法

  1. 添加服务:builder.Services.AddMemoryCache()
  2. 注入IMemoryCache接口,查看接口的方法:TryGetValue、Remove、Set、GetOrCreate、GetOrCreateAsync

GetOrCreateAsync

GetOrCreateAsync<TItem>(object key, Func<ICacheEntry, Task<TItem>> factory)

获取缓存键为key的缓存值,方法的返回值为获取的缓存值,如果缓存中没有缓存键为key的缓存值,则调用factory指向的回调从数据源获取数据,把获取的数据作为缓存值保存到缓存中,并且把获取的数据作为方法的返回值。

Program.cs
//添加内存缓存服务
services.AddMemoryCache();
//添加DbContext
services.AddScoped<MyDbContext>();

public record Book(long Id, string Name);

public class BookConfig : IEntityTypeConfiguration<Book>
{
    public void Configure(EntityTypeBuilder<Book> builder)
    {
        builder.ToTable("T_Books");
    }
}

public class MyDbContext:DbContext
{
    DbSet<Book> books {  get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseSqlServer("Server=.;Database=demo;Trusted_Connection=true;MultipleActiveResultSets=true;TrustServerCertificate=true;");
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
    }
}
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
    private readonly IMemoryCache memoryCache;
    private readonly ILogger<TestController> logger;
    public TestController(IMemoryCache memoryCache, ILogger<TestController> logger)
    {
        this.memoryCache = memoryCache;
        this.logger = logger;
    }

    [HttpGet]
    public async Task<ActionResult<Book?>> GetById(int id)
    {
        using (MyDbContext ctx = new MyDbContext())
        {
            //没加内存缓存
            //var result = await ctx.Set<Book>().SingleOrDefaultAsync(o => o.Id == id);
            //if (result == null)
            //{
            //    return NotFound($"找不到id={id}的书");
            //}
            //else
            //{
            //    return result;
            //}

            //内存缓存
            logger.LogInformation($"开始执行GetBookById,id={id}");
            Book? b = await memoryCache.GetOrCreateAsync("Book" + id, async (e) =>
            {
                logger.LogInformation($"缓存没找到,到数据库查询,id={id}");
                return await ctx.Set<Book>().SingleOrDefaultAsync(o => o.Id == id);
            });
            logger.LogInformation($"GetBookById结果:{b}");
            if (b == null)
            {
                return NotFound($"找不到id={id}的书");
            }else
            {
                return Ok(b);
            }
        }
    }
}

 查询两次结果

缓存过期时间策略

缓存的过期时间

上面的例子中的缓存不会过期,除非重启服务器。

解决方法:

  1. 在数据改变的时候调用Remove或者Set来删除或者修改缓存(优点:及时);
  2. 过期时间(只要过期时间比较短,缓存数据不一致的情况也不会持续很长时间。)

两种过期时间策略:

GetOrCreateAsync()方法的回调方法中有一个ICacheEntry类型的参数,通过ICacheEntry对当前的缓存项做设置。

绝对过期时间

设置缓存完的指定时间后,缓存项被清除。

AbsoluteExpirationRelativeToNow:用来设定缓存项的绝对过期时间。

Book? b = await memoryCache.GetOrCreateAsync("Book" + id, async (e) =>
{
    logger.LogInformation($"缓存没找到,到数据库查询,id={id}");
    //缓存有效期10s
    e.AbsoluteExpirationRelativeToNow=TimeSpan.FromSeconds(10);
    return await ctx.Set<Book>().SingleOrDefaultAsync(o => o.Id == id);
});
滑动过期时间

设置缓存完的指定时间后,如果对应的缓存数据没有被访问,缓存项被清除,如果在指定时间内,被访问一次,则缓存项的过期时间会自动续期。

Book? b = await memoryCache.GetOrCreateAsync("Book" + id, async (e) =>
{
    logger.LogInformation($"缓存没找到,到数据库查询,id={id}");
    e.SlidingExpiration=TimeSpan.FromSeconds(10);
    return await ctx.Set<Book>().SingleOrDefaultAsync(o => o.Id == id);
});
两种过期时间混用

使用滑动过期时间策略,如果一个缓存项一直被频繁访问,那么这个缓存项就会一直被续期而不过期。可以对一个缓存项同时设定滑动过期时间和绝对过期时间,并且把绝对过期时间设定的比滑动过期时间长,这样缓存项的内容会在绝对过期时间内随着访问被滑动续期,但是一旦超过了绝对过期时间,缓存项就会被删除。

总结

  1. 无论用那种过期时间策略,程序中都会存在缓存数据不一致的情况。部分系统(博客等)无所谓,部分系统不能忍受(比如金融)。
  2. 可以通过其他机制获取数据源改变的消息,再通过代码调用IMemoryCache的Set方法更新缓存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2284546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【微服务与分布式实践】探索 Dubbo

核心组件 服务注册与发现原理 服务提供者启动时&#xff0c;会将其服务信息&#xff08;如服务名、版本、所在节点的网络地址等&#xff09;注册到注册中心。服务消费者则可以从注册中心发现可用的服务提供者列表&#xff0c;并与之通信。注册中心会存储服务的信息&#xff0c…

Java 大视界 -- Java 大数据在生物信息学中的应用与挑战(67)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

NeuIPS 2024 | CoT推理的新突破:推理边界框架(RBF)

近年来&#xff0c;大型语言模型&#xff08;LLMs&#xff09;在推理任务上的能力不断提升&#xff0c;尤其是 思维链&#xff08;Chain-of-Thought, CoT&#xff09; 技术&#xff0c;使得模型可以逐步推演逻辑&#xff0c;提高预测准确率。然而&#xff0c;当前的CoT推理仍然…

linux——进程树的概念和示例

一些程序进程运行后&#xff0c;会调用其他进程&#xff0c;这样就组成了一个进程树。 比如,在Windows XP的“运行”对话框中输入“cmd”启动命令行控制台&#xff0c;然后在命令行中输入“notepad”启动记事本&#xff0c;那么命令行控制台进程“cmd.exe”和记事本进程“note…

CSAPP学习:前言

前言 本书简称CS&#xff1a;APP。 背景知识 一些基础的C语言知识 如何阅读 Do-做系统 在真正的系统上解决具体的问题&#xff0c;或是编写和运行程序。 章节 2025-1-27 个人认为如下章节将会对学习408中的操作系统与计算机组成原理提供帮助&#xff0c;于是先凭借记忆将其简单…

【番外篇】鸿蒙扫雷天纪:运混沌灵智勘破雷劫天局

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 这一节课我们不学习新的知识&#xff0c;我们来做一个扫雷小游戏 目录 扫雷小游戏概述一、扫雷游戏分析…

【反悔堆】力扣1642. 可以到达的最远建筑

给你一个整数数组 heights &#xff0c;表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。 你从建筑物 0 开始旅程&#xff0c;不断向后面的建筑物移动&#xff0c;期间可能会用到砖块或梯子。 当从建筑物 i 移动到建筑物 i1&#xff08;下标 从 0 开始 &#xff09;…

电力晶体管(GTR)全控性器件

电力晶体管&#xff08;Giant Transistor&#xff0c;GTR&#xff09;是一种全控性器件&#xff0c;以下是关于它的详细介绍&#xff1a;&#xff08;模电普通晶体管三极管进行对比学习&#xff09; 基本概念 GTR是一种耐高电压、大电流的双极结型晶体管&#xff08;BJT&am…

Cursor 帮你写一个小程序

Cursor注册地址 首先下载客户端 点击链接下载 1 打开微信开发者工具创建一个小程序项目 选择TS-基础模版 官方 2 然后使用Cursor打开小程序创建的项目 3 在CHAT聊天框输入自己的需求 比如 小程序功能描述&#xff1a;吃什么助手 项目名称&#xff1a; 吃什么小程序 功能目标…

【shell工具】编写一个批量扫描IP地址的shell脚本

批量扫描某个网段中的主机&#xff08;并发&#xff09; 创建目录编写脚本文件 mkdir /root/ip_scan_shell/ touch /root/ip_scan_shell/online_server.txt touch /root/ip_scan_shell/offline_server.txt touch /root/ip_scan_shell/ip_scan.sh写入下面shell到脚本文件中…

vim如何设置制表符表示的空格数量

:set tabstop4 设置制表符表示的空格数量 制表符就是tab键&#xff0c;一般默认是四个空格的数量 示例&#xff1a; &#xff08;vim如何使设置制表符表示的空格数量永久生效&#xff1a;vim如何使相关设置永久生效-CSDN博客&#xff09;

LangChain:使用表达式语言优化提示词链

在 LangChain 里&#xff0c;LCEL 即 LangChain Expression Language&#xff08;LangChain 表达式语言&#xff09;&#xff0c;本文为你详细介绍它的定义、作用、优势并举例说明&#xff0c;从简单示例到复杂组合示例&#xff0c;让你快速掌握LCEL表达式语言使用技巧。 定义 …

多线程编程杂谈( 下)

问题 是否存在其它中途线程退出的方法&#xff1f; 通过调用Linux系统函数 pthread_cancel(...) 可中途退出线程 Linux 提供了线程取消函数 取消状态 接受取消状态: PTHREAD_CANCEL_ENABLE拒绝取消状态: PTHREAD_CANCEL_DISABLE 取消请求 延迟取消: PTHREAD_CANCEL_DEFERR…

电脑无法开机,重装系统后没有驱动且驱动安装失败

电脑无法开机&#xff0c;重装系统后没有驱动且驱动安装失败 前几天电脑突然坏了&#xff0c;电脑卡住后&#xff0c;强制关机&#xff0c;再开机后开机马上就关机。尝试无数次开机后失败&#xff0c;进入BIOS界面&#xff0c;发现已经没有Windows系统了。重新安装系统后&…

【Java数据结构】了解排序相关算法

基数排序 基数排序是桶排序的扩展&#xff0c;本质是将整数按位切割成不同的数字&#xff0c;然后按每个位数分别比较最后比一位较下来的顺序就是所有数的大小顺序。 先对数组中每个数的个位比大小排序然后按照队列先进先出的顺序分别拿出数据再将拿出的数据分别对十位百位千位…

机器学习-线性回归(对于f(x;w)=w^Tx+b理解)

一、&#x1d453;(&#x1d499;;&#x1d498;) &#x1d498;T&#x1d499;的推导 学习线性回归&#xff0c;我们那先要对于线性回归的表达公示&#xff0c;有所认识。 我们先假设空间是一组参数化的线性函数&#xff1a; 其中权重向量&#x1d498; ∈ R&#x1d437; …

Ubuntu环境通过Ollama部署DeepSeek-R1模型教程

Ollama 是一个专注于简化模型部署和推理的工具&#xff0c;特别适合在生产环境中快速部署和运行模型。 以下是如何使用 Ollama 来安装、部署和使用模型的步骤&#xff1a; 一. 安装 Ollama 首先&#xff0c;你需要安装 Ollama。Ollama 通常支持多种平台&#xff08;如 Linux、…

【中间件快速入门】什么是Redis

现在后端开发会用到各种中间件&#xff0c;一不留神项目可能在哪天就要用到一个我们之前可能听过但是从来没接触过的中间件&#xff0c;这个时候对于开发人员来说&#xff0c;如果你不知道这个中间件的设计逻辑和使用方法&#xff0c;那在后面的开发和维护工作中可能就会比较吃…

poi在word中打开本地文件

poi版本 5.2.0 方法1&#xff1a;使用XWPFFieldRun&#xff08;推荐&#xff09; 比如打开当前相对路径的aaaaa.docx XWPFFieldRun run paragraph.createFieldRun();CTRPr ctrPr run.getCTR().addNewRPr();CTFonts font ctrPr.addNewRFonts();// 设置字体font.setAscii(&quo…

Meta 计划 2025 年投资 650 亿美元推动 AI 发展

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…