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

基于C#缓存处理大数据:新旧缓存无缝切换方案问询

实现缓存平滑更新:海量数据加载时保留旧缓存方案

这个场景我太熟悉了——处理几十万级数据的缓存更新,最怕的就是加载过程中前端卡壳或者拿到半截数据。核心要做到的就是「后台异步刷新,前端无缝衔接」,下面给你一套落地的方案,分传统ASP.NET和ASP.NET Core两种情况:

核心思路

  • 用异步任务在后台加载新数据,完全不阻塞主线程和用户请求
  • 缓存存储的是完整的数据实例,新数据加载完成后原子性替换旧实例,保证前端永远拿到完整的有效数据
  • 加并发控制,避免同一时间多个线程重复触发数据库查询(比如半小时刷新点同时有N个请求进来,不能都去查库)
  • 定时任务用可靠的方式实现,避免因线程池问题导致刷新延迟

传统ASP.NET(基于HttpContext.Current.Cache)实现

我们可以封装一个静态缓存管理类,用线程安全的方式处理缓存更新:

public static class BookCacheManager
{
    // 存储当前有效的缓存数据,volatile保证多线程可见性
    private static volatile List<Book> _currentBookData;
    // 锁对象,控制并发刷新
    private static readonly object _lockObj = new object();
    // 定时刷新计时器
    private static Timer _refreshTimer;

    static BookCacheManager()
    {
        // 程序启动时先加载一次初始缓存
        _currentBookData = LoadBookDataFromDb();
        // 启动定时任务:立即执行一次,之后每半小时刷新
        _refreshTimer = new Timer(RefreshCacheAsync, null, TimeSpan.Zero, TimeSpan.FromMinutes(30));
    }

    // 对外暴露的获取缓存方法,无阻塞,直接返回当前有效数据
    public static List<Book> GetBookData()
    {
        return _currentBookData;
    }

    private static async void RefreshCacheAsync(object state)
    {
        // 双重检查锁定:避免多个线程同时触发刷新
        if (Monitor.TryEnter(_lockObj))
        {
            try
            {
                // 异步加载新数据,不阻塞线程池
                var newBookData = await LoadBookDataFromDbAsync();
                // 原子性替换旧缓存:这一步是瞬间完成的,前端不会拿到不完整数据
                _currentBookData = newBookData;
                Debug.WriteLine("书籍缓存已成功更新");
            }
            catch (Exception ex)
            {
                // 关键:加载失败时保留旧缓存,绝对不能清空或设为null
                Debug.WriteLine($"缓存更新失败,保留旧数据:{ex.Message}");
            }
            finally
            {
                Monitor.Exit(_lockObj);
            }
        }
        else
        {
            Debug.WriteLine("已有线程正在更新缓存,本次刷新跳过");
        }
    }

    // 同步加载数据(初始化用)
    private static List<Book> LoadBookDataFromDb()
    {
        // 替换成你的实际数据库查询逻辑
        using (var conn = new SqlConnection("你的数据库连接字符串"))
        {
            conn.Open();
            return conn.Query<Book>("SELECT * FROM Tab1").ToList();
        }
    }

    // 异步加载数据(推荐用异步避免阻塞)
    private static async Task<List<Book>> LoadBookDataFromDbAsync()
    {
        using (var conn = new SqlConnection("你的数据库连接字符串"))
        {
            await conn.OpenAsync();
            return (await conn.QueryAsync<Book>("SELECT * FROM Tab1")).ToList();
        }
    }
}

前端使用方式:直接调用BookCacheManager.GetBookData()即可,不管后台有没有在刷新,拿到的都是完整的旧数据,直到新数据加载完成后自动切换。


ASP.NET Core 版本(更推荐,替代HttpContext.Cache)

在Core里,我们用IMemoryCache结合BackgroundService实现定时刷新,框架托管的服务可靠性更高:

1. 创建后台缓存刷新服务

public class BookCacheRefreshService : BackgroundService
{
    private readonly IMemoryCache _cache;
    private readonly ILogger<BookCacheRefreshService> _logger;
    private const string CacheKey = "BookData";

    public BookCacheRefreshService(IMemoryCache cache, ILogger<BookCacheRefreshService> logger)
    {
        _cache = cache;
        _logger = logger;
    }

    // 初始化缓存(程序启动时执行)
    public async Task InitializeCacheAsync()
    {
        var initialData = await LoadBookDataFromDbAsync();
        _cache.Set(CacheKey, initialData);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 先加载初始缓存
        await InitializeCacheAsync();

        // 定时刷新循环
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                // 异步加载新数据
                var newBookData = await LoadBookDataFromDbAsync();
                // 原子性替换缓存,Core的IMemoryCache.Set是线程安全的
                _cache.Set(CacheKey, newBookData);
                _logger.LogInformation("书籍缓存已成功更新");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "缓存更新失败,保留旧数据");
            }

            // 等待半小时,同时响应程序终止信号
            await Task.Delay(TimeSpan.FromMinutes(30), stoppingToken);
        }
    }

    private async Task<List<Book>> LoadBookDataFromDbAsync()
    {
        // 实际的数据库异步查询逻辑
        using (var conn = new SqlConnection("你的数据库连接字符串"))
        {
            await conn.OpenAsync();
            return (await conn.QueryAsync<Book>("SELECT * FROM Tab1")).ToList();
        }
    }
}

2. 注册服务(Program.cs)

builder.Services.AddHostedService<BookCacheRefreshService>();
builder.Services.AddMemoryCache();

3. 控制器中使用缓存

public class BookController : Controller
{
    private readonly IMemoryCache _cache;
    private const string CacheKey = "BookData";

    public BookController(IMemoryCache cache)
    {
        _cache = cache;
    }

    public IActionResult Index()
    {
        // 直接获取缓存,永远拿到完整有效数据
        var bookData = _cache.Get<List<Book>>(CacheKey);
        return View(bookData);
    }
}

关键细节提醒

  • 原子性替换:不管是用私有字段还是IMemoryCache.Set,替换缓存的操作都是瞬间完成的,前端绝对不会看到半加载的状态
  • 异常容错:数据库查询失败时,一定要保留旧缓存,不能让用户看到空数据或错误页面
  • 异步优先:海量数据加载必须用异步方法,避免阻塞线程池,保证系统整体响应性
  • 并发控制:传统ASP.NET用双重检查锁定避免重复查询,Core的后台服务是单线程循环,天然不会重复触发

内容的提问来源于stack exchange,提问作者Bilal Ahmed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:36:20