1

我们有一个 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 检查,看看这是否是罪魁祸首。

4

3 回答 3

2

我不知道这是否是您问题的根源,但我看到您有一个由多个线程调用您的GetAllSummaries方法引起的竞争条件。

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

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

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

更重要的是:

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

(重点补充)

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

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

  1. 制作一个对象来包装缓存,以使您对缓存线程安全的多次调用。
  2. 使用双重检查锁定模式来确保只允许一个线程通过来获取数据。
  3. 使用缓存包装器的静态实例,确保所有线程使用相同的线程锁定代码。

摘要缓存

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());
}

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

于 2015-09-22T20:59:41.827 回答
1

HttpContext.Cache 仅在应用程序的生命周期内可用,并且该生命周期与 IIS 应用程序池相关联。当应用程序池回收或终止时,缓存将被清除。不幸的是,您无法控制 IIS 何时执行此操作。在我的脑海中,我可以看到 2 个选项或(可能两者兼而有之)。

  1. 将缓存存储在本地数据库中。仍然会比内存缓存慢,但可能比 Web 服务快。
  2. 在应用程序启动时(在 Global.asax 文件的函数内)重新加载缓存,Application_Start()而不是定期 ping(取决于您的要求)。
于 2015-09-22T19:43:12.323 回答
0

抱歉,如果这个答案没有帮助,但我以前见过类似的问题,所以我想我会发布。

当记录器(在本例中为 nlog)实际写入应用程序目录,特别是 bin 文件夹时,我已经看到了一个问题。通过这样做,它实际上会触发网站重新加载其域逻辑,从而导致会话和缓存数据无效。

再次抱歉,如果这对您没有帮助,但我认为值得一提。

于 2015-09-22T19:54:32.140 回答