.NET Core中IMemoryCache定时刷新与闲置缓存清理方案咨询
Hey Dave, 刚好我之前处理过类似的缓存刷新需求,针对你用IMemoryCache缓存Web API数据、需要每小时精准失效的场景,给你几个靠谱的解决方案:
方案1:绝对过期+主动预刷新(兼顾性能和实时性)
滑动过期的问题确实如你所说——如果用户持续访问,缓存会一直续命,导致拿到 stale 数据。所以首先要用绝对过期时间替代滑动过期,确保缓存最多存活1小时。同时为了避免用户请求刚好碰到缓存过期时的延迟,可以在缓存快要失效时主动异步刷新。
示例代码:
public async Task<ApiResponse> GetCachedApiDataAsync(IMemoryCache cache) { const string cacheKey = "HourlyApiData"; // 尝试读取缓存 if (cache.TryGetValue(cacheKey, out ApiResponse cachedData)) { // 检查缓存剩余有效期,小于5分钟则后台异步刷新 var cacheEntry = cache.GetCacheEntry(cacheKey); var timeLeft = cacheEntry.AbsoluteExpiration.Value - DateTimeOffset.UtcNow; if (timeLeft.TotalMinutes < 5) { // 用_ = 避免阻塞当前请求 _ = RefreshCacheAsync(cache); } return cachedData; } // 缓存不存在时,直接获取新数据并设置1小时绝对过期 var freshData = await FetchDataFromSourceAsync(); cache.Set(cacheKey, freshData, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) }); return freshData; } private async Task RefreshCacheAsync(IMemoryCache cache) { const string cacheKey = "HourlyApiData"; var freshData = await FetchDataFromSourceAsync(); cache.Set(cacheKey, freshData, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) }); }
方案2:后台定时任务强制刷新(精准控制过期时间)
如果需要完全确保到点就更新缓存,不管有没有用户访问,可以用.NET的BackgroundService写一个定时任务,每小时主动刷新缓存。这种方式最直接,能彻底避免 stale 数据。
示例代码:
// 定时刷新缓存的后台服务 public class HourlyCacheRefreshService : BackgroundService { private readonly IMemoryCache _cache; private readonly IServiceScopeFactory _scopeFactory; private readonly TimeSpan _refreshInterval = TimeSpan.FromHours(1); public HourlyCacheRefreshService(IMemoryCache cache, IServiceScopeFactory scopeFactory) { _cache = cache; _scopeFactory = scopeFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 启动时先刷新一次,避免初始缓存为空 await RefreshCache(); while (!stoppingToken.IsCancellationRequested) { await Task.Delay(_refreshInterval, stoppingToken); await RefreshCache(); } } private async Task RefreshCache() { // 创建作用域获取API服务(避免长生命周期服务注入问题) using var scope = _scopeFactory.CreateScope(); var apiService = scope.ServiceProvider.GetRequiredService<IApiDataService>(); var freshData = await apiService.FetchDataFromSourceAsync(); _cache.Set("HourlyApiData", freshData, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) }); } }
然后在Program.cs里注册这个后台服务:
builder.Services.AddHostedService<HourlyCacheRefreshService>();
方案3:自定义缓存项监控与手动失效(高度可控)
如果需要更灵活的缓存监控(比如记录所有缓存项的过期时间、手动触发失效),可以维护一个缓存元数据字典,定时扫描并移除过期的缓存项。这种方式适合需要精细化管理缓存的场景。
示例代码:
// 用于记录缓存项的过期时间 private readonly ConcurrentDictionary<string, DateTimeOffset> _cacheExpiryTracker = new(); public void SetTrackedCache(IMemoryCache cache, string key, object value, TimeSpan absoluteExpiration) { var expiryTime = DateTimeOffset.UtcNow.Add(absoluteExpiration); cache.Set(key, value, new MemoryCacheEntryOptions { AbsoluteExpiration = expiryTime }); _cacheExpiryTracker.TryAdd(key, expiryTime); } // 定时扫描并清理过期缓存的方法 public async Task ScanAndCleanExpiredCache(IMemoryCache cache, CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); // 每5分钟扫描一次 var now = DateTimeOffset.UtcNow; // 找出所有过期的缓存键 var expiredKeys = _cacheExpiryTracker .Where(kv => kv.Value <= now) .Select(kv => kv.Key) .ToList(); foreach (var key in expiredKeys) { cache.Remove(key); _cacheExpiryTracker.TryRemove(key, out _); } } }
最后总结
- 优先推荐方案1+方案2结合:绝对过期保证缓存不会无限续命,后台定时任务确保到点必更,主动预刷新提升用户体验。
- 滑动过期完全不适合你这种有固定更新周期的场景,一定要避免使用。
内容的提问来源于stack exchange,提问作者Dave Morrison
相关产品推荐
相关产品推荐

