You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 7.0内存缓存与客户端缓存实现及原理咨询

内存缓存与客户端缓存的机制验证与疑问

背景与实现

我正尝试在.NET 7.0环境下验证**内存缓存(In-memory)与客户端缓存(Client Side Caching)**的工作机制。

内存缓存实现

将数据库查询结果存储在缓存对象中,缓存过期时重新拉取数据:

[HttpGet]
[Route("/user/GetUsers")]
public async Task<List<User>> GetUserDetails()
{
    myStopWatch.Start();
    Task<List<User>> users = GetCachedResponse(CacheKey.users, () => _user.GetUsers());
    myStopWatch.Stop();

    double total = myStopWatch.Elapsed.TotalSeconds;

    return await users;
}

private async Task<List<User>> GetCachedResponse(string cacheKey, Func<Task<List<User>>> func)
{
    List<User> users = new List<User>();

    try
    {
        users = _cacheProvider.GetFromCache<List<User>>(cacheKey);
        if (users != null) return users;

        users = await func();

        _cacheProvider.SetCache(cacheKey, users, cacheEntryOptions);
    }
    catch (Exception ex)
    {
        ex.ToString();
    }

    return users;
}

后续根据建议修正了StopWatch的使用方式:

[HttpGet]
[Route("/user/GetUsers")]
public async Task<List<User>> GetUserDetails() 
{
  myStopWatch.Start();
  var users = await GetCachedResponse(CacheKey.users, () => _user.GetUsers());
  myStopWatch.Stop();

  double total = myStopWatch.Elapsed.TotalSeconds;

  return users;
}

客户端缓存实现

Startup.cs配置

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.CacheProfiles.Add("UserCacheForOneMin",
            new CacheProfile()
            {
                Duration = 60,
                Location = ResponseCacheLocation.Client
            });
    });
}
    
public void Configure(WebApplication app, IWebHostEnvironment env)
{
    app.UseResponseCaching();
}

接口实现

[HttpGet]
[Route("/user/GetAllUsers")]
[ResponseCache(CacheProfileName = "UserCacheForOneMin")]
public async Task<List<User>> GetAllUsers()
{
    myStopWatch.Start();
    Task<List<User>> users = _user.GetUsers();
    myStopWatch.Stop();

    double total = myStopWatch.Elapsed.TotalSeconds;

    return await users;
}

修正StopWatch后的版本:

[HttpGet]
[Route("/user/GetAllUsers")]
[ResponseCache(CacheProfileName = "UserCacheForOneMin")]
public async Task<List<User>> GetAllUsers() 
{
  myStopWatch.Start();
  var users = await _user.GetUsers();
  myStopWatch.Stop();

  double total = myStopWatch.Elapsed.TotalSeconds;

  return users;
}

观察到的现象

  • 性能差异:客户端缓存加载速度比内存缓存更快
  • 数据更新差异:内存缓存过期前返回旧数据,过期后拉取新数据;客户端缓存每次都会查询数据库,但响应时长短于内存缓存

疑问

  1. 客户端缓存的底层工作机制是什么?
  2. 我的客户端缓存实现是否正确?
  3. 为何会出现上述性能与数据更新的差异?

问题解答

1. 客户端缓存的底层工作机制

客户端缓存核心依赖HTTP响应头控制浏览器/客户端存储响应内容,无需服务端维护缓存存储:

  • 当配置ResponseCacheLocation.Client和Duration时,ASP.NET Core会自动在响应头中添加Cache-Control: private, max-age=60(private表示仅客户端可缓存,max-age指定缓存有效期为60秒)
  • 浏览器收到响应后,会将响应内容存入自身本地缓存(如磁盘或内存)
  • 在max-age有效期内,浏览器再次请求该接口时,会直接从本地缓存返回内容,不会发起实际HTTP请求到服务端;只有缓存过期后,才会重新向服务端发起请求

2. 客户端缓存实现的正确性

你的配置部分是正确的,但存在测试逻辑的问题:

  • 接口内的StopWatch仅能反映服务端执行时间,如果浏览器命中客户端缓存,根本不会调用到服务端的接口方法,所以你的计时数据仅代表浏览器未命中缓存时的服务端耗时
  • 另外,app.UseResponseCaching()是服务端响应缓存中间件,对于ResponseCacheLocation.Client的配置来说并非必须(它主要用于服务端层面的响应缓存),但添加后不会影响客户端缓存的正常工作

3. 性能与数据更新差异的原因

性能差异的原因

你观察到的"客户端缓存响应时长短于内存缓存",本质是测试场景的误解:

  • 浏览器首次请求客户端缓存接口时,服务端确实会执行数据库查询,此时耗时应与内存缓存的首次请求相近
  • 后续请求中,若浏览器命中客户端缓存,不会走到服务端接口方法,你看到的"短耗时"其实是浏览器直接返回本地缓存的时间,但你的StopWatch在服务端代码中,这种情况下应该看不到计时输出——如果你的测试中每次都有计时,说明浏览器未命中缓存(比如每次清空了浏览器缓存,或者请求头包含Cache-Control: no-cache等强制跳过缓存的指令)

数据更新差异的原因

  • 内存缓存:数据存储在服务端内存中,只有缓存过期或被主动清除时,服务端才会重新查询数据库,因此过期前会持续返回旧数据
  • 客户端缓存:每个客户端的缓存相互独立,服务端无法主动更新客户端缓存。数据库新增记录后,只有当客户端缓存过期并发起新请求时,才能拿到新数据;如果你的测试中每次都查询数据库,说明浏览器未命中客户端缓存,需检查请求上下文或缓存头配置是否正确

内容的提问来源于stack exchange,提问作者AT-2017

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 06:47:03