caching - LazyCache:定期刷新缓存项
问题描述
我正在使用LazyCache 并希望每小时刷新一次缓存,但理想情况下,我希望缓存项过期后的第一个调用者不要等待缓存重新加载。我写了以下
public async Task<List<KeyValuePair<string, string>>> GetCarriersAsync()
{
var options = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = new TimeSpan(1,0,0),// consider to config
}.RegisterPostEvictionCallback(
async (key, value, reason, state) =>
{
await GetCarriersAsync();//will save to cache
_logger.LogInformation("Carriers are reloaded: " );
});
Func<Task<List<KeyValuePair<string, string>>>> cacheableAsyncFunc = () => GetCarriersFromApi();
var cachedCarriers = await _cache.GetOrAddAsync($"Carriers", cacheableAsyncFunc, options);
return cachedCarriers;
}
然而,当缓存项过期时不会调用 RegisterPostEvictionCallback,而只会在对该项的下一个请求发生时调用(并且调用者需要等待一个冗长的操作)。
线程 Expiration 几乎从未在后台自行发生 #248解释这是设计使然,并建议解决方法来指定 CancellationTokenSource.CancelAfter(TimeSpan.FromHours(1)) 而不是 SetAbsoluteExpiration。
不幸的是 LazyCache.GetOrAddAsync 没有 CancellationToken 作为参数。在预定时间触发重新加载缓存的最佳方法是第一个用户的等待时间最短?
解决方案
我发现了类似的问题In-Memory Caching with auto-regeneration on ASP.Net Core建议调用
AddExpirationToken(new CancellationChangeToken(new CancellationTokenSource(_options.ReferenceDataRefreshTimeSpan).Token)
.
我试过了,但没有让它工作。但是,相同的答案通过使用计时器具有替代(和推荐)选项。我创建了一个 RefreshebleCache 类,用于不同的可缓存选项,如下所示:
var refreshebleCache = new RefreshebleCache<MyCashableObjectType>(_cache, _logger);
Task<MyCashableObjectType> CacheableAsyncFunc() => GetMyCashableObjectTypeFromApiAsync();
var cachedResponse = await refreshebleCache.GetOrAddAsync("MyCashableObject", CacheableAsyncFunc,
_options.RefreshTimeSpan);
RefreshebleCache 实现:
/// <summary>
/// Based on https://stackoverflow.com/questions/44723017/in-memory-caching-with-auto-regeneration-on-asp-net-core
/// </summary>
/// <typeparam name="T"></typeparam>
public class RefreshebleCache<T>
{
protected readonly IAppCache _cache;
private readonly ILogger _logger;
public bool LoadingBusy = false;
private string _cacheKey;
private TimeSpan _refreshTimeSpan;
private Func<Task<T>> _functionToLoad;
private Timer _timer;
public RefreshebleCache(IAppCache cache, ILogger logger)
{
_cache = cache;
_logger = logger;
}
public async Task<T> GetOrAddAsync (string cacheKey , Func<Task<T>> functionToLoad, TimeSpan refreshTimeSpan)
{
_refreshTimeSpan= refreshTimeSpan;
_functionToLoad = functionToLoad;
_cacheKey = cacheKey;
var timerCachedKey = "Timer_for_"+cacheKey;
//if removed from cache, _timer could continue to work, creating redundant calls
_timer = _appCache.GetOrAdd(timerCachedKey, () =>
CreateTimer(refreshTimeSpan),
SetMemoryCacheEntryOptions(CacheItemPriority.NeverRemove));
var cachedValue = await LoadCacheEntryAsync();
return cachedValue;
}
private Timer CreateTimer(TimeSpan refreshTimeSpan)
{
Debug.WriteLine($"calling CreateTimer for {_cacheKey} refreshTimeSpan {refreshTimeSpan}"); //start first time in refreshTimeSpan
return new Timer(TimerTickAsync, null, refreshTimeSpan, refreshTimeSpan);
}
private async void TimerTickAsync(object state)
{
if (LoadingBusy) return;
try
{
LoadingBusy = true;
Debug.WriteLine($"calling LoadCacheEntryAsync from TimerTickAsync for {_cacheKey}");
var loadingTask = LoadCacheEntryAsync(true);
await loadingTask;
}
catch(Exception e)
{
_logger.LogWarning($" {nameof(T)} for {_cacheKey} was not reloaded. {e} ");
}
finally
{
LoadingBusy = false;
}
}
private async Task<T> LoadCacheEntryAsync(bool update=false)
{
var cacheEntryOptions = SetMemoryCacheEntryOptions();
Func<Task<T>> cacheableAsyncFunc = () => _functionToLoad();
Debug.WriteLine($"called LoadCacheEntryAsync for {_cacheKey} update:{update}");
T cachedValues = default(T);
if (update)
{
cachedValues =await cacheableAsyncFunc();
if (cachedValues != null)
{
_cache.Add(_cacheKey, cachedValues, cacheEntryOptions);
}
// _cache.Add(_cacheKey, cacheableAsyncFunc, cacheEntryOptions);
}
else
{
cachedValues = await _cache.GetOrAddAsync(_cacheKey, cacheableAsyncFunc, cacheEntryOptions);
}
return cachedValues;
}
private MemoryCacheEntryOptions SetMemoryCacheEntryOptions(CacheItemPriority priority= CacheItemPriority.Normal)
{
var cacheEntryOptions = new MemoryCacheEntryOptions
{
Priority = priority
};
return cacheEntryOptions;
}
}
}
推荐阅读
- javascript - 如何使用 for 循环创建打字效果
- php - 属于Parent模型的三个模型的Laravel 8关系
- angular-cli - ng12 更新指令产生损坏的应用程序
- json - Best Buy Developer API 最大页面大小和分页
- python - 没有这样的列:dashboard_book.id django
- sql - 优化更新第一个、最后一个、倒数第二个排名值
- android - Android Kotlin - viewBinding 类型不匹配:推断类型为 DrawerLayout 但预期为 ConstraintLayout
- excel - 如何用百分比和空白单元格做 SUMPRODUCT
- python - Cython 如何让 help() 描述方法
- java - 我们可以创建两个具有相同端口号的套接字吗