【问题标题】:MemoryCache's SlidingExpiration - Optionally slide expiration?MemoryCache 的 SlidingExpiration - 可选择滑动到期?
【发布时间】: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


【解决方案1】:

最好使用单独的类进行内存缓存。这是一个示例。

public class MyMemoryCache
{
    private readonly ObjectCache _cache;

    public MyMemoryCache()
    {
        _cache = MemoryCache.Default;
    }

    /// <summary>
    /// Get an object from cache
    /// </summary>
    /// <param name="cacheKey"></param>
    /// <returns></returns>
    public object Get(string cacheKey)
    {
        return _cache.Get(cacheKey);
    }

    /// <summary>
    /// check if the object is available in the cache
    /// </summary>
    /// <param name="cacheKey"></param>
    /// <returns></returns>
    public bool Contains(string cacheKey)
    {
        return _cache.Contains(cacheKey);
    }

    /// <summary>
    /// Add an objct in to the cache
    /// </summary>
    /// <param name="objectToBeChached"></param>
    /// <param name="cacheKey"></param>
    public void Add(string cacheKey, object objectToBeChached)
    {
        var cacheItemPolicy = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now.AddMinutes(5.0) };
        _cache.Add(cacheKey, objectToBeChached, cacheItemPolicy);
    }
}

【讨论】:

  • 我不明白这对我的问题有何帮助。你能详细说明一下吗?
  • 你看过msdn sample吗?你错过了 MemoryCache.Default。您还需要设置过期策略。
  • 在发布之前,我已经阅读了 MemoryCache 的整个 MSDN 条目。我帖子中的第一个代码 sn-p 引用 MemoryCache.Default 作为示例伪代码,但我的完整代码示例在不使用默认缓存的情况下构建显式 MemoryCache 对象。无论如何...我的问题是关于 SlidingExpiration 交互,这与我是使用默认缓存还是我自己的缓存无关..
  • 你为什么使用 SlidingExpiration ?一旦您知道该问题的答案,您就会得到答案,否则,您可以简单地尝试我的代码。 :)
  • 投反对票是因为完全误解了我的问题并发布了样板回复。 AbsoluteExpiration 与我需要的完全不同。你所做的只是从 MSDN 复制/粘贴,然后问我是否读过它。对措辞恰当的问题投反对票也是相当不成熟的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多