【问题标题】:What's the best way to cache complicated search queries in a .NET webapp?在 .NET webapp 中缓存复杂搜索查询的最佳方法是什么?
【发布时间】:2012-02-19 18:07:59
【问题描述】:

我有一个网站,允许用户使用各种搜索条件查询特定食谱。例如,您可以说“向我展示我可以在 30 分钟内制作的所有食谱,这些食谱将使用鸡肉、大蒜和意大利面,但不使用橄榄油。”

此查询通过 JSON 发送到 Web 服务器,并反序列化为 SearchQuery 对象(具有各种属性、数组等)。

实际的数据库查询本身是相当昂贵的,并且有很多默认的搜索模板会被频繁使用。出于这个原因,我想开始缓存常见的查询。我已经对各种缓存技术进行了一些调查,并阅读了很多关于该主题的其他 SO 帖子,但我仍在寻找关于走哪条路的建议。目前,我正在考虑以下选项:

  1. 内置System.Web.Caching这将提供对缓存中的项目数量、到期时间和优先级的大量控制。但是,缓存对象由字符串而不是可散列对象作为键。我不仅需要能够将 SearchQuery 对象转换为字符串,而且哈希必须是完美的并且不会产生任何冲突。
  2. 开发我自己的 InMemory 缓存:我真正想要的是一个在所有会话中都保留在内存中的 Dictionary<SearchQuery, Results> 对象。由于搜索结果可能开始变得相当大,我希望能够限制将缓存的查询数量,并为旧查询提供一种过期方式。像 FIFO 队列这样的东西在这里可以很好地工作。我很担心线程安全之类的事情,并且想知道在这里编写自己的缓存是否值得。

我还研究了其他一些第三方缓存提供程序,例如 NCacheVelocity。这些都是分布式缓存提供程序,可能完全满足我目前的需求。另外,似乎我见过的每个缓存系统仍然需要用字符串作为键的对象。理想情况下,我想要一个在进程中保存缓存的东西,允许我按对象的哈希值作为键,并允许我控制过期时间和优先级。

如果有任何关于免费且最好是开源的解决方案的建议或参考资料,我将不胜感激。谢谢!

【问题讨论】:

    标签: asp.net .net performance caching


    【解决方案1】:

    根据您所说的,我建议您使用 System.Web.Caching 并将其构建到您的 DataAccess 层中,以将其与系统的其余部分屏蔽。调用时,您可以根据您的业务/应用程序需求进行实时查询或从缓存对象中提取。我今天这样做,但使用Memcached

    【讨论】:

    • 所以基本上是选项1,并找到一种方法将SearchQuery 散列成一个字符串?
    • 是的,我们的密钥基于 Lib + Method + 等...为调用生成唯一密钥,您可以根据 SearchQuery/Params 执行相同操作。
    • 获取SearchQuery 的唯一字符串散列的能力无疑具有更大的灵活性。例如,如果我添加更多 Web 服务器并希望有一天设置分布式缓存,我将有更多可用的选项。
    • 我们完全按照 Rick 所描述的方式进行操作,结果非常好 - 由于我们的 Web 服务器是负载平衡的,因此使用分布式缓存也比内存缓存更受欢迎。
    • 我最终这样做了,结果证明生成字符串缓存键非常简单。我只是将所有内容序列化为一个字节数组,然后返回 Convert.ToBase64String(bytes) 作为密钥。
    【解决方案2】:

    快速浏览Enterprise library 缓存应用程序块。假设您想要一个 Web 应用程序范围的缓存,这可能是您正在寻找的解决方案。

    【讨论】:

    • 是的,我调查了这一点以及 AppFabric 的东西。但是,在我的情况下,与 ASP.NET 中内置的 System.Web.Caching 支持相比,我并没有真正看到这样做的好处。
    • 虽然这篇文章违背了我的建议,但考虑到您的情况,值得一读:stackoverflow.com/questions/21870/…。我非常同意“创建一个包装器”的说法,所以无论你选择什么解决方案,如果需要的话,以后很容易分出。
    • 是的!当然,我很想把整个事情都包起来,这样如果我想扩大规模,有一天我可以加入 NCache 或 AppFabric。很好的建议。
    【解决方案3】:

    内存缓存应该很容易实现。我想不出为什么您应该特别担心验证 SearchQuery 对象与其他对象的唯一性 - 也就是说,虽然键必须是字符串,但您可以将原始对象与结果一起存储在缓存中,并在您命中哈希后直接验证相等性。我会使用System.Web.Caching 来表示您提到的好处(到期等)。如果碰巧发生了冲突,那么第二个就不会被缓存。但这将是极其罕见的。

    此外,存储搜索结果所需的内存量应该是微不足道的。您不需要完整详细地保留每个字段、每一行的数据。您只需要保持快速访问每个结果的方式,例如int 主键。

    最后,如果可能有数千个搜索结果可以缓存,您甚至不需要为每个结果保留 ID - 只需保留前 100 个或其他内容(以及点击总数)。我怀疑如果您分析人们如何使用搜索结果,那么很少有人会超过几页。如果有人这样做了,那么您可以再次运行查询。

    所以基本上你只是为每个常见搜索的前 X 条记录存储一个主键,然后如果你的缓存命中,你所要做的就是运行一个非常便宜的查找少数索引键。

    【讨论】:

      【解决方案4】:

      我假设从 SearchQuery 对象生成数据库查询并不昂贵,并且您希望缓存从执行查询获得的结果(即行集)。

      您可以从 SearchQuery 对象生成查询文本,并将该文本用作使用 System.Web.Caching 进行查找的键。

      通过快速阅读 Cache 类的文档,似乎键必须是唯一的 - 如果您使用它们查询文本,它们将是唯一的 - 而不是键的哈希。

      编辑

      如果您担心长缓存键,请查看以下链接:

      Cache key length in asp.net

      Maximum length of cache keys in HttpRuntime.Cache object?

      似乎 Cache 类将缓存项存储在内部字典中,该字典使用键的 hash。具有相同散列的键(查询文本)最终会出现在字典中的同一个桶中,在进行缓存查找时,它只是快速线性搜索以找到所需的。所以我认为你可以使用长键字符串。

      asp.net 缓存是经过深思熟虑的,我不认为这是您需要其他东西的情况。

      【讨论】:

      • 是的,或者是SearchQuery 的序列化 XML 或 JSON 的密钥 - 我想知道是否存在与非常长的缓存密钥有关的任何问题。无论哪种方式,这似乎有点像一个 hacky 方式来密钥项目这就是为什么我想知道我是否最好实现自己的内存缓存,它可以通过对象键入并在内部处理键冲突。
      • 是的,看起来很多人也对使用非常冗长的键感到好奇。由于内部缓存是通过 32 位哈希存储的,所以他们应该首先将键设为对象 :)
      • 我试图在我的回答中解释这一点,但不要担心可能的重复哈希,为什么不在缓存 SearchQuery 对象及其结果时存储它呢?然后只需对您正在查找的对象与使用该哈希存储的对象进行直接相等测试。重复哈希的可能性应该非常低,因此冲突的唯一后果是您无法缓存两个查询。但即使这种情况发生过,也只是一个没有被缓存的查询。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-09
      • 2011-01-10
      • 2016-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多