【问题标题】:Is caching results by querying every 10 seconds a bad Idea?通过每 10 秒查询一次来缓存结果是一个坏主意吗?
【发布时间】:2016-09-28 00:18:23
【问题描述】:

我们有一种情况,我们在 API 上调用一个相当昂贵的函数。让我们称之为API.ExpensiveCall()

这个ExpensiveCall() 在Web 应用程序中经常被调用。虽然对于一个用户来说并不明显,但当您同时有 5 个左右的用户时,它就会变得很明显。

我们想要缓存这些结果。但是由于API.ExpensiveCall() 对我们来说本质上是一个黑盒子,所以我们无法使缓存失效,也无法知道何时刷新。

所以我们建议的解决方案是创建一个 Windows 服务,它会简单地每十秒调用一次 API.ExpensiveCall(),然后将结果保存到 Web 应用程序已经在使用的本地数据库中。

这样,无论网站上有 1 个用户还是 20 多个用户,ExpensiveCall() 只会每 10 秒调用一次。在API.ExpensiveCall() 所连接的外部系统上,这是一个非常受控的负载。

问题

我们的项目经理不同意这一点。出于某种原因,他认为每 10 秒定时刷新是一个坏主意,因为在他看来会给外部系统带来过多的负载。

但是,如果我们不采取任何措施,并且在没有任何缓存的情况下保持原样,它不仅会降低 Web 应用程序的性能,而且肯定会导致每秒超过一次 ExpensiveCall在外部系统上。这个数字会随着 Web 应用程序上的用户数量而增加。

我想问你这种缓存方式真的是个坏主意吗?您是否听说过其他系统使用这种方法进行缓存?如果这是一个坏主意,当系统对您来说是一个黑盒子时,是否还有其他更好的方法来缓存系统结果?

编辑:

您的回复似乎表明我应该使用 ASP.Net 的内存缓存机制的超时功能。

我喜欢超时的想法。我现在看到的唯一(小)问题是,当超时到期并且是时候调用 ExpensiveCall() 时,这将是一个阻塞调用。与查询本地表相反,本地表通过在单独的进程中不断刷新来保持最新。这是我觉得投票想法很有吸引力的地方。虽然我必须承认每 10 秒轮询一次确实感觉很奇怪,这就是我对它持谨慎态度的原因。

【问题讨论】:

    标签: asp.net caching


    【解决方案1】:

    您的项目经理可能是对的:当连续五分钟没有请求时,您仍然会进行轮询,从而导致对昂贵函数的 30 次不必要的调用。

    解决这些问题的方式通常是这样的:每当您需要来自昂贵调用的数据时,您就检查缓存。如果不存在,则调用昂贵的函数并将数据存储在缓存中,并添加时间戳。如果是,那么您检查数据的年龄(因此是时间戳)。如果它超过 10 秒,无论如何调用昂贵的函数,并更新你的缓存。否则,使用缓存的数据。 ASP.NET 的内置缓存 API 让您可以轻松完成所有这些工作 - 只需将缓存过期时间设置为 10 秒,您就可以了。

    这样,您将获得两全其美 - 您的数据永远不会超过 10 秒,但您仍然可以避免不断的轮询。

    【讨论】:

    • ASP.Net 的内置缓存对所有会话都是全局的吗?
    • 我喜欢超时的想法。我现在看到的唯一(小)问题是,当超时到期并且是时候调用 ExpensiveCall() 时,这将是一个阻塞调用。与查询本地表相反,本地表通过在单独的进程中不断刷新来保持最新。这是我觉得投票想法很有吸引力的地方。虽然我必须承认每 10 秒轮询一次确实感觉很奇怪,这就是为什么我对此持谨慎态度。
    • 如果调用对用户来说不是很明显,那么它被阻塞可能不是什么大问题。
    • 是的,缓存是应用程序全局的。否则就没有多大意义了。
    • 如何避免所有并行调用API.ExpensiveCall()在到期超时后立即出现多个请求?
    【解决方案2】:

    如果“一个用户不明显”,那么也许你应该让对方法的第一次调用缓存结果,然后每隔 X 秒使缓存过期。这样,每 X 秒只有一个调用会影响性能。

    我同意每 10 秒点击一次外部服务听起来很不酷,最坏的情况是拒绝服务攻击(取决于呼叫的权重和提供商的能力)。

    【讨论】:

    • 很公平。但你能详细说明为什么它不酷吗?此外,外部系统仍在组织内。它不会通过网络或类似的东西。
    • @7wp:至少让负责该服务的团队知道您调用他们的效率很低,因为他们没有公开一种机制来帮助您智能地使缓存无效并要求他们添加适当的功能在未来的版本中。
    • 如果它是一个内部系统,那么只要维护其他系统的人签署他们可以处理负载,它就不会不酷。我同意 Eric 的意见,您应该先去找他们,看看他们是否能提供更好的解决方案。
    【解决方案3】:

    使用时间跨度(例如 10 秒)使缓存无效从根本上没有错。

    如果从业务角度来看,用户看到过期 10 秒的数据的后果是可以接受的,那么这是一个完全合理的解决方案。在 10 秒后收到第一个请求之前,您可能不希望真正使缓存过期(鉴于对单个用户的影响不是很显着......如果处理该用户请求的延迟很明显,可以让缓存提前过期)。

    【讨论】:

      【解决方案4】:

      看看my response to this question - 它描述了一种确保标准缓存中数据新鲜的方法。似乎它也可以直接解决您的情况。

      【讨论】:

      • 是的,这正是我需要做的。我想我在描述中遗漏了“异步”这个词。我将对此进行调查。谢谢。
      【解决方案5】:

      听起来这可能是一个单例操作(例如,所有请求都将使用相同的操作结果)。如果是这种情况,听起来也像是可以缓存在HttpRuntime.Cache 中的东西。

      var mutex = new Mutex();
      
      public IEnumerable<MyData> GetMyData()
      {
        mutex.WaitOne();
        var cache = HttpRuntime.Cache;
        var data = cache["MyData"] as IEnumerable<MyData>;
        if (data == null)
        {
          data = API.ExpensiveCall();
          cache.Add("MyData", data, null, 
            DateTime.Now.AddSeconds(60), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
        }
      
        mutex.ReleaseMutex();
        return data;
      }
      

      从这个意义上说,您调用GetMyData,它会检查应用程序缓存,如果数据不存在(或已过期,并且已被删除),我们会进行昂贵的调用,缓存结果并返回数据。

      【讨论】:

      • 我喜欢超时的想法。我现在看到的唯一(小)问题是,当超时到期并且是时候调用 ExpensiveCall() 时,这将是一个阻塞调用。与查询本地表相反,本地表通过在单独的进程中不断刷新来保持最新。这是我觉得投票想法很有吸引力的地方。虽然我必须承认每 10 秒轮询一次确实感觉很奇怪,这就是为什么我对此持谨慎态度。
      • 请使用locktry {…} finally {…} 以避免将mutex 锁定在特殊情况下。
      • 另外,请注意mutex 应该如何成为静态类成员或其他东西……使用var 表明它可以存在于某个东西的本地范围内。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-02-01
      • 2019-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多