【发布时间】:2016-02-15 16:39:24
【问题描述】:
我正在与MemoryCache 合作。
我已经创建了缓存并使用 5 分钟的滑动到期向其中添加了一个条目:
MemoryCache.Default.Set("Key", "Value", new CacheItemPolicy
{
SlidingExpiration = new TimeSpan(0, 5, 0)
});
如果在 5 分钟内没有访问该条目,它将从缓存中删除。如果它被访问,则滑动到期计时器重置为 5 分钟:
// Resets the sliding expiration:
MemoryCache.Default.Get("Key");
我希望能够有选择地从缓存中检索条目,而无需重置滑动到期计时器。
这似乎不可能,但我想确认一下。
为了澄清我的具体需求:
我有两个实体,Report 和 ReportData。 ReportData 查询速度很慢。 Report 和 ReportData 都缓存在两个单独的 MemoryCache 中。
报告内存缓存在四天后过期。 ReportData MemoryCache 在 30 分钟后过期。
只要 ReportData 自然过期,它就会自动刷新并重新缓存。这可确保所有 ReportData 条目都是最新的。
如果用户在 4 天内未请求报告,则该报告将从缓存中删除,并且相应的 ReportData 也会被删除。每当用户请求报告时,这个 4 天的计时器应该重新启动。
问题是:刷新 ReportData 需要引用 Report。通过缓存获取对 Report 的引用会导致缓存计时器重新启动。这是不希望的。报告缓存计时器应仅在用户请求报告时重新启动。
一个潜在的解决方案是引入第三个缓存。另一个缓存将允许外部和内部访问的不同过期行为。
这是我当前的代码:
/// <summary>
/// A service for caching Custom reports.
/// </summary>
public class ReportCachingService : ServiceBase
{
/// <summary>
/// Refresh report data every N minutes.
/// </summary>
private int ReportDataRefreshInterval { get; set; }
/// <summary>
/// Remember reports for N days.
/// </summary>
private int MaxReportAge { get; set; }
private MemoryCache ReportDataCache { get; set; }
private MemoryCache ReportCache { get; set; }
private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public ReportCachingService()
{
Logger.Info("ReportCachingService initializing...");
LoadConfiguration();
// Note: The name 'ReportDataCache' must be kept in-sync w/ App.config namedCache entry.
ReportDataCache = new MemoryCache("ReportDataCache");
ReportCache = new MemoryCache("ReportCache");
Logger.Info("ReportCachingService successfully started.");
}
public ReportData GetReportData(int reportID)
{
string key = reportID.ToString();
ReportData reportData = ReportDataCache.Get(key) as ReportData ?? GetAndCacheReportData(reportID);
return reportData;
}
public Report GetReport(int reportID)
{
string key = reportID.ToString();
Report report = ReportCache.Get(key) as Report ?? GetAndCacheReport(reportID);
return report;
}
private void LoadConfiguration()
{
try
{
ReportDataRefreshInterval = GetConfigValue("ReportDataRefreshInterval");
MaxReportAge = GetConfigValue("MaxReportAge"); ;
Logger.Info(string.Format("Configuration loaded. Report data will refresh every {0} minutes. Maximum report age is {1} day(s).", ReportDataRefreshInterval, MaxReportAge));
}
catch (Exception exception)
{
Logger.Error("Error loading configuration.", exception);
throw;
}
}
private static int GetConfigValue(string key)
{
string configValueString = ConfigurationManager.AppSettings[key];
if (string.IsNullOrEmpty(configValueString))
{
throw new Exception(string.Format("Failed to find {0} in App.config", key));
}
int configValue;
bool isValidConfigValue = int.TryParse(configValueString, out configValue);
if (!isValidConfigValue)
{
throw new Exception(string.Format("{0} was found in App.config, but is not a valid integer value.", key));
}
return configValue;
}
private ReportData GetAndCacheReportData(int reportID)
{
Report report = GetReport(reportID);
ReportData reportData = report.GetData(false, "Administrator");
if (reportData == null)
{
string errorMessage = string.Format("Failed to find reportData for report with ID: {0}", reportID);
Logger.Error(errorMessage);
throw new Exception(errorMessage);
}
// SlidingExpiration forces cache expiration to refresh when an entry is accessed.
TimeSpan reportDataCacheExpiration = new TimeSpan(0, ReportDataRefreshInterval, 0);
string key = reportID.ToString();
ReportDataCache.Set(key, reportData, new CacheItemPolicy
{
SlidingExpiration = reportDataCacheExpiration,
UpdateCallback = OnReportDataUpdate
});
return reportData;
}
private Report GetAndCacheReport(int reportID)
{
// If the ReportCache does not contain the Report - attempt to load it from DB.
Report report = Report.Load(reportID);
if (report == null)
{
string errorMessage = string.Format("Failed to find report with ID: {0}", reportID);
Logger.Error(errorMessage);
throw new Exception(errorMessage);
}
// SlidingExpiration forces cache expiration to refresh when an entry is accessed.
TimeSpan reportCacheExpiration = new TimeSpan(MaxReportAge, 0, 0, 0);
string key = reportID.ToString();
ReportCache.Set(key, report, new CacheItemPolicy
{
SlidingExpiration = reportCacheExpiration,
RemovedCallback = OnReportRemoved
});
return report;
}
private void OnReportRemoved(CacheEntryRemovedArguments arguments)
{
Logger.DebugFormat("Report with ID {0} has expired with reason: {1}.", arguments.CacheItem.Key, arguments.RemovedReason);
// Clear known ReportData for a given Report whenever the Report expires.
ReportDataCache.Remove(arguments.CacheItem.Key);
}
private void OnReportDataUpdate(CacheEntryUpdateArguments arguments)
{
Logger.DebugFormat("ReportData for report with ID {0} has updated with reason: {1}.", arguments.UpdatedCacheItem.Key, arguments.RemovedReason);
// Expired ReportData should be automatically refreshed by loading fresh values from the DB.
if (arguments.RemovedReason == CacheEntryRemovedReason.Expired)
{
int reportID = int.Parse(arguments.Key);
GetAndCacheReportData(reportID);
}
}
}
【问题讨论】:
-
我不认为你可以通过这种方式使用内存缓存来实现你想要的。请参阅下面的代码。
-
这是可能的。您在这里看到的是 SlidingExpiration 的行为。您必须改用 AbsoluteExpiration。
标签: c# caching memorycache