11

我目前正在处理一个相当大的基于问题/答案的应用程序(有点像 stackoverflow / answerbag.com) 我们使用 SQL (Azure) 和 nHibernate 进行数据访问,使用 MVC 进行 UI 应用程序。

到目前为止,在我们有一个Post表(包含问题/答案)的意义上,该模式大致沿着 stackoverflow db 的路线

可能会使用以下存储库接口的内容:

public interface IPostRepository
{
    void PutPost(Post post);
    void PutPosts(IEnumerable<Post> posts);

    void ChangePostStatus(string postID, PostStatus status);

    void DeleteArtefact(string postId, string artefactKey);
    void AddArtefact(string postId, string artefactKey);

    void AddTag(string postId, string tagValue);
    void RemoveTag(string postId, string tagValue);

    void MarkPostAsAccepted(string id);
    void UnmarkPostAsAccepted(string id);

    IQueryable<Post> FindAll();
    IQueryable<Post> FindPostsByStatus(PostStatus postStatus);
    IQueryable<Post> FindPostsByPostType(PostType postType);
    IQueryable<Post> FindPostsByStatusAndPostType(PostStatus postStatus, PostType postType);
    IQueryable<Post> FindPostsByNumberOfReplies(int numberOfReplies);
    IQueryable<Post> FindPostsByTag(string tag);
}

我的问题是:我将在哪里/如何将 solr 放入其中以便更好地查询这些“帖子”(我将使用 solrnet 与 Solr 进行实际通信)

理想情况下,我会将 SQL db 仅用作持久存储——上述 IQueryable 操作的大部分将移至某种 SolrFinder 类(或类似的类)

Body 属性是当前导致问题的一个 - 它相当大,并且会减慢 sql 的查询速度。

我的主要问题是,例如,如果有人“更新”了一篇文章——例如添加了一个新标签,那么整个文章都需要重新索引。显然,这样做需要这样的查询:

“SELECT * FROM POST WHERE ID = xyz”

这当然会非常缓慢。Solrnet 有一个 nHibernate 设施 - 但我相信这将与上述结果相同?

我想了一个办法来解决这个问题,我想听听你的看法:

  • 将 ID 添加到队列(amazon sqs 或其他东西 - 我喜欢它的易用性)
  • 在某处有一个服务(或一堆服务)来执行上述查询、构建文档并将其重新添加到 solr。

我的设计遇到的另一个问题: 应该从哪里调用“重新索引”方法?MVC 控制器?还是我应该有一个“PostService”类型的类来包装 IPostRepository 的实例?

任何指针都非常受欢迎!

4

3 回答 3

27

在我工作的电子商务网站上,我们使用 Solr 提供产品目录的快速分面和搜索。(在非 Solr 极客术语中,这意味着“ATI Cards (34)、NVIDIA (23)、Intel (5)”样式的导航链接,您可以使用这些链接在 Zappos、Amazon、 NewEgg 和 Lowe's。)

这是因为 Solr 被设计用来快速、良好地完成这种事情,而试图在传统的关系数据库中高效地完成这种事情是不会发生的,除非你想开始在fly and go full EAV,这只是Magento咳嗽愚蠢。因此,我们的 SQL Server 数据库是“权威”数据存储,而 Solr 索引是该数据的只读“投影”。

到目前为止,你和我在一起是因为听起来你处于类似的情况。下一步是确定 Solr 索引中的数据可能稍微陈旧是否正常。您可能已经接受了它会有些陈旧的事实,但接下来的决定是

  • 陈旧到什么程度太陈旧了?
  • 我什么时候应该重视速度或查询功能而不是陈旧性?

例如,我有我称之为“Worker”的东西,它是一个使用Quartz.NET定期执行 C#IJob实现的 Windows 服务。每 3 小时,其中一项被执行的作业是RefreshSolrIndexesJob,而该作业所做的只是HttpWebRequesthttp://solr.example.com/dataimport?command=full-import. 这是因为我们使用 Solr 内置的DataImportHandler来实际从 SQL 数据库中吸入数据;该工作只需定期“触摸”该 URL 以使同步工作。因为 DataImportHandler 定期提交更改,所以这一切都在后台有效地运行,对 Web 站点的用户是透明的。

这确实意味着产品目录中的信息最长可能会过期 3 小时。用户可能会在目录页面上单击“Medium In Stock (3)”的链接(因为这种分面数据是通过查询 SOLR 生成的),但随后在产品详细信息页面上看到没有介质库存(因为在此页面,数量信息是少数缓存和直接针对数据库查询的内容之一)。这很烦人,但在我们的特殊场景中通常很少见(我们是一家相当小的企业,流量不是那么高),无论如何,当我们从头开始重新构建整个索引时,它会在 3 小时内修复,所以我们接受了这一点作为一个合理的权衡。

如果你能接受这种程度的“陈旧”,那么这个后台工作进程是一个不错的选择。您可以采用“每隔几个小时重新构建整个事物”的方法,或者您的存储库可以将 ID 插入到表中,例如,dbo.IdentitiesOfStuffThatNeedsUpdatingInSolr然后后台进程可以定期扫描该表并仅更新 Solr 中的那些文档,如果重建鉴于数据集的大小或复杂性,定期从头开始创建整个索引是不合理的。

第三种方法是让您的存储库产生一个后台线程,该线程或多或少地同时更新关于当前文档的 Solr 索引,因此数据只会在几秒钟内过时:

class MyRepository
{
    void Save(Post post)
    {
         // the following method runs on the current thread
         SaveThePostInTheSqlDatabaseSynchronously(post);

         // the following method spawns a new thread, task,
         // queueuserworkitem, whatevever floats our boat this week,
         // and so returns immediately
         UpdateTheDocumentInTheSolrIndexAsynchronously(post);
    }
}

但是,如果由于某种原因爆炸了,您可能会错过 Solr 中的更新,因此让 Solr 定期“将其全部清除并刷新”仍然是一个好主意,或者有一个收割者后台 Worker 类型的服务来检查是否存在 - Solr 中的最新数据,每个人都有过一次好机会。

至于从 Solr 查询这些数据,您可以采取一些方法。一种是隐藏 Solr 完全通过 Repository 的方法存在的事实。我个人不建议这样做,因为您的 Solr 架构很可能会无耻地针对将访问该数据的 UI 进行定制;我们已经决定使用 Solr 来提供简单的分面、排序和快速显示信息,所以我们不妨充分利用它。这意味着当我们打算访问 Solr 以及当我们打算访问最新的、非缓存的数据库对象时,在代码中使其显式化。

在我的例子中,我最终使用 NHibernate 来进行 CRUD 访问(加载一个ItemGroup.已经抽象了数据库。(这是个人选择。)

但是在查询数据时,我很清楚我是把它用于面向目录的目的(我关心速度查询)还是在后端管理应用程序的表中显示(我关心货币)。为了在网站上查询,我有一个名为ICatalogSearchQuery. 它有一个Search()方法可以接受SearchRequest我定义一些参数的地方——选择的方面、搜索词、页码、每页的项目数等——并返回一个——SearchResult剩余的方面、结果数、结果页面等。相当无聊的东西。

有趣的是,它的实现ICatalogSearchQuery是使用ICatalogSearchStrategy下面的 s 列表。默认策略 .SolrCatalogSearchStrategy直接通过普通的老式访问 SOLRHttpWebRequest并解析中的 XML HttpWebResponse(恕我直言,这比某些 SOLR 客户端库更容易使用,尽管自从我上次查看以来它们可能已经变得更好了他们一年多以前)。如果该策略由于某种原因引发异常或呕吐,则DatabaseCatalogSearchStrategy直接命中 SQL 数据库——尽管它忽略了SearchRequest,比如分面或高级文本搜索,因为这样做效率很低,这也是我们首先使用 Solr 的全部原因。这个想法是 SOLR 通常会以全功能的荣耀快速响应我的搜索请求,但如果出现问题并且 SOLR 出现故障,那么网站的目录页面仍然可以通过点击数据库以“缩减功能模式”运行一个有限的功能集直接。(因为我们已经在代码中明确表明这是一个搜索,所以该策略可以自由地忽略一些搜索参数,而不必担心过于严重地影响客户端。)

关键要点:重要的是,针对可能陈旧的数据存储与权威数据存储执行查询的决定已经明确——如果我想要快速、可能具有高级搜索功能的陈旧数据,我使用ICatalogSearchQuery. 如果我想要具有插入/更新/删除功能的缓慢、最新的数据,我使用 NHibernate 的命名查询(或您的情况下的存储库)。如果我在 SQL 数据库中进行更改,我知道进程外的 Worker 服务最终会更新 Solr,使事情最终保持一致。(如果某些事情真的很重要,我可以广播一个事件或直接 ping SOLR 存储,告诉它更新,如果必须的话,可能在后台线程中。)

希望能给你一些见解。

于 2010-09-09T01:36:56.363 回答
8

我们使用 solr 查询大型产品数据库。大约 100 万种产品和 30 家商店。

我们所做的是在 Sql 服务器上的产品表和库存表上使用触发器。

每次更改一行时,它都会标记要重新索引的产品。我们有一个 Windows 服务,它每 10 秒抓取一次这些产品并将它们发布到 Solr。(每批最多 100 个产品)。

它非常高效,几乎是实时的股票信息。

于 2010-09-15T18:55:02.200 回答
2

如果您有一个大文本字段(您的“正文”字段),那么可以,在后台重新索引。您提到的解决方案(队列或定期后台服务)就可以了。

MVC 控制器应该忽略这个过程。

我注意到您的存储库界面中有 IQueryables。SolrNet 当前没有 LINQ 提供程序。无论如何,如果这些操作都是您要用 Solr 做的(即没有分面),您可能要考虑使用 Lucene.Net,它确实有一个 LINQ 提供程序。

于 2010-09-09T00:48:33.743 回答