我们有一个Web应用程序,它从SOAP Web服务检索很多数据,大约需要4到5分钟的时间。
为了确保用户不会对此感到困扰,数据将按照以下方式进行缓存:

//using Nlog to log caching behaviour
private Logger log = LogManager.GetCurrentClassLogger();
//a public actionresult to get the summaries with an ajax call or site24x7 service
public ActionResult GetSummariesAsync()
{
  log.Info(String.Format("GetSummariesAsync called"));
  List<ProjectDataSummary> summaries = GetAllSummaries();
  log.Info(String.Format("{0} summaries found", summaries.Count));
  List<ProjectDataSummary> cache = HttpContext.Cache["SummariesCache"] as List<ProjectDataSummary>;
  log.Info(String.Format("{0} in cache", cache.Count));

  return Json(summaries, JsonRequestBehavior.AllowGet);
}
private List<ProjectDataSummary> GetAllSummaries()
{
  List<ProjectDataSummary> summaries = new List<ProjectDataSummary>();
  //use setting in web.config if we want to force no cache, but set to false in released version
  if (ConfigurationManager.AppSettings["NoCache"] == "true")
  {
    summaries = _service.GetAllSummaries();
  }
  else
  {
    //get summaries from cache if available
    summaries = HttpContext.Cache["SummariesCache"] as List<ProjectDataSummary>;
    if (summaries == null)
    {
      //cache empty, retrieve values
      summaries = _service.GetAllSummaries();
      //cache it
      HttpContext.Cache.Add("SummariesCache", summaries, null, new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 23, 59, 59), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(this.cacheCallback));
    }
  }
  return summaries;
}
private void cacheCallback(String K, Object v, CacheItemRemovedReason r)
{
  CacheItemRemovedReason reason = r;
  log.Info("Cache expired, reason: {0}", r.ToString());
}


我有一个来自www.site24x7.com的服务,它每小时都会创建一次缓存,因此从理论上讲,它应该在凌晨0:00之前达到目标,并使其创建另一个缓存。但是由于某种原因,缓存将被删除,并且站点每天都会创建几次新的缓存,这也给用户带来了非常长的加载时间。

这是我的Nlog日志的一部分:

2015-09-22 18:31:07.8746 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 18:31:07.8901 GetSummariesAsync Info 263 summaries
2015-09-22 18:31:07.8901 GetSummariesAsync Info 263 in cache
2015-09-22 18:52:15.9684 cacheCallback Info Cache expired, reason: Removed
2015-09-22 19:02:06.9839 cacheCallback Info Cache expired, reason: Removed
2015-09-22 19:23:08.3590 cacheCallback Info Cache expired, reason: Removed
2015-09-22 19:31:42.7182 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 19:32:15.0776 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 19:32:46.2495 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 19:33:01.8276 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 19:33:18.3746 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 19:33:34.8589 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 19:34:10.7182 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 19:34:15.4215 GetSummariesAsync Info 263 summaries
2015-09-22 19:34:15.4215 GetSummariesAsync Info 263 in cache
2015-09-22 19:35:07.3121 GetSummariesAsync Info 263 summaries
2015-09-22 19:35:07.3121 GetSummariesAsync Info 263 in cache
2015-09-22 19:36:07.9213 GetSummariesAsync Info 263 summaries
2015-09-22 19:36:07.9213 GetSummariesAsync Info 263 in cache
2015-09-22 19:36:19.5309 GetSummariesAsync Info 263 summaries
2015-09-22 19:36:19.5309 GetSummariesAsync Info 263 in cache
2015-09-22 19:36:36.3588 GetSummariesAsync Info 263 summaries
2015-09-22 19:36:36.3588 GetSummariesAsync Info 263 in cache
2015-09-22 19:37:07.9996 GetSummariesAsync Info 263 summaries
2015-09-22 19:37:07.9996 GetSummariesAsync Info 263 in cache
2015-09-22 19:37:33.7183 GetSummariesAsync Info 263 summaries
2015-09-22 19:37:33.7183 GetSummariesAsync Info 263 in cache
2015-09-22 19:37:59.8747 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 19:37:59.8747 GetSummariesAsync Info 263 summaries
2015-09-22 19:37:59.8747 GetSummariesAsync Info 263 in cache
2015-09-22 19:44:00.2496 cacheCallback Info Cache expired, reason: Removed
2015-09-22 19:54:25.2183 cacheCallback Info Cache expired, reason: Removed
2015-09-22 20:34:55.3589 GetSummariesAsync Info GetSummariesAsync called
2015-09-22 20:34:55.3745 GetSummariesAsync Info 263 summaries
2015-09-22 20:34:55.3745 GetSummariesAsync Info 263 in cache
2015-09-22 20:44:32.5153 cacheCallback Info Cache expired, reason: Removed
2015-09-22 20:56:38.8746 cacheCallback Info Cache expired, reason: Removed




编辑

我已经实现了NightOwl888给出的答案,但无济于事,缓存一直被删除。

这是我当前的Nlog文件中的一部分:

2015-09-27 20:05:29.9682 Controllers.HomeController.GetSummariesAsync Info 263 summaries found
2015-09-27 20:05:29.9682 Controllers.HomeController.GetSummariesAsync Info 263 in cache found
2015-09-27 20:23:51.5464 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 20:23:51.5464 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 20:23:51.5464 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 20:25:48.1714 Models.CachingBase`1.GetItemsCache Info Cache fileEntries created
2015-09-27 20:25:48.1714 Models.CachingBase`1.GetItemsCache Info Cache idCertificaat created
2015-09-27 20:26:46.3588 Controllers.HomeController.Index Info No cache found, Home/Index needs to create the cash Async
2015-09-27 20:26:46.7963 Controllers.HomeController.GetSummariesAsync Info GetSummariesAsync triggered by User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0 vanuit Host: 88.159.95.251
2015-09-27 20:27:26.9682 Models.CachingBase`1.GetItemsCache Info Cache GebouwProjectenSummaries created
2015-09-27 20:27:26.9682 Controllers.HomeController.GetSummariesAsync Info 263 summaries found
2015-09-27 20:27:26.9682 Controllers.HomeController.GetSummariesAsync Info 263 in cache found
2015-09-27 20:33:48.7340 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 20:33:48.7340 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 20:33:48.7340 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 20:38:21.0151 Models.CachingBase`1.GetItemsCache Info Cache fileEntries created
2015-09-27 20:38:21.0151 Models.CachingBase`1.GetItemsCache Info Cache idCertificaat created
2015-09-27 20:38:21.0620 Controllers.HomeController.Index Info No cache found, Home/Index needs to create the cash Async
2015-09-27 20:43:41.0620 Controllers.HomeController.Index Info No cache found, Home/Index needs to create the cash Async
2015-09-27 20:46:29.0620 Models.CachingBase`1.GetItemsCache Info Cache GebouwProjectenSummaries created
2015-09-27 20:57:21.5776 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 20:57:21.5776 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 20:57:21.5776 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 20:58:23.9553 Models.CachingBase`1.GetItemsCache Info Cache fileEntries created
2015-09-27 20:58:23.9553 Models.CachingBase`1.GetItemsCache Info Cache idCertificaat created
2015-09-27 20:58:23.9839 Controllers.HomeController.Index Info No cache found, Home/Index needs to create the cash Async
2015-09-27 21:04:24.5151 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 21:04:24.5151 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 21:05:31.8277 Models.CachingBase`1.GetItemsCache Info Cache fileEntries created
2015-09-27 21:05:31.8277 Models.CachingBase`1.GetItemsCache Info Cache idCertificaat created
2015-09-27 21:05:31.8432 Controllers.HomeController.GetSummariesAsync Info GetSummariesAsync triggered by User-Agent: Site24x7 vanuit Host: 72.5.230.111
2015-09-27 21:06:03.5307 Controllers.HomeController.GetSummariesAsync Info GetSummariesAsync triggered by User-Agent: Site24x7 vanuit Host: 72.5.230.111
2015-09-27 21:06:36.3433 Controllers.HomeController.GetSummariesAsync Info GetSummariesAsync triggered by User-Agent: Site24x7 vanuit Host: 120.138.27.125
2015-09-27 21:06:40.2651 Controllers.HomeController.GetSummariesAsync Info GetSummariesAsync triggered by User-Agent: Site24x7 vanuit Host: 119.81.237.98
2015-09-27 21:07:08.6401 Controllers.HomeController.GetSummariesAsync Info GetSummariesAsync triggered by User-Agent: Site24x7 vanuit Host: 120.138.27.125
2015-09-27 21:07:11.4057 Models.CachingBase`1.GetItemsCache Info Cache GebouwProjectenSummaries created
2015-09-27 21:07:11.4057 Controllers.HomeController.GetSummariesAsync Info 263 summaries found
2015-09-27 21:07:11.4057 Controllers.HomeController.GetSummariesAsync Info 263 summaries found
2015-09-27 21:07:11.4057 Controllers.HomeController.GetSummariesAsync Info 263 in cache found
2015-09-27 21:07:11.4057 Controllers.HomeController.GetSummariesAsync Info 263 in cache found
2015-09-27 21:07:11.4214 Controllers.HomeController.GetSummariesAsync Info 263 summaries found
2015-09-27 21:07:11.4214 Controllers.HomeController.GetSummariesAsync Info 263 in cache found
2015-09-27 21:07:11.4214 Controllers.HomeController.GetSummariesAsync Info 263 summaries found
2015-09-27 21:07:11.4214 Controllers.HomeController.GetSummariesAsync Info 263 in cache found
2015-09-27 21:07:11.4214 Controllers.HomeController.GetSummariesAsync Info 263 summaries found
2015-09-27 21:07:11.4214 Controllers.HomeController.GetSummariesAsync Info 263 in cache found
2015-09-27 21:07:12.5620 Controllers.HomeController.GetSummariesAsync Info GetSummariesAsync triggered by User-Agent: Site24x7 vanuit Host: 119.81.237.98
2015-09-27 21:07:12.5620 Controllers.HomeController.GetSummariesAsync Info 263 summaries found
2015-09-27 21:07:12.5620 Controllers.HomeController.GetSummariesAsync Info 263 in cache found
2015-09-27 21:15:32.4370 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 21:15:32.4370 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed
2015-09-27 21:15:32.4370 Controllers.HomeController.cacheCallback Info Cache expired, reason: Removed


您会看到,Site24x7有时连续多次调用GetSummariesAsync函数。

而且用户(尽管很少)也遇到问题。

从创建现金到取出现金的时间可以短至8分钟。

我现在禁用了site24x7检查,以查看是否是罪魁祸首。

最佳答案

我不知道这是否是问题的根源,但我发现您有一个竞争状况,该竞争状况是由多个线程调用您的GetAllSummaries方法引起的。

当多个线程正在填充高速缓存时(由于过多的条目由于冲突而被删除),并且由于多个线程在填充高速缓存之前争用系统资源而导致用户仅遇到问题时,您可能会得到错误的日志条目。

首先,看一下Cache.Add方法的文档:


  如果具有相同键参数的项目已存储在缓存中,则对该方法的调用将失败。要使用相同的键参数覆盖现有的Cache项目,请使用Insert方法。


more importantly


  如果使用Add方法,并且缓存中已经存在具有相同名称的项目,则该方法将不会替换该项目,并且不会引发异常。


(强调)

此外,您的缓存检查代码也不是线程安全的。多个线程可以运行GetAllSummaries方法,因为在第一个调用返回之前,缓存将没有值。而且由于Add方法不会由于重复而引发异常,所以这种工作只是浪费了。

您可以通过以下方法解决此问题:


创建一个用于包装高速缓存的对象,以对线程进行多次安全调用。
使用double-checked locking pattern来确保只允许一个线程来获取数据。
使用缓存包装器的静态实例,以确保所有线程使用相同的线程锁定代码。


SummariesCache

public sealed class SummariesCache
{
    private ReaderWriterLockSlim synclock =
        new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

    public List<ProjectSummaryData> GetSummaries(
        ISummariesService service,
        ref CacheItemRemovedCallback onRemoveCallback)
    {
        List<ProjectDataSummary> summaries;
        string key = "SummariesCache";
        bool success;

        synclock.EnterReadLock();
        try
        {
            success = TryGetCacheValue(key, out summaries);
        }
        finally
        {
            synclock.ExitReadLock();
        }

        if (!success)
        {
            synclock.EnterWriteLock();
            try
            {
                if (!TryGetCacheValue(key, out summaries))
                {
                    //cache empty, retrieve values
                    summaries = service.GetAllSummaries();

                    // load the cache (using Insert)
                    HttpContext.Current.Cache.Insert(
                        key,
                        summaries,
                        null,
                        new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 23, 59, 59),
                        Cache.NoSlidingExpiration,
                        CacheItemPriority.NotRemovable,
                        onRemoveCallback
                    );
                }
            }
            finally
            {
                synclock.ExitWriteLock();
            }
        }

        return summaries;
    }

    private bool TryGetCacheValue(string key, out List<ProjectSummaryData> value)
    {
        value = HttpContext.Current.Cache["key"] as List<ProjectDataSummary>;
        if (value != null)
        {
            return true;
        }
        return false;
    }
}


用法

// Use a static instance of the cache to ensure all threads use it.
private static _summariesCache = new SummariesCache();

private List<ProjectDataSummary> GetAllSummaries()
{
    List<ProjectDataSummary> summaries = new List<ProjectDataSummary>();
    //use setting in web.config if we want to force no cache, but set to false in released version
    if (ConfigurationManager.AppSettings["NoCache"] == "true")
    {
        summaries = _service.GetAllSummaries();
    }
    else
    {
        summaries = _summariesCache.GetSummaries(_service, new CacheItemRemovedCallback(cacheCallback));
    }
    return summaries;
}

private void cacheCallback(String K, Object v, CacheItemRemovedReason r)
{
    CacheItemRemovedReason reason = r;
    log.Info("Cache expired, reason: {0}", r.ToString());
}



  注意:此代码大部分是从Micro-Caching in .NET借用的。如果愿意,可以使用该解决方案。

08-06 18:58