Solr-教程-二-

190 阅读1小时+

Solr 教程(二)

原文:Apache Solr

协议:CC BY-NC-SA 4.0

五、索引数据

如您所知,Solr 会为您摄取的数据创建一个倒排索引,因此将数据添加到 Solr 的过程称为索引。在 Solr 中,您主要做的是索引和搜索,为了使数据可搜索,必须对其进行索引。

在第二章中,您使用 Solr 管理控制台索引了样本文档。在本章中,您将从深度和广度上了解索引过程。我所说的深度是指对过程的详细理解。我所说的宽度是指在索引文档之前需要做出的各种决定,比如文档格式、索引工具、索引频率、文档预处理和文本分析。前一章介绍了文本分析,所以本章介绍了其他方面。

你可能急于了解搜索过程,因为这是开发搜索引擎的最终目标,你可能想浏览这一章。如果您不熟悉 Solr,您可以这样做,并在搜索过程就绪后回来详细阅读。但是,如果您正致力于将系统投入生产,就不应该忽略索引过程。索引不仅仅是必要的先决条件。你必须记住,如果你把垃圾放进去,你就会把垃圾弄出来。

索引过程可以像添加文档以使内容可搜索一样简单,但是您不应该将您的应用程序局限于此。应该充分利用 Solr 和其他补充技术在数据清理、丰富和元数据提取方面的潜力。你将在本章中了解这些方法。此外,第十一章介绍了一些提取隐藏在非结构化数据中的金块的高级方法。您可以遵循迭代和增量模型来引入这些功能。

本章涵盖以下主题:

  • 文档索引工具
  • 索引操作
  • 为 XML、JSON 和 CSV 等文本文档编制索引
  • 索引二进制文档和提取元数据
  • 从数据库导入数据
  • 预处理和文档丰富
  • 索引性能
  • 编写定制处理器
  • 了解经常出现的问题

索引工具

Solr 通过 HTTP 公开了一个 RESTful 服务,因此任何 REST 客户端都可以搜索和索引文档。您只需要知道正确的端点和适用的参数。访问服务的客户端可以是您最喜欢的浏览器,比如 Chrome 或 Firefox,甚至是 Wget 工具。浏览器和 Wget 工具很适合评估和简单的请求,但是您通常需要更复杂的工具。

您选择的工具取决于您的索引需求、数据源、文档格式和性能要求。Solr 支持各种文档格式,包括 XML 和 JSON,一些工具支持一组特定的文档格式或数据源。本节介绍一些常用的索引工具。

附言

Solr 5.0 引入了一个 bash 脚本,可以在*nix 环境中执行该脚本来索引文档。这个脚本可以索引 Solr 原生格式(Solr 为 XML 和 JSON 指定并理解的格式)的文档,可以索引 CSV 文档,甚至可以执行简单的 web 爬行。它还允许您指定包含文档的目录路径。这个脚本存在于 Solr 发行版的bin目录中,您可以通过使用-help选项运行它来找到它的使用细节:

$ bin/post -help

下面是一个将example/exampledocs目录中的money.xml文件索引到hellosolr内核的例子:

$ bin/post -c hellosolr ../example/exampledocs/money.xml

SimplePostTool

如果你运行的是早于 Solr 5.0 的版本或者你的平台是 Windows,你不用担心。您可以依赖在example/exampledocs目录中作为post.jar提供的SimplePostTool包,这是一个 Java 可执行文件。前面的bin/post脚本在内部使用这个 JAR 来提供方便的方法来索引文档。您可以通过如下方式运行 JAR 来查找使用细节:

$ java -jar example/exampledocs/post.jar -h

让我们像以前一样使用 SimplePostTool 来索引相同的文档:

java -Dc=hellosolr -jar post.jar money.xml

在这个例子中,我们不提供money.xml的相对路径,因为post.jar存在于同一个目录中。

卷曲

另一种强大的文档索引方式是curl实用程序。在本书中,你将使用 curl 来索引文档。请参阅 http://curl.haxx.se/docs/manpage.html 或*nix 手册中的 curl 用法详情:

$ man curl

solr java 库

Solr 提供了一个 SolrJ Java 客户端来从您的程序访问服务器。它提供了创建文档、索引文档、形成查询、执行搜索和遍历结果的方法。对于索引,如果您从主数据源(如内容管理系统(CMS ))读取数据,您可能更喜欢使用 SolrJ。对于从关系数据库导入数据,Solr DataImportHandler contrib 模块是一个很好的起点。然而,因为它是单线程的,所以人们确实使用 SolrJ 来编写定制的导入程序,以处理大量的数据。

Note

Solr 的每个版本都捆绑了 SolrJ 库,Solr 提供的新特性更新了 Solr 库。使用 SolrJ 时,确保使用匹配版本的库作为服务器。

其他图书馆

除了 SolrJ,其他客户端库也可用于 Java。如果您的 Java 应用程序使用 Spring 框架,您甚至可以尝试 Spring Data Solr。客户端库也可用于其他语言。Python 有十几个客户端库。你可以参考 Solr wiki, https://wiki.apache.org/solr/IntegratingSolr ,获得完整的客户端库列表。

索引过程

如果数据是结构化的并且格式良好,那么索引过程可能会很简单,但是如果数据是非结构化的,并且可以在不同的格式和数据源中使用,那么索引过程可能会有点复杂。例如,在 CSV 文件中索引文档只需要触发一个上传按钮或发布文本流。

Solr 支持包括 XML 和 JSON 在内的多种文档格式作为索引的输入:您可以用 Solr 指定的结构甚至随机格式来格式化数据并发布它。您定义的结构化文件将包含一组文档,每个文档都指定字段和应该索引到该字段的内容。输入文件中的字段名应映射到schema.xml中定义的字段名。

有趣的是,您可能有一个 XML 文档,但是结构和元素可能与原生 Solr 格式不同。对于这样的场景,DataImportHandler contrib 模块允许您定义 XML 元素和 Solr 字段之间的映射,并且您可以通过调用处理程序端点来启动索引过程。

此外,数据可以在任何不同的数据源中获得(例如,本地文件系统、数据库或网页)。以下步骤将帮助您根据您的索引需求理解和开发索引过程:

Text extraction: In this process, you extract the text for indexing. The text can be acquired, for example, by reading files, querying databases, crawling web pages, or reading RSS feeds. Extraction can be performed by your Java client application or Solr components. DataImportHandler is a contrib module that can be used for reading data from databases or XML files, for example. The Solr Cell framework, built on Apache Tika, can directly extract data from files in Office, Word, and PDF formats, as well as other proprietary formats.   Document preparation: The extracted text should be transformed into a Solr document for ingestion. The prepared document should adhere to the native format specified, for example, for XML or JSON. As a better alternative, you can use the SolrJ client to create a document for ingestion. If data is directly ingested using one of the Solr frameworks having support for automatic transformation, this step might not be required.   Post and commit: During this process, you post the document to the appropriate Solr endpoint with required parameters. Solr-provided extraction capabilities are performed based on the endpoint you invoke. You may optionally like to trigger a commit to persist the added documents immediately.   Document preprocessing: You might want to do cleanup, enrichment, or validation of text received by Solr. Solr provides a large number of UpdateRequestProcessor implementations for performing these tasks. It prebundles the processor implementation for common tasks such as deduplication and language detection, and allows you to write custom processors. You can even do the custom processing in the client program during document preparation, if you are not interested in writing Solr plug-ins.   Field analysis: Field analysis converts the input stream into terms. This step refers to the analysis chain of analyzers, tokenizers and token filters that are applied on the fieldType definition, which you read about in the previous chapter.   Index: The terms output from field analysis are finally indexed; the inverted index is created. These indexed terms are used for matching and ranking in search requests. After you trigger the post operation (mentioned in Step 3), the preprocessing and field analysis defined in Solr will be triggered automatically and documents will be indexed.  

图 5-1 描述了分度步骤。您可以看到,数据可以直接索引,也可以使用客户端应用程序索引。

A978-1-4842-1070-3_5_Fig1_HTML.gif

图 5-1。

Solr indexing process

现在让我们更详细地理解索引过程和相关的 Solr 组件。假设您想要索引一个 XML 文档。为了索引它,您向/update端点发出请求。如果你穿过solrconfig.xml,你会发现它被映射到UpdateRequestHandler:

<requestHandler name="/update" class="solr.UpdateRequestHandler" />

/update的任何请求都由UpdateRequestHandler处理。类似地,其他处理程序用于处理不同类型的内容流——例如,DataImportHandler用于从数据库、定制 XML、RSS 和 atom 提要导入数据;ExtractingRequestHandler用于从二进制文档中提取数据;等等。每个端点被映射到一个特定的处理器,就像/update被映射到UpdateRequestHandler一样。您甚至可以通过扩展ContentStreamHandlerBase来编写自己的处理程序。

当向UpdateRequestHandler发出请求时,将 XML 作为内容流发送,同时发送其他参数来处理请求。现在,我们来探索一下UpdateRequestHandler

更新请求处理程序

UpdateRequestHandler支持对 XML、JSON、CSV 和 javabin 格式的文档进行索引。对于每个更新请求,您可以指定内容类型。基于这些信息,UpdateRequestHandler通过调用相应的加载器来解析文档,然后通过调用更新链中注册的处理器来索引文档。

您可以通过提供 MIME Content-Type或传递请求参数update.contentType来通知UpdateRequestHandler文档的格式。如果没有提供任何此类信息,处理程序会尝试自动识别内容类型。如果它检测到内容是 XML 或 JSON 文档,它会继续处理。否则,它将引发异常。根据您的文档格式,您需要指定相应的内容类型,如表 5-1 所示。

表 5-1。

Document Formats and Corresponding Content Type

| 文档格式 | 内容类型 | 特征 | | --- | --- | --- | | 可扩展置标语言 | `application/xml, text/xml` | 接受 Solr 定义的任意格式`XML`。功能齐全。 | | 数据 | `application/json, text/json` | 接受 Solr 定义的任意格式`JSON`。功能齐全。不允许注释。 | | 战斗支援车 | `application/csv, text/csv` | 标准`CSV`格式。能力有限。 | | 爪哇滨 | `application/javabin` | 更快。 |

默认情况下,索引操作的响应格式与文档的内容类型相同。如果您想获得不同格式的响应,您需要通过使用wt参数来提供响应格式。

UpdateRequestProcessorChain

Solr 提供了在文档被索引之前修改文档的条款。您可以对文本执行任何处理,如清理、丰富和验证。Solr 允许您修改一个字段的值(例如,设置缺省值),创建一个新字段(例如,添加当前时间戳),删除一个字段,甚至过滤文档使其不被索引(例如,在重复文档的情况下)。

这些操作由UpdateRequestProcessorFactory实例执行,这些实例可以链接在一起,并在更新处理程序中配置。每当处理程序收到更新请求时,它就执行这个链,这个链运行所有已配置的处理器,然后索引文档。在solrconfig.xml中可以定义多个链,但是只能将一个链分配给一个处理程序来执行。

除了作为 core Solr 一部分的UpdateRequestProcessor,一些处理器也可以作为 contrib 模块使用,比如 UIMA。您甚至可以编写自己的处理器实现,根据您的定制需求修改文档。以下是如何向更新处理程序注册处理器的示例:

<updateRequestProcessorChain name="mychain" default="true">

<processor class="solr.TimestampUpdateProcessorFactory">

<str name="fieldName">timestamp</str>

</processor>

<processor class="solr.CustomUpdateRequestProcessorFactory">

<lst name="name">

<str name="name1">value</str>

<str name="name2">value</str>

</lst>

</processor>

<processor class="solr.LogUpdateProcessorFactory" />

<processor class="solr.RunUpdateProcessorFactory" />

</updateRequestProcessorChain>

那么这个updateRequestProcessorChain是干什么的呢?这个链按顺序运行注册的处理器。它运行TimestampUpdateProcessorFactory,将当前时间戳设置为timestamp字段的默认值。该工厂的输出输入到CustomUpdateRequestProcessorFactory。假设这是您的定制处理器,它会根据配置进行一些处理并更新文档。接下来是LogUpdateProcessorFactory用更新信息更新日志。最后应该运行的是RunUpdateProcessorFactory,因为它最终会更新倒排索引。

Note

如果RunUpdateProcessorFactory没有在您的链中注册,将不会发生索引更新。如果它没有被注册为最后一个组件,它后面的处理器将没有任何作用。

要执行任何预处理,链必须在更新处理程序中注册。如果您想为每个请求运行处理,您可以在solrconfig.xml中设置它,如下所示:

<requestHandler name="/myupdate " class="solr.UpdateRequestHandler">

<lst name="defaults">

<str name="update.chain">mychain</str>

</lst>

</requestHandler>

您也可以通过用参数update.chain传递名称来为特定请求注册链,如下所示:

$ curl http://localhost:8983/solr/hellosolr/update?update.chain=mychain

Note

Solr 提供的UpdateRequestProcessors的详细列表可以在 Solr-Start 项目、 www.solr-start.com/info/update-request-processors/ 中找到。也可以参考 http://lucene.apache.org/solr/5_3_1/solr-core/org/apache/solr/update/processor/UpdateRequestProcessorFactory.html 的 Solr Javadoc。

UpdateRequestProcessor 与分析器/令牌化器

在前一章中,你学习了分析器和记号赋予器。但是如果UpdateRequestProcessor的工作方式确实类似于索引时间分析器,您可能会想为什么需要两个特性。接下来的几段将揭示它们之间的主要区别,并解释为什么我们两者都需要。

分析器背后的思想是修改输入流以生成用于匹配的术语(例如小写和词干),因为它通过维护标记的位置和偏移信息来提供更好的控制。它可以在索引和查询时应用。实现对被索引的文档进行预处理,例如清理、丰富新字段中的信息以及验证。它仅在索引文档时应用,在搜索时不起作用。

在讨论下一个区别之前,让我们回顾一下 Solr 中文本是如何索引和存储的。

schema.xml中,您如下定义一个字段:

<field name="title" type="string" indexed="true" stored="true" />

在这个字段定义中,indexed="true"意味着应该为字段创建一个倒排索引,stored="true"意味着字段文本应该以不倒排的方式逐字存储在索引中。只能检索存储的字段进行显示。

回到差别,在文档被RunUpdateProcessorFactory提交用于索引之后,一个分析器进入画面。它只能转换被索引的文本,而不能转换被存储的文本(未转换的文本值保持不变)。但是一个UpdateRequestProcessor接收到了SolrInputDocument,它的修改适用于索引和存储的文本。如果您将相同的逻辑放入组件和索引中并检索一些文本,分析器检索的文本将不会被修改,而UpdateRequestProcessor的文本将被修改。例如,如果您编写一个算法,根据售出的产品数量来评估产品的受欢迎程度,分析器将为搜索、执行函数查询和获取范围方面索引受欢迎程度;但是如果你想查看受欢迎程度(显示给用户),你需要写UpdateRequestProcessor

在每个字段的基础上应用分析器。它不允许您从一个字段获取数据并将其添加到另一个字段。是的,在某种程度上你可以通过使用copyField来实现,但是灵活性仍然有限。此外,分析器不能应用于非文本原始字段。

索引操作

到目前为止,您已经对 Solr 支持的文档格式有了很好的理解。在本节中,您将对不同格式的文档进行索引。本节涵盖了 XML 文档的所有索引操作。对于其他格式,它为您提供了足够的信息来帮助您管理索引。

XML 文档

在本节中,您将学习如何使用 Solr 的原生 XML 格式添加、删除和更新文档。所谓 Solr 的原生格式,我指的是 Solr 定义的一组标签和属性。要使用 XML 本地格式索引文档,您需要以 Solr 指定的格式准备文档,并将这些文档发送到服务器。

对索引所做的任何更改只有在触发 commit 后才可见,这将结束操作。让我们来看看每一项操作。

增加

你已经在第二章中看到了一个向 Solr 添加文档的例子。add命令将提供的文档集索引到 Solr。提醒一下,每个文档都可以比作关系数据库中的一个元组。下面是一个索引 XML 文档的请求示例:

$ curl http://localhost:8983/solr/hellosolr/update

-H "Content-Type: text/xml"

--data-binary ’<add commitWithin="5000" overwrite="true">

<doc>

<field name="id">apl1001</field>

<field name="product">iPad</field>

<field name="model">nano</field>

<field name="manufacturer">apple</field>

</doc>

<doc boost="2.0">

<field name="id">apl1002</field>

<field name="product">iPhone</field>

<field name="model" boost="2.0">iPhone 6</field>

<field name="manufacturer">apple</field>

<field name="color">gold</field>

<field name="color">silver</field>

</doc>

</add>’

以下是 Solr 支持的 XML 元素和属性,使用其原生格式对文档进行索引。

  • add:该根标签定义了向 Solr 添加文档的操作。它可以构成一个或多个要添加的文档。
    • commitWithin:如果您想确保在指定的时间内自动触发提交,请以毫秒为单位设置该属性的值。或者,您可以调用提交作为请求参数。
    • overwrite:可选属性,默认设置为true,用相同的uniqueKey覆盖现有文档。如果不希望现有文档被覆盖,将其设置为false,那些文档将被忽略。
  • 这个标签定义了一个 Solr 文档,并组成了一组字段。
    • boost:使用这个可选的属性来提升文档的索引时间。
  • field:定义一个 Solr 字段。对于多值字段,将其多次添加到文档中。
    • name:这个强制属性应该对应schema.xml中的一个字段名或者动态字段,除非你运行的是无模式。
    • boost:这个可选属性给字段一个索引时间提升。

在这个例子中,我们为文档 ID apl1002定义了两次color字段。这就是向多值字段添加值的方法。如果这个字段在schema.xml中没有被定义为多值,Solr 将抛出一个异常。

更新

如果添加一个已经存在的文档会怎样?如果指定了overwrite="true",文档将被覆盖;否则,新文档将被忽略。覆盖操作会索引新文档,删除旧文档。如果您想要更新特定字段的值并保留其他字段的值,或者想要向字段的现有值添加一个值,该怎么办?

在内部,Lucene 没有更新文档的功能。如果您想要更新一个字段的值,您需要删除文档并重新添加它。如果添加只有特定字段的文档,其他字段的值将被删除。要更新一个字段,您需要从 Solr 或您的主数据源获取所有其他字段的值,准备一个新文档,并添加它。

但是不用担心;Solr 的原子更新特性通过获取现有值消除了准备文档的痛苦。您需要执行以下操作来执行原子更新:

  • schema.xml中,将所有字段设置为stored="true",以便 Solr 可以在内部执行一个获取并准备一个新文档。使用copyField填充的所有字段可以保持不存储。
  • solrconfig.xml中,注册<updateLog/>。通过这种设置,原子更新可以确保获得索引文档的最新版本。
  • 在索引文档时,传递一个附加属性update。它的值可以是下列值之一:
    • set:设置字段的值。如果存在旧值,它将被覆盖。如果要删除现有值,将值设置为null
    • add:向字段的现有值添加新值。该字段应该是多值的。
    • remove:从字段中删除指定的值。该字段应该是多值的。
    • removeregex:删除所有匹配指定正则表达式模式的值。该字段应该是多值的。
    • inc:将数值字段的值增加所提供的值。该字段应为单值和数字。

以下示例将product字段的值设置为iPod,并将银色、金色和粉色添加到索引中,用于具有唯一 ID apl1001的文档:

<add>

<doc>

<field name="id">apl1001</field>

<field name="product" update="set">iPod</field>

<field name="color" update="add">silver</field>

<field name="color" update="add">gold</field>

<field name="color" update="add">pink</field>

</doc>

</add>

删除

delete命令标记要删除的文档。在下一次提交时,这些文档将从索引中永久删除,并且不再可搜索。您可以通过指定文档的唯一 ID 或指定查询来将文档标记为删除。以下是在 Solr 中删除文档的示例。

Delete by ID: deletes the documents with id apl1001 and apl1002

<delete>

<id>apl1001</id>

<id>apl1002</id>

</delete>

Delete by query: deletes all the documents that contain iPad as the product name

<delete>

<query>product:iPad</query>

</delete>

Delete by ID and query combined: deletes the document with id apl1001 and all the products of type iPad

<delete>

<id>apl1001</id>

<query>product:iPad</query>

</delete>

正如您可以在搜索时使用*:*选择所有文档一样,您也可以用同样的方式删除所有文档。

Delete all the documents in ‘hellosolr’ core

$ curl http://localhost:8983/solr/hellosolr/update

-H "Content-Type: text/xml"

--data-binary ’<delete><query>*:*</query></delete>’

犯罪

在 SQL 数据库中,您在更新后执行提交。类似地,Solr 也需要提交,只有在内核上触发了提交之后,更改才是可搜索的。您可以按如下方式执行提交:

$ curl``http://localhost:8983/solr/hellosolr/update

--data-binary ’<commit/>’

此外,您可以通过向更新处理程序传递一个额外的commit参数来触发提交:

$ curl http://localhost:8983/solr/hellosolr/update?commit=true

Solr 支持两种类型的提交:硬提交和软提交。

硬提交

硬提交使您的数据可搜索,并将更改保存到磁盘。前面的命令会触发硬提交。您还可以配置solrconfig.xml在指定的持续时间(以毫秒为单位)之后或者当指定数量的文档被添加到核心时自动提交文档。

Perform hard commit when 50,000 documents are added or every 5 minutes, whichever occurs earlier

<autoCommit>

<maxDocs>50000</maxDocs>

<maxTime>300000</maxTime>

</autoCommit>

软提交

因为硬提交会将文档保存到辅助存储中,所以操作成本很高。Solr 4.0 引入了软提交的概念,这使得添加的文档可以立即被搜索到,但是需要依赖硬提交来实现持久性。软提交有助于实现接近实时的搜索,但也带来了一个代价:如果系统在下一次硬提交之前崩溃,更改将会丢失。您可以按如下方式配置软提交:

Perform soft commit when 5,000 documents are added or every 5 seconds, whichever occurs earlier

<autoSoftCommit>

<maxDocs>5000</maxDocs>

<maxTime>5000</maxTime>

</autoSoftCommit>

您通常喜欢更频繁地执行软提交,但是要确保它们不会太频繁,以至于在第一次提交完成之前另一次提交就开始了。您应该减少执行硬提交的频率,但是持续时间不应该太长,因为崩溃会导致数据丢失。这些值应该根据您的要求适当设置。

使最优化

Lucene 索引由更小的称为段的块组成。在添加文档的过程中,Solr 会在每次硬提交时创建新的段,并不时地合并它们。当段数增加时,查询需要更多的时间;执行合并加快了查询速度,但这是一个需要大量磁盘交换的高成本操作。Lucene 根据合并策略自动合并段,但是如果你想强制合并,你可以调用optimize。它执行硬提交,然后将数据段合并成一个数据段。因为这是一项开销很大的操作,所以应该减少执行频率(例如,作为夜间作业)。

$ curl http://localhost:8983/solr/hellosolr/update?optimize=true

$ curl http://localhost:8983/solr/hellosolr/update

-H "Content-Type: text/xml" --data-binary ’<optimize/>’

不调用optimize,可以考虑在solrconfig.xml中将合并因子设置为一个较低的值:

<mergeFactor>10</mergeFactor>

反转

与数据库类似,您可以回滚未提交的更改:

$ curl http://localhost:8983/solr/hellosolr/update?rollback=true

$ curl http://localhost:8983/solr/hellosolr/update

-H "Content-Type: text/xml" --data-binary ’<rollback/>’

JSON 文档

Solr 使您能够在 Solr 指定的结构以及您的定制结构中索引 JSON 文档。在 Solr 指定的 JSON 结构中索引文档的过程与前面描述的 XML 文档的过程相同。您需要做的就是将内容类型指定为application/jsontext/json,并提供 JSON 数据。

这里,我们使用与前面 XML 文档中相同的数据来索引 JSON 文档:

$ curl``http://localhost:8983/solr/hellosolr/update

{

"id": "apl1001",

"product": "iPad",

"model":"nano",

"manufacturer":"apple"

},

{

"id": "apl1002",

"product": "iPhone",

"model": {

"value": "iPhone 6",

"boost": 2.0

},

"color": ["gold", "silver"]

}

]’

同样,您可以执行其他操作,如删除或提交,如下所示:

$ curl -X POST -H ’Content-Type: application/json’ ’``http://localhost:8983/solr/hellosolr/update’

{

"commit": {},

"delete": { "query":"*:*" }

}’

如果 JSON 数据不是 Solr 指定的样式,而是遵循任意结构,那么可以通过在更新请求中传递额外的映射参数来索引它。有关附加参数的详细信息,请参考 Solr 官方文件 https://cwiki.apache.org/confluence/display/solr/Uploading+Data+with+Index+Handlers

CSV 文档

如果您的数据是 CSV 格式的,那么建立索引就非常简单。您不需要遵循 Solr 指定的格式,因为值只是用逗号分隔。您可以分两步索引一个简单的 CSV 文件:

Map the values to Solr fields either by specifying comma-separated field names on the first line of the file or by specifying the comma separated names as the value of the fieldnames parameter in the request.   Set the content type as text/csv or application/csv. Alternatively, you can make a request without passing content-type information, if you call /update/csv handler instead of /update.  

下面是一个索引books.csv的请求示例,它随 Solr 包一起提供:

$ curl http://localhost:8983/solr/hellosolr/update/csv

--data-binary @books.csv -H ’Content-Type:text/plain’

以下是重要的参数,您可以随请求一起传递:

  • separator:如果逗号不是您的默认分隔符,请将这个附加参数与适用的分隔符一起传递。
  • skip:如果您不想索引 CSV 中的所有字段,请指定要跳过的字段的逗号分隔列表。
  • skipLines:指定您不想索引的第一行的数量。
  • 字段的文本包含逗号是很常见的,这也是默认的分隔符。您可以为文件指定一个封装器,并用它包围该值。此外,您可以指定特定于字段的封装器。
  • split:为多值字段索引数据,需要split布尔参数。指定split=true在所有multiValued字段上启用拆分,指定f.<fieldname>.split=true在特定字段上应用拆分。此外,您需要提供分隔符,这可以使用特定于字段的分隔符来完成。

下面是一个几乎完全成熟的 CSV 请求,它对我们用于 XML 和 JSON 的相同数据进行索引,但采用 CSV 格式:

$ curl``http://localhost:8983/solr/hellosolr/update?commit=true&split=true&f.color.separator=,&f.color.encapsulator

-H "Content-Type: text/xml"

--data-binary ’

id,product,model,manufacturer,color

apl1001,iPad,nano,apple,

apl1002,iPhone,Person,iPhone 6,apple,"gold,silver"’

索引丰富的文档

如果您有为书籍、期刊或杂志编制索引的业务需求,那么这些文档很可能是 PDF 或 Word 格式的。Solr 提供了一个称为 Solr Cell(以前称为内容提取库)的框架,它以二进制格式从文件中提取文本和元数据。这个框架由一个名为ExtractingRequestHandler的处理程序公开。

在索引富文档时,您需要指定 MIME 类型。如果没有提供信息,Solr 将尝试自动检测文档类型。它使用 Tika 解析器来识别文档类型并提取内容和元数据。提取的内容被添加到内容字段,元数据被添加到根据诸如 Dublin Core 的规范定义的字段。

Apache Tika 是一个专注于从一千多种文件类型中检测和提取元数据和文本的项目。该项目首页 https://tika.apache.org/ 提供了更多详情。

以下是索引 PDF 文件的步骤:

Add dependencies to lib directives in solrconfig.xml: <lib dir="../../../contrib/extraction/lib" regex=".*\.jar" /> <lib dir="../../../dist/" regex="solr-cell-\d.*\.jar" />   Configure the ExtractionRequestHandler in solrconfig.xml: <requestHandler name="/update/extract" class="org.apache.solr.handler.extraction.ExtractingRequestHandler">   <lst name="defaults">     <str name="lowernames">true</str>     <str name="uprefix">others_</str>     <str name="captureAttr">true</str>     <str name="fmap.a">url</str>     <str name="fmap.div">others_</str>   </lst> </requestHandler> The following points describe the specified parameters: lowernames: Setting this Boolean parameter to true, maps the field names to names with lowercase and underscores, if required. For example, “Author-Name” will be mapped to “author_name”. uprefix: The fields that are undefined in Solr are prefixed with the value specified here. You can define a dynamic field with the same pattern to handle the contents appropriately. The next step, demonstrates a dynamic field to ignore the undefined fields. captureAttr: Setting this Boolean parameter to true will even index the values of attributes extracted from XHTML elements. fmap.<source_field>: This parameter allows you to rename the field. The specified source field gets renamed to the value of the parameter. In this configuration, a field with name “a” will get renamed to “url”, for example.   Define appropriate fields in schema.xml: <field name="id" type="string" indexed="true" stored="true" required="true"/> <field name="content" type="string" indexed="true" stored="true" multiValued="true"/> <field name="author" type="string" indexed="true" stored="true" multiValued="true"/> <field name="title" type="text" indexed="true" stored="true"/> <dynamicField name="others_*" type="ignored" />   Index the document: $ curl ’ http://localhost:8983/solr/hellosolr/update/extract?literal.id=doc1&commit=true’ -F myfile=@example/exampledocs/solr-word.pdf  

示例

DataImportHandler是一个 contrib 模块,允许您从各种来源导入数据。如果 RDBMS 是您的主要数据存储,或者您需要从任意 XML 导入数据,那么DataImportHandler是一个很好的起点。只需使用 XML 配置,您就可以让一切就绪。

对于数据库,您可以从多个表中导入数据,方法是执行连接并展平结果以将其作为 Solr 文档进行索引。您还可以嵌套查询,为父查询提取的每一行执行子查询。一旦获取了一行,就可以在索引之前应用转换器来修改文档。DataImportHandler允许您执行以下两种类型的更新:

  • 完全导入:完全导入类似于完全转储,从表中提取所有记录并对其进行索引。
  • Delta import:这将执行增量更新,只获取自上次导入以来添加/修改过的文档。

DataImportHandler不限于数据库。有了它,您可以用以下任何一种格式来索引文档:

  • 任意 XML:使用 XSLT 进行转换。
  • 电子邮件:使用 JavaMail API。
  • Word/PDF 文档:使用 Apache Tika。这是使用 Solr Cell 索引文档的替代方法。

从 RDBMS 导入

在继续下一步之前,您需要理解数据导入的重要元素:

  • dataSource: dataSource d定义从哪个数据源读取数据以及如何连接。
  • 实体:生成单据的entity。例如,您可以创建一个从表中读取每一行并创建一个文档的entity
  • 处理器:它从数据源中提取内容,并在转换后将其添加到索引中(如果有的话)。缺省值是SqlEntityProcessor,它适用于关系数据库。entity元素支持processor属性,该属性允许您指定适用的处理器名称。
  • transformer:允许您转换文档(例如,拆分数据或剥离 HTML)。

下面是从 HSQLDB 中的items表导入数据的步骤:

Add dependencies to the lib directives in solrconfig.xml: <lib dir="../../../dist/" regex="solr-dataimporthandler-.*\.jar" />   Define a handler in solrconfig.xml: <requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">     <lst name="defaults">       <str name="config">data-config.xml</str>     </lst> </requestHandler> data-config.xml contains all the information regarding which source to read the data from, how to read the data, and how to map it with Solr fields and apply any transformation to the document, if needed.   Create data-config.xml in the conf directory: <dataConfig> <dataSource driver="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:example/example-DIH/hsqldb/ex" user="sa" batchSize="10000"/>     <document name="products">         <entity name="item" query="select * from item">             <field column="ID" name="id" />             <field column="NAME" name="name" />             <entity name="feature" query="select description from feature where item_id=’${item.ID}’">                 <field name="features" column="description" />             </entity>             <entity name="item_category" query="select CATEGORY_ID from item_category where item_id=’${item.ID}’">                 <entity name="category" query="select description from category where id = ’${item_category.CATEGORY_ID}’">                     <field column="description" name="cat" />                 </entity>             </entity>         </entity>     </document> </dataConfig>   Define the fields in schema.xml: <field name="id" type="string" indexed="true" stored="true" required="true"/> <field name="name" type="string" indexed="true" stored="true" multiValued="false"/> <field name="features" type="string" indexed="true" stored="true" multiValued="true"/> <field name="description" type="string" indexed="true" stored="true" multiValued="true"/>   Trigger the data import: $ curl http://localhost:8983/solr/hellosolr/dataimport?command=full-import  

文档预处理

正如您已经知道的,一个UpdateRequestProcessor可以修改或删除一个字段,创建一个新的字段,甚至跳过一个被索引的文档。在这一节中,您将看到一些重要的UpdateRequestProcessors,它们可以用来修改和丰富文档。本节提出了一个问题或需求,然后解释了在这种情况下您将使用的更新处理器。要使用处理器,必须在updateRequestProcessorChain中注册,如下所示:

<updateRequestProcessorChain name="customchain">

<!--

Your custom processor goes here.

-->

<processor class="solr.LogUpdateProcessorFactory" />

<processor class="solr.RunUpdateProcessorFactory" />

</updateRequestProcessorChain>

请记住,RunUpdateProcessorFactory应该始终是您在链中注册的最后一个处理器。

这个updateRequestProcessorChain必须添加到处理程序的更新链中,或者应该为每个请求提供update.chain参数。您可以将它添加到请求处理程序中,如下所示:

<requestHandler name="/update" class="solr.XmlUpdateRequestHandler" >

<lst name="defaults">

<str name="update.chain">customchain</str>

</lst>

</requestHandler>

语言检测

多语言数据是常见的,语言检测 contrib 模块在这里派上了用场。它有助于检测输入文档的语言,并允许您将语言信息存储在单独的字段中。Solr 提供了updateRequestProcessorChain的两个实现:

在本节中,您将看到如何使用LangDetect项目来配置语言检测。以下是步骤:

Add dependencies to the lib directives in solrconfig.xml: <lib dir="../../../contrib/langid/lib" regex=".*\.jar" /> <lib dir="../../../dist/" regex="solr-langid-\d.*\.jar" />   Register the LangDetect processor to the UpdateRequestProcessorChain in solrconfig.xml: <processor class="org.apache.solr.update.processor.LangDetectLanguageIdentifierUpdateProcessorFactory">   <lst name="defaults">     <str name="langid.fl">title,abstract,content</str>     <str name="langid.langField">lang</str>   </lst> </processor>   Define the fields in schema.xml: <field name="title" type="string" indexed="true" stored="true" /> <field name="abstract" type="string" indexed="true" stored="true" /> <field name="content" type="string" indexed="true" stored="true" /> <field name="lang" type="string" indexed="true" stored="true" />  

现在,当您将该处理器添加到链中,并将该链注册到更新处理程序并索引文档时,lang字段将根据在文档的titleabstractlanguage字段中检测到的语言自动填充。

生成唯一 ID

如果被索引的文档没有惟一的 ID,并且您希望 Solr 自动为每个文档分配一个 ID,那么您需要在更新链中配置UUIDUpdateProcessorFactory。以下是步骤:

Register the UUIDUpdateProcessorFactory to UpdateRequestProcessorChain in solrconfig.xml: <processor class="solr.UUIDUpdateProcessorFactory">   <str name="fieldName">id</str> </processor>   Add the id field of type String or UUID to hold the unique IDs generated by the processor, in schema.xml: <field name="id" type="string" indexed="true" stored="true" required="true"/>   Optionally, register the id field as a uniqueKey: <uniqueKey>id</uniqueKey>  

如果被索引的文档不包含id值,将生成一个随机 ID 并索引到该字段。如果文档包含一个值,那么 Solr 不会生成它,而是使用文档提供的值。

重复数据删除

UniqueKey可以确保没有重复的文档被编入索引,但是它根据字段中的值来识别重复的文档。如果您的需求更复杂呢?假设你有一个包含汽车特征信息的系统;制造商、型号和年份创建了一个唯一的实体,因此您希望确保没有两个文档是使用相同的制造商、型号和年份信息进行索引的。可以用SignatureUpdateProcessorFactory来实现这个功能。

当您添加文档时,SignatureUpdateProcessor根据fields参数中提供的一组字段,使用signatureClass中配置的签名生成算法生成一个签名,并将其写入signatureField。对于精确的重复检测,可以使用MD5SignatureLookup3Signature 实现,对于模糊或近似的重复检测,可以使用 TextProfileSignature 。以下是配置重复数据删除的步骤:

Register the SignatureUpdateProcessorFactory to UpdateRequestProcessorChain in solrconfig.xml: <processor class="solr.processor.SignatureUpdateProcessorFactory">   <bool name="enabled">true</bool>   <str name="signatureField">signature</str>   <bool name="overwriteDupes">false</bool>   <str name="fields">make,model,year</str>   <str name="signatureClass">solr.processor.Lookup3Signature</str> </processor>   Add a signature field in schema.xml: <field name="make" type="string" stored="true" indexed="true" multiValued="false"/> <field name="model" type="string" stored="true" indexed="true" multiValued="false"/> <field name="year" type="string" stored="true" indexed="true" multiValued="false"/> <field name="signature" type="string" stored="true" indexed="true" multiValued="false"/>  

您还可以将签名添加到现有字段,如id,而不是添加到单独的字段signature

文档到期

在许多情况下,您会希望文档只在有限的时间范围内有效。例如,对于电子商务网站上的闪购,您希望文档在特定时间或特定时间段后过期。Solr 提供了自动删除过期文档的DocExpirationUpdateProcessorFactory。过期文档列表是根据配置的过期字段确定的。自动删除由工厂分叉的后台线程完成,该线程每 N 秒唤醒一次并执行一次deleteByQuery

该处理器提供两种功能。首先,它计算文档的到期日期,并根据提供的生存时间(TTL)值将其填充到一个字段中。TTL 表示所提供的文档具有有限的寿命,并且应该在 N 秒后过期。处理器允许您以两种方式提供 TTL 信息:

  • _ttl_请求参数:更新请求中所有文档过期的持续时间。
  • _ttl_字段:该字段的值为该文档提供 TTL,并覆盖_ttl_请求参数。

为了澄清任何混淆,_ttl_字段是每个文档的,_ttl_请求参数是全局的。基于所提供的 TTL 信息,处理器计算来自NOW的到期日期,并将其存储在到期字段中。以下步骤配置文档到期时间:

Register the DocExpirationUpdateProcessorFactory to UpdateRequestProcessorChain in solrconfig.xml: <processor class="solr.processor.DocExpirationUpdateProcessorFactory">   <str name="expirationFieldName">_expire_me_at_</str> </processor> This populates the _expire_me_at_ field of the document, with the expiry time based on the _ttl_ parameter. No automatic deletion will happen. You either need to delete the documents manually or you can hide the documents from Solr requests by adding a filter query such as fq=-_expire_me_at_:[* TO NOW]. Along with setting the expiration time, the second functionality this processor provides is automatic deletion of a document. If you want to delete the documents automatically, you can add autoDeletePeriodSeconds to the processor. This triggers the deletion thread every N seconds: <processor class="solr.processor.DocExpirationUpdateProcessorFactory">   <int name="autoDeletePeriodSeconds">300</int>   <str name="expirationFieldName">_expire_at_</str> </processor>   Add _expire_me_at_ field in schema.xml: <field name="_expire_me_at_" type="string" stored="true" indexed="true" multiValued="false"/>   Index the documents. When you index the documents, provide the TTL information for expiration to work: $ curl -X POST -H ’Content-Type: application/xml’ ’ http://localhost:8983/solr/hellosolr/update?commit=true &_ttl_=+4HOURS’ -d ’<add> <doc>   <field name="id">1</field>   <field name="title">This title will persist for 4 hours</field>   <field name="abstract">This abstract will persist for 4 hours</field>   <field name="content">This content will persist for 4 hours</field> </doc> <doc>   <field name="id">2</field>   <field name="title">This title will persist for 30 minutes</field>   <field name="abstract">This abstract will persist for 30 minutes</field>   <field name="content">This content will persist for 30 minutes</field>   <field name="_ttl_">+30MINUTES</field> </doc> <add>’  

索引性能

除了我们所讨论的一切,索引性能是一个需要考虑的关键因素。企业搜索需要处理不同数量的数据,这些数据可以大致分为小型、中型或大型。除了体积,另一个重要因素是速度。一些搜索引擎需要高速索引,以处理大量数据或支持接近实时的搜索需求。

对 Solr 索引性能的深入探究超出了本书的范围。本节提供了索引性能的基本概述;第十章提供了更多细节。

根据数据量和索引速度要求,您需要做出必要的设计决策和定制,其中一些如下所示:

  • Solr 架构:基于数据量、可伸缩性需求和容错需求,应该设计一个合适的架构。
  • 索引工具:索引工具有不同的功能;一个提供简单性,另一个提供快速索引。例如,DataImportHandler是单线程的,如果您要索引大量数据,您可能希望有一个定制的多线程实现。
  • CPU 和内存优化:许多 Solr 特性都可以优化 CPU 和内存利用率。solrconfig.xml文件还提供了定制资源利用的配置。

表 5-2 提供了关于 Solr 架构和索引工具的信息,可以根据数据量考虑这些工具。

表 5-2。

Data Volume Decisions

| 数据卷宗 | 太阳能建筑 | 索引工具 | | --- | --- | --- | | 小的 | 独立复制 | SimplePostTool 数据导入处理程序 | | 中等 | 共享和复制的 solrcloud | 索勒 | | 大型 | 索尔鲁德 | 索勒 |

以下是一些可以用来优化索引性能的措施:

  • 执行批量写入,并根据可用资源找到最佳的批量大小。
  • 仅存储您真正需要存储的字段。
  • 仅索引真正需要搜索的字段。
  • 如果可能的话,避免昂贵的预处理和文本分析。
  • 避免频繁的硬提交。

定制组件

Solr 为大多数常见问题和重要用例提供了现成的实现,包括它的每个组件,比如RequestHandler and UpdateRequestProcessor。在本章中,您看到了用于索引 XML、从数据库导入和从 PDF 文件提取的RequestHandler的实现;以及针对生成唯一 ID 和重复数据删除等需求的UpdateRequestProcessor实现。但是您可能需要一个 Solr 没有提供的特性,或者您可能想要编写一个定制的实现。对于这样的场景,Solr 允许您通过扩展现有的基类并将它们插入 Solr 来开发定制组件。

在本节中,您将学习扩展UpdateRequestProcessor并插入一个自定义逻辑。

自定义更新 request 处理器

到目前为止,你一直使用 Solr 提供的UpdateRequestProcessors或者作为 contrib 模块。Solr 还允许你编写自己的UpdateRequestProcessor并链接到updateRequestProcessorChain。下面是编写定制处理器的步骤:

Extend the UpdateRequestProcessorFactory.   Override the getInstance() method to create an instance of CustomUpdateProcessor. If you want to do some initialization on core load, you can override the init() method.   Your CustomUpdateProcessor should extend the abstract class UpdateRequestProcessor.   Override the processAdd() method, to hook in your code to process the document being indexed. This is the method where all the action happens. Its argument AddUpdateCommand has the getSolrInputDocument() method, which contains a reference to the SolrInputDocument.   Get the values from the fields in SolrInputDocument, do your processing, update or delete the value of those fields, or add the result to a new field altogether.   super.processAdd() should always be the last statement of the method.  

下面是一个定制更新处理器的例子,如果一个文档的下载次数超过 100 万,它会添加popular字段并将其值设置为true。这需要进行以下更改:

Write your custom code in Java: Custom implementation of UpdateRequestProcessorFactory package com.apress.solr.pa.chapter05 .processor; import java.io.IOException; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.util.NamedList; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.update.AddUpdateCommand; import org.apache.solr.update.processor.UpdateRequestProcessor; import org.apache.solr.update.processor.UpdateRequestProcessorFactory; public class CustomUpdateProcessorFactory extends UpdateRequestProcessorFactory { /** * Initialize your factory. * This method is not mandatory. */ public void init(NamedList args) {         super.init(args); } @Override public UpdateRequestProcessor getInstance(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor nxt)   {     return new CustomUpdateProcessor(nxt);   } } class CustomUpdateProcessor extends UpdateRequestProcessor {   public CustomUpdateProcessor ( UpdateRequestProcessor nxt) {     super( nxt );   }   @Override   public void processAdd(AddUpdateCommand cmd) throws IOException {     SolrInputDocument doc = cmd.getSolrInputDocument();     Object obj = doc.getFieldValue( "downloadcount" );     if( obj != null ) {       int dc = Integer.parseInt( String.valueOf(obj) );       if( dc > 1000000 ) {         doc.setField("popular", true);       }     }     // you must make this call     super.processAdd(cmd);   } }   Add the executable JAR of the program to lib and register the processor in solrconfig.xml: <lib dir="../lib" regex="solr-practical-approach-*.jar" /> <requestHandler name="/update" class="solr.UpdateRequestHandler">   <lst name="defaults">     <str name="update.chain">customprocessor</str>   </lst> </requestHandler> <updateRequestProcessorChain name="customprocessor">   <processor class="com.apress.solr.pa.chapter``05``.processor.CustomUpdateProcessorFactory" />   <processor class="solr.LogUpdateProcessorFactory" />   <processor class="solr.RunUpdateProcessorFactory" /> </updateRequestProcessorChain>   Add the required field to schema.xml: <field name="id" type="string" indexed="true" stored="true" required="true"/> <field name="downloadcount" type="int" indexed="true" stored="true" /> <field name="popular" type="boolean" indexed="true" stored="true" default="false" />   Index documents: $ curl -X POST -H ’Content-Type: application/xml’ http://localhost:8983/solr/apress/update?commit=true ’ -d ’<add> <doc>   <field name="id">123</field>   <field name="downloadcount">1200000</field> </doc> </add>’  

当您索引此文档时,值true将自动被索引并存储在popular字段中。

经常出现的问题

本节探讨为文档编制索引时遇到的常见问题。

将多个字段复制到单值字段

为文档编制索引时,请确保不要将多个字段添加到单值字段中。这将引发一个异常。例如,如果您试图索引下面的文档,该文档包含多个text字段的值,并且在schema.xml中被定义为multiValued="false",Solr 将抛出一个异常。因此,您要么需要将文本字段设置为multiValued="true",要么只为每个文档提供一个文本元素。

schema.xml:

<field name="text" type="string" indexed="true" stored="true" multiValued="false"/>

XML document:

<add>

<doc>

<field name="id">1</field>

<field name="text">Red Wine</field>

<field name="text">White Wine</field>

</doc>

</add>

文档缺少必需的唯一键字段

如果您在schema.xml中定义了UniqueKey,则必须为该字段提供一个值。如果被索引的文档不包含该字段,Solr 将抛出以下异常:

Document is missing mandatory uniqueKey field: <field>

如果您的用例不需要惟一键,那么为schema.xml中的uniqueKey元素提供一个附加属性:

<uniqueKey required="false">id</uniqueKey>

数据未编入索引

如果您的索引过程没有索引数据,那么检查错误和异常的最佳位置是日志文件。如果日志文件中没有任何信息,并且您已经修改了处理器链,可能的原因是您在链接处理器时遗漏了某些内容。确保RunUpdateProcessorFactory是链中的最后一个处理器。如果您已经编写了自定义处理器,请检查它是否将引用传递给了链中的下一个处理器。

索引很慢

由于多种原因,索引可能会很慢。以下是可以提高索引性能的因素,使您能够相应地调整您的设置:

  • 内存:如果分配给 JVM 的内存很低,垃圾收集将被更频繁地调用,索引将会很慢。
  • 索引字段:索引字段的数量影响索引大小、内存需求和合并时间。仅索引您希望可搜索的字段。
  • 合并因素:合并段是一项开销很大的操作。合并因子越高,索引越快。
  • 提交频率:提交频率越低,索引速度越快。
  • 批量大小:在每个请求中索引的文档越多,索引就越快。

请记住,这些因素都有利弊。考虑系统的内存和搜索要求,为索引过程提供最佳设置。

OutOfMemoryError—Java 堆空间

第二章介绍了OutOfMemoryError,但是我想再讨论一下,因为索引任务是内存密集型的,如果没有适当的计划,你可能会得到一个异常。当您索引文档时,Solr 将它们全部放入内存,因此文档大小很重要。为了避免这个错误,您可以增加分配给 JVM 的内存。另一个解决方案是以较小的块来索引文档。如果您使用的是DataImportHandlerit提供了batchSize属性来批量获取数据并减少内存使用。

另一个要考虑的重要因素是提交时间。Solr 将自上次提交以来的所有文档索引保存在堆内存中,并在提交时释放它。要修复OutOfMemoryError,您可以在solrconfig.xml中更改autoCommit的频率,以在更短的持续时间或添加更少的文档时触发,如下所示:

<autoCommit>

<maxDocs>100000</maxDocs>

<maxTime>60000</maxTime>

</autoCommit>

<autoSoftCommit>

<maxDocs>5000</maxDocs>

<maxTime>5000</maxTime>

</autoSoftCommit>

摘要

在本章中,您学习了如何为文档编制索引。您已经看到 Solr 支持包括 XML 和 JSON 在内的各种输入格式,以及包括文件系统和数据库在内的各种输入源。为了索引数据,你需要某种工具来做索引。您了解了 Solr 附带的工具、操作系统提供的实用程序以及其他可用于索引文档的库。

我们并不总是幸运地拥有正确的数据格式和结构。Solr 使您能够在文档被索引之前对其进行预处理和丰富。它提供了一组处理器,可用于转换被索引的文档。如果捆绑或贡献的处理器不符合您的需要,您可以编写自己的处理器并将其添加到链中。您学习了链接进程和编写定制的处理器。

文档被编入索引后,就可以进行搜索查询了。在下一章,你将学到你一直在等待的东西:如何在 Solr 中搜索文档。

六、搜索数据

前面所有的章节都是本章的敲门砖。到目前为止,您已经了解了 Solr 的基础知识,包括配置核心、定义模式和索引文档。文档成功编制索引后,您就可以搜索结果了。

Solr 提供了广泛的搜索功能,比如查询、分面、点击高亮、拼写检查、建议和自动完成。本章介绍搜索过程,然后概述主要组件。然后进入查询结果的细节。本章涵盖以下主题:

  • 先决条件
  • 搜索过程
  • 搜索组件
  • 查询的类型
  • 查询分析器
  • JSON 请求 API
  • 自定义SearchComponent
  • 常见问题

搜索基础

在上一章中,您了解了用于索引文档的工具。同样的工具也可以用于搜索结果。您还可以从浏览器或任何 GET 工具(如 Wget)执行查询。

对于分析和开发来说,Solr 管理控制台是运行查询的最佳地方,因为它提供了丰富的 GUI,即使您不记得 URL 和请求参数,它也很方便。对于一组标准的功能,控制台提供了相关的文本框;相关的新框会根据您选择的功能自动加载。如果请求参数没有文本框,可以在原始查询参数文本框中提供键值对。在这个文本框中,每一对都应该用一个&符号分隔,并且应用标准 HTTP 请求参数的规则。

下面是一个用户查询示例:请求被发送到带有q参数的hellosolr内核的/select端点,其中包含用户查询 solr 搜索引擎:

$ curl``http://localhost:8983/solr/hellosolr/select?q=solr

对于那些不熟悉 Solr 并希望比较 Solr 中的查询和 SQL 数据库中的查询的人来说,映射如下。记住,SQL 和 Solr 返回的结果可能是不同的。

  • SQL 查询:select album,title,artist``from hellosolr``where album in ["solr","search","engine"]
  • Solr 查询:$ curl``http://localhost:8983/solr/hellosolr/select?q=solr``search engine

先决条件

Solr 搜索能力和行为在很大程度上取决于字段属性,这些属性基于schema.xml中的字段定义。您可以在schema.xml本身或者 Solr admin UI 中的模式浏览器中验证字段属性。要搜索字段并显示匹配的文档,以下是一些先决条件:

  • 应该对可搜索字段(您查询的字段)进行索引。这需要您在schema.xml的字段定义中指定indexed="true"属性。下面是一个示例字段定义,其中的indexed属性用粗体标记:<field name="name" type="text_general" indexed="true" stored="true"/>
  • 作为 Solr 响应的一部分检索的字段(fl参数中的字段)应该被存储。这要求您在schema.xml的字段定义中指定stored="true"属性。下面是一个示例字段定义,其中的stored属性用粗体标记:<field name="name" type="text_general" indexed="true" stored="true" />

第四章提供了更多关于字段定义和属性如何影响搜索行为的细节。

Solr 搜索过程

本节展示了与/select端点相关的底层查询过程,这是 Solr 管理控制台中最常用的默认端点。当您向/select发出请求时,它会被SearchHandler处理,后者是查询文档的主要请求处理器。如果您浏览solrconfig.xml,您会发现这个处理程序被定义并映射到/select,如下所示:

<requestHandler name="/select" class="solr.SearchHandler">

<!-- default values for query parameters can be specified, these

will be overridden by parameters in the request -->

<lst name="defaults">

<str name="echoParams">explicit</str>

<int name="rows">10</int>

</lst>

</requestHandler>

SearchHandler执行一系列SearchComponents来处理搜索请求。负责处理搜索查询的SearchComponent``QueryComponent执行配置好的QueryParser,后者解析原始查询,将其翻译成 Solr 理解的格式。被解析的查询被SolrIndexSearcher用来匹配索引文档。匹配的文档由ResponseWriter根据wt请求参数格式化,然后用户最终得到响应。图 6-1 描述了搜索查询如何流经各种组件来检索匹配的文档。

A978-1-4842-1070-3_6_Fig1_HTML.jpg

图 6-1。

Search request flow

搜索处理程序

SearchHandler是负责处理搜索请求的控制器。处理程序声明一个组件链,并将请求的处理交给它们。组件按照它们在链中注册的顺序执行。链中的每个组件代表一个搜索特性,例如查询或分面。

注册组件

以下是默认情况下在SearchHandler中注册的组件:

  • QueryComponent
  • FacetComponent
  • MoreLikeThisComponent
  • HighlightComponent
  • StatsComponent
  • DebugComponent
  • ExpandComponent

在这些组件中,QueryComponent针对所有请求执行。只有当请求包含适当的参数时,所有其他组件才会执行。例如,FacetComponent仅在搜索请求中提供了facet=true参数时执行。

SearchHandler通过将组件注册到solrconfig.xml中处理程序的first-componentslast-components部分,使您能够将组件添加到链的开头或结尾:

<requestHandler name="/select" class="solr.SearchHandler">

<lst name="defaults">

..

</lst>

<arr name="first-components">

<str>terms</str>

</arr>

<arr name="last-components">

<str>spellcheck</str>

</arr>

</requestHandler>

图 6-2 的左侧提供了在声明前面的first-componentslast-components后注册到SearchHandler的最终组件列表。这些组件将按照它们在列表中出现的顺序执行。

A978-1-4842-1070-3_6_Fig2_HTML.jpg

图 6-2。

Chain of SearchComponents

您可以覆盖默认组件,通过在components部分注册,只执行您想要的组件。例如,如果您只想执行QueryComponentFacetComponentMoreLikeThisComponentTermsComponent,您可以在components部分注册它们,如下所示。组件链将如图 6-2 右侧的表格所示。

<arr name="components">

<str>query</str>

<str>facet</str>

<str>mlt</str>

<str>terms</str>

</arr>

不允许将first-componentslast-componentscomponents段一起注册,这样的尝试会抛出SolrException:

SolrException: First/Last components only valid if you do not declare ’components’

您注册到componentsfirst-componentslast-?? 的组件名必须在solrconfig.xml中定义,以便 Solr 将其映射到相应的可执行 Java 类。例如,如果您将terms注册为SearchHandler中的一个组件,solrconfig.xml应该有如下定义:

<searchComponent name="terms" class="solr.TermsComponent"/>

Note

如果使用first-componentslast-components部分,DebugComponent被设计为总是最后出现。如果不希望这样,您必须在components部分显式声明所有组件。

声明参数

SearchHandler使您能够根据对用户提供的请求参数的所需操作,通过在各种列表中指定参数来控制请求参数的值。它允许您以三种方式声明参数。

默认

defaults值指定了参数列表及其默认值。如果用户请求包含相同的参数,该值将被覆盖。在下面的例子中,rows在默认列表中声明;如果用户请求不包含该参数,默认情况下,响应中将返回 10 个文档。

<lst name="defaults">

<int name="rows">10</int>

</lst>

附加

appends值指定了应该附加到所有搜索请求的参数列表,而不是像在defaults部分的情况下那样被覆盖。对于下面的例子,如果用户请求是q=bob marley,那么appends部分会将fq=genre:reggae添加到请求中。如果在defaults部分指定了相同的参数,它甚至会追加。

<lst name=" appends">

<int name="fq">genre:reggae</int>

</lst>

不变量

invariants值指定了参数列表,其值是强制应用的,并且不能被参数的任何其他定义覆盖,例如在用户查询请求或defaults部分中。这是配置您不想让用户控制其行为的功能的好地方。以下是invariants部分中请求参数的定义示例。

<lst name="invariants">

<str name="fq">country:usa<str>

</lst>

搜索组件

SearchComponent是一个抽象的 Java 类,这个类的每个实现都代表一个搜索特性。实现类应该在solrconfig.xml中声明,然后在处理程序中注册。下面是在solrconfig. xml中访问索引术语的组件TermsComponent的配置:

<searchComponent name="terms" class="solr.TermsComponent"/>

<requestHandler name="/terms" class="solr.SearchHandler" startup="lazy">

<lst name="defaults">

<bool name="terms">true</bool>

<bool name="distrib">false</bool>

</lst>

<arr name="components">

<str>terms</str>

</arr>

在本例中,TermsComponent被注册在components部分,所以默认情况下不会注册其他组件,比如查询或方面。

表 6-1 提供了 Solr 提供的主要组件的关键信息。在表中,“名称”列表示组件的默认名称,您可以更改它。默认栏列出了在SearchHandler中默认配置的组件。要使用非默认的组件,您需要将它注册到现有的处理程序,或者配置一个新的处理程序并将其注册到该处理程序。

表 6-1。

Solr Primary Components

| 成分 | 名字 | 默认 | 描述 | | --- | --- | --- | --- | | `QueryComponent` | `query` | `True` | 处理查询参数以检索相关文档。 | | `FacetComponent` | `facet` | `True` | 生成方面,即通常出现在许多页面左侧的聚合。 | | `MoreLikeThisComponent` | `mlt` | `True` | 查询与结果文档相似的文档。 | | `HighlightComponent` | `highlight` | `True` | 突出显示响应中的查询词。 | | `StatsComponent` | `stats` | `True` | 获取有关字段中值的统计信息。 | | `DebugComponent` | `debug` | `True` | 获取调试信息,如已解析的查询或文档分数说明。 | | `TermsComponent` | `terms` | `False` | 查找索引词及其计数。对自动完成等功能很有用。 | | `TermVectorComponent` | `tvComponent` | `False` | 获取有关匹配文档的附加信息。 | | `SpellCheckComponent` | `spellcheck` | `False` | 获取与查询词相似的词。有用的功能,如拼写纠正和你的意思是。 | | `QueryElevationComponent` | `elevation` | `False` | 配置查询的顶级结果。活动、促销和付费搜索的理想选择。 | | `SuggestComponent` | `suggest` | `False` | 建议与查询词相似的词。对于构建自动建议功能非常有用。 |

对于每个传入的请求,一个SearchComponent分两个阶段执行:准备和处理。SearchHandler按照链中出现的顺序执行所有的准备方法,然后执行所有的处理方法。prepare 方法对请求进行初始化,process 方法进行实际的处理。所有准备方法都保证在任何处理方法之前执行。

你将在第七章和第九章中详细了解这些重要的组件。

QueryParser(查询解析器)

解析用户查询,将其转换成 Solr 能够理解的格式。QueryComponent基于defType请求参数调用适当的解析器。同一个查询可以为不同的查询解析器获取不同的结果,因为每个解析器对用户查询的解释不同。Solr 支持的一些查询解析器是 standard、DisMax 和 eDisMax 查询解析器。在本章的后面你会学到更多关于QueryParser的知识。

QueryResponseWriter

默认情况下,Solr 以 XML 格式返回对查询的响应。为了以另一种支持的格式获得响应,可以在请求中指定或在处理程序中配置wt参数。其他支持的格式有 JSON、CSV、XSLT、javabin、Velocity、Python、PHP 和 Ruby。

javabin 响应返回一个 Java 对象,Java 客户端可以使用这个对象。对于使用 SolrJ 客户端的请求,这是首选的响应格式。类似地,Python、PHP 和 Ruby 格式也可以被这些语言的客户端直接使用。Velocity 是另一种受支持的有趣的响应格式,它允许您使用 Apache Velocity 模板引擎将响应转换成网页。

Solr 5.3.0 支持新的 smile 格式,这是一种二进制格式,非 Java 语言可以使用这种格式进行高效响应。参考 http://wiki.fasterxml.com/SmileFormat 了解这种格式。

Solr 查询

您已经做了一些查询,并且知道您通过使用q参数发送查询请求。但是还有更多。在这一节中,您将看到用于控制文档匹配和排序行为的查询语法。然后你会看到各种各样的解析器。

使用q参数发送的查询由查询解析器进行语法检查和解析。在字段的分析阶段,查询将被标记化,生成的标记将根据索引进行匹配,并根据评分算法进行排名。

图 6-3 描述了如何处理用户查询以搜索相关结果。此图提供了检索相关文档的搜索请求流程,不包括SearchHandler等组件。请参考图 6-1 以获得更广泛的流程视图,该流程适用于任何搜索功能,如分面和建议,而不是专门用于检索文档。

A978-1-4842-1070-3_6_Fig3_HTML.jpg

图 6-3。

Search request processing

图 6-3 中的例子描述了用户查询 kills the mockingbird 的搜索流程。第四章在覆盖领域分析中使用了这个例子。我有意使用这个部分匹配的查询来让您理解真实场景中的匹配。此外,让我们假设处理程序被配置为使用ExtendedDismaxQParser进行查询解析,这允许您执行类似于 Google 中的自由文本搜索。

以下是 Solr 执行的步骤,如图 6-3 所示:

The user-provided query is passed to the query parser. The purpose of the query parser is to parse the user query and convert it into a format that Lucene understands.   The query parser sends the query to appropriate analyzers to break the input text into terms. In this example, the analyzer for the title and artist fields are invoked, which is based on the qf parameter provided in the request.   The query parser returns the Query object, which is provided to SolrIndexSearcher for retrieving the matching documents. The Query object contains the parsed query, which you can see if you execute the search request in debug mode (by providing the additional request parameter debug="true").   SolrIndexSearcher does lots of processing and invokes all the required low-level APIs such as for matching and ranking the documents. It also performs several other tasks such as caching.   SolrIndexSearcher returns the TopDocs object, which is used to prepare the response to send it back to the application.  

图 6-3 中指定的所有任务(在前面的步骤中解释过)都由QueryComponent类协调,它是一个负责执行搜索请求的SearchComponent

默认查询

在 Solr 中,当我们说查询时,默认情况下我们指的是基于术语的匹配。术语是匹配的最小单位。术语可以由一个单词、后续单词或单词的一部分组成。假设您想要匹配单词的一部分,您可以通过使用 N 元语法来创建更小的术语,N 元语法在单词中创建 N 个字符的标记。本节描述如何查询。

查询默认字段

当您仅指定查询字符串和q参数时,您正在查询结果的默认字段:

q=to kill a mockingbird

您可以使用df参数指定默认字段。在下面的例子中,Solr 将对字段album执行查询:

q=to kill a mockingbird&df=album

查询指定的字段

查询特定字段的标准语法使用后跟冒号和值的字段名称,如下所示:

title:mockingbird

这将在title字段中搜索术语mockingbird

现在,如果您的查询包含多个标记,并且您将它指定为:

q=title:to kill a mockingbird&df=album

这将在title字段中查询to,在album字段中查询令牌catch a mockingbird。如果要在一个字段中搜索多个标记,需要用括号将它括起来:

q=title:(to kill a mockingbird)&df=album

这将在title字段中查询令牌,而album字段根本不会被查询。

匹配多个字段中的令牌

标准的查询解析器为查询文档提供了粒度控制。您可以针对不同的术语查询不同的字段。以下查询将在title字段中搜索标记buffalo soldier,并在artist字段中搜索标记bob marley:

q=title:(buffalo soldier) artist:(bob marley)

字段和标记之间应用的默认运算符是OR。Solr 支持一组操作符,允许您将布尔逻辑应用于查询,以指定匹配文档的条件。上述查询也可以通过显式指定运算符来表达:

q=title:(buffalo OR soldier) OR artist:(bob OR marley)

因此,前面的两个查询都将检索包含任何这些术语的所有文档。

查询运算符

以下是查询分析器支持的运算符:

  • OR:执行 Union,如果满足任何子句,文档将匹配。
  • AND:只有两个子句都满足,才会进行关联,匹配一个单据。
  • NOT:排除包含该子句的文档的操作符。
  • +/-:操作员强制条款的出现。+确保包含令牌的文档必须存在,-确保包含令牌的文档不能存在。

请注意,AND / OR / NOT在标准查询解析器中是区分大小写的。因此,查询bob and marley不同于bob AND marley。第一个查询将搜索包含任何标记bobandmarley的文档。第二个查询将搜索包含标记bobmarley的文档。

此外,您可以用多种方式设置查询的格式。例如,q=(Bob AND marley)q=(+bob +marley)构成同一个查询。

短语查询

前面的查询匹配流中任何地方存在这些术语的所有文档。如果您想要查找具有连续术语的文档,您需要执行短语搜索。短语搜索要求在双引号内指定查询。例如,q="bob marley"是一个短语查询。

邻近查询

邻近查询匹配彼此邻近出现的术语。您可以将其视为一个自由短语查询,它考虑了附近的术语。邻近查询要求短语 query 后跟波浪号()运算符和数字距离,用于标识邻近的术语。

为了帮助您理解所有这些概念,请考虑下面的示例索引,它包含四个文档并支持不区分大小写的匹配。

Index

doc1: While Bob took the initial lead in race Marley finally won it.

doc2: Bob Marley was a legendary Jamaicanreggae

doc3: Jamaican singer Bob Marley has influenced many singers across the world.

doc4: Bob is a renowned rugby player.

Query

q=bob marley \\ Default operator OR. All 4 documents match

q=(bob AND marley) \\ AND operator. Both the terms must exist. First 3 documents match

q="bob marley" \\ Phrase query. doc2 and doc3 matches

q="jamaican singer" \\ Phrase query. Only doc3 matches

q="jamaican singer"∼1 \\ Proximity query. Both doc2 and doc3 match

q="singer jamaican"∼1 \\ Proximity query. Only doc3 matches

q="jamaican singer"∼3 \\ Proximity query. Both doc2 and doc3 match

在邻近查询q="jamaican singer"∼1中指定数值 1 匹配doc2doc3,因为移动doc2Jamaican reggae singer序列中的一个术语将形成短语。但是在查询q="singer jamaican"∼1中,只有doc3匹配,因为singerjamaican之前,需要更多的移动来匹配doc2的短语。正如您在最后一个例子中看到的,查询中的数值 3 将使它同时匹配doc2doc3

模糊查询

除了精确匹配术语之外,如果想要匹配相似的术语,可以使用模糊查询。模糊查询基于 Damerau-Levenshtein 距离或编辑距离算法,该算法确定将一个令牌转换为另一个令牌所需的最少编辑次数。要使用它,标准查询解析器需要在术语后有一个波浪号(),后面可选地跟一个数值。该数值可以在 0 到 2 的范围内。默认值 2 匹配最大编辑次数,值 0 表示没有编辑,与术语查询相同。模糊查询的分数基于编辑次数:0 次编辑表示最高分(因为最不宽松),2 次编辑表示最低分(因为最宽松)。下面是模糊查询的语法:

Syntax

q=<field>:<term>∼

q=<field>:<term>∼N // N specifies the edit distance

Example

q=title:mockingbird∼2

q=title:mockingbird∼    // effect is same as above

q=title:mockingbird∼1.5 // invalid

早些时候,Solr 允许将 N 指定为 0.0 到 1.0 范围内的浮点值,该值被转换为适当的编辑距离。在 4.0 版中,引入了一种更直接的方法来指定整数值 0、1 或 2,其中表示 N 个插入、删除或替换。Solr 不允许小数编辑距离,如果您的查询包含小数编辑距离,将会响应以下错误:

"error": {

"msg": "org.apache.solr.search.SyntaxError: Fractional edit distances are not allowed!",

"code": 400

}

模糊查询是搜索容易出现拼写错误的文本(例如 tweets、SMS 或移动应用程序)的好解决方案,在这些情况下,词干和语音技术是不够的。

模糊查询中的波浪号不要与邻近查询中的波浪号混淆。在邻近查询中,波浪号应用于引号之后(例如,q="jamaican singer"∼1)。模糊查询没有引号,并且在标记后应用波浪号(例如,q=bob∼1 marley∼1)。

通配符查询

Solr 标准查询解析器支持单个查询的通配符搜索。您可以指定通配符?,它只匹配一个字符,或者指定*,它匹配零个或多个字符。它们不能应用于数值或日期字段。以下是通配符查询示例:

q=title:(bob* OR mar?ey) OR album:(*bird)

通配符查询的一个经典例子是*:*,它匹配所有字段中的所有术语,并检索语料库中的所有文档。请注意,不能在字段名中使用通配符来搜索符合某个模式的一个或多个术语(可以在整个索引中搜索符合某个模式的一个或多个术语)。以下查询无效,将引发异常:

q=*:mockingbird // undefined field *

q=ti*:mocking*  // SyntaxError: Cannot parse ’ti*:mocking*’

通配符查询执行起来可能会很慢,因为它需要遍历所有与模式匹配的术语。您应该避免以通配符开头的查询,因为它们可能会更慢。通配符查询为所有匹配的文档返回 1.0 的常量分数。

范围查询

范围查询支持匹配下限和上限之间的字段值。范围查询广泛用于数值和日期字段,但也可用于文本字段。范围查询允许边界包含、排除或两者的组合。它们也支持通配符。以下是范围查询的示例:

q=price:[1000 TO 5000] // 1000 <= price <= 5000

q=price:{1000 TO 5000} // 1000 < price > 5000

q=price:[1000 TO 5000} // 1000 <= price > 5000

q=price:[1000 TO *]    // 1000 <= price

查询解析器的语法要求方括号[]表示包含值,花括号{}表示排除值。连接器TO应该用大写字母指定;否则,解析器将抛出一个异常。范围查询为所有匹配的文档提供 1.0 的恒定分数。

函数查询

在真实的场景中,您并不总是希望文档仅基于 TF-IDF 匹配进行排名。其他因素也会发挥作用,您可能希望这些因素对整个文档排名有所贡献。例如,人气计数在电子商务网站的产品排名中起着重要作用,用户评级在电影或产品评论中起着作用,空间信息在本地搜索中起着作用。

函数查询的解析器使您能够定义一个函数查询,该函数查询根据一个数值(常量或字段中的索引值或另一个函数的计算值)生成一个分数。

函数查询因使用函数来导出值而得名。在计算分数、返回字段值或对文档进行排序时,可以应用函数查询。这个主题在第七章中有详细介绍,在那里你会看到函数查询是如何帮助推断一个实际的相关性分数的。

过滤查询

过滤查询帮助您过滤掉与请求不相关的文档,留给您一个执行主查询的文档子集。筛选子句只与索引匹配,不计分,不匹配筛选条件的文档会被直接拒绝。应该在请求参数fq中指定过滤器查询。过滤子句也可以在主查询中指定,但是过滤查询在获得快速响应时间方面证明是有利的。Solr 为过滤器查询维护一个单独的缓存。当新的查询带有相同的过滤条件时,会发生缓存命中,从而导致查询时间减少。解析器允许多个过滤器查询,并且为每个fq参数创建一个单独的缓存。因此,您可以在同一个fq参数中包含所有过滤子句,或者在不同的fq参数中包含每个过滤子句。将条件放在哪里的选择完全取决于您的业务案例。此示例以不同的方式指定相同的查询子句:

// no filter cache

q=singer(bob marley) title:(redemption song) language:english genre:rock

// one cache entry

q=singer(bob marley) title:(redemption song)&fq=language:english AND genre:rock

// two cache entry

q=singer(bob marley) title:(redemption song)&fq=language:english&fq=genre:rock

fq参数是关联的。如果指定了多个fq参数,查询解析器将选择匹配所有fq查询的文档子集。如果希望子句被OR连接,那么过滤查询应该是单个fq参数的一部分。

例如,在法律搜索引擎中,律师一般从特定国家的法院搜索案例。在这种情况下,国家名称是一个很好的过滤查询,如下所示:

q=labour laws&fq=country:usa

类似地,在空间搜索(如本地交易搜索)中,城市和交易类别可以使用fq参数,如下所示:

q=product:restaurant&fq=city:california&fq=category:travel

但是,如果拉斯维加斯市的大多数查询是关于旅游交易的,则请求可以形成如下:

q=product:hotel&fq=city:"las vegas" AND category:travel

查询提升

在一个查询中,并不是所有的术语都同样重要,因此 Solr 使您能够提高查询术语的重要性。查询提升是 Solr 在计算分数时考虑的一个因素;较高的提升值会返回较高的分数。提升术语的因素取决于您的需求、术语的相对重要性以及术语的预期贡献程度。得出最佳值的最佳方法是通过实验。

通过在令牌后指定一个(^)运算符,后跟增强因子来应用增强。DisMax 和 e DisMax 查询解析器允许您提升查询字段,从而提升在该字段上搜索的所有术语。默认情况下,所有字段和令牌都获得 1.0 的提升。

下面是一个应用了 boost 的示例查询。该查询指定 redemption 和 song 是最重要的术语,提升值为 2.0,其次是术语 bob 和 marley,提升值为 1.4;所有其他术语的默认提升值为 1.0。

q=singer(bob marley)¹.4 title:(redemption song)².0 language:english genre:rock

您将在第八章的中了解查询提升如何影响文档得分。

全局查询参数

Solr 提供了一组查询参数。有些是通用的,适用于所有类型的搜索请求,而有些是特定于用于处理请求的查询解析器的。

这是适用于所有搜索请求的查询参数的综合参考部分。特定于查询解析器的请求参数包含在本章的“查询解析器”一节中。

q

此参数定义用于检索相关文档的主查询字符串。这是一个强制参数,如果没有指定,其他参数将不起任何作用。

会计季度(fiscal quarter)

使用此参数指定筛选查询。一个请求可以有多个fq参数,每个参数都是关联的。您已经在“过滤查询”一节中了解了这个参数。

指定要在结果集中检索的文档数。默认值为 10。不要指定一个很高的值,因为这样效率会很低。一个更好的替代方法是分页和分块获取文档。当您仅对其他搜索特性(如方面或建议)感兴趣时,可以指定rows=0

开始

指定文档返回的偏移量。默认值为 0(返回第一个匹配文档的结果)。您可以同时使用startrows来获得分页的搜索结果。

定义类型

指定用于解析查询的查询解析器。每个解析器都有不同的行为,并支持专门的参数。

分类

指定以逗号分隔的字段列表,结果应根据该列表进行排序。字段名后面应该跟有ascdesc关键字,以分别按升序或降序对字段进行排序。要排序的字段应该是单值的(multiValued=false),并且应该只生成一个令牌。非数值字段按字典顺序排序。要按分数排序,您可以指定score作为字段名以及asc / desc关键字。默认情况下,结果按分数排序。这里有一个例子:

sort=score desc,popularity desc

股骨长度

指定要在响应中显示的以逗号分隔的字段列表。如果列表中的任何字段不存在或未存储,它将被忽略。要在结果中显示分数,可以将分数指定为字段名称。例如,fl=score,*将返回文档的分数以及所有存储的字段。

重量

指定返回响应的格式,如 JSON、XML 或 CSV。默认的响应格式是 XML。响应是由响应编写器格式化的,这在本章开始时已经介绍过。

调试查询

这个布尔参数在分析查询是如何被解析的以及文档是如何得到分数的方面非常有用。调试操作成本很高,不应在实时生产查询中启用。该参数目前仅支持 XML 和 JSON 响应格式。

解释其他

explainOther对于分析不属于调试解释的文档非常有用。debugQuery解释作为结果集一部分的文档的分数(如果您指定rows=10debugQuery将只为这 10 个文档添加解释)。如果您想要额外文档的解释,您可以在explainOther参数中指定一个 Lucene 查询来识别这些额外文档。记住,explainOther查询将选择要解释的附加文档,但是解释将针对主查询。

允许的时间

指定允许执行查询的最长时间(以毫秒为单位)。如果达到这个阈值,Solr 将返回部分结果。

奥米泰德

默认情况下,Solr 响应包含responseHeader,它提供响应状态、查询执行时间和请求参数等信息。如果您不希望这些信息作为响应,您可以指定omitHeader=true

躲藏

默认情况下,缓存是启用的。您可以通过指定cache=false来禁用它。

查询分析器

您已经学习了一些关于查询解析器的知识,并且知道defType参数指定了查询解析器的名称。在本节中,您将看到主要的查询解析器:standard、DisMax 和 eDisMax。

标准查询解析器

标准查询解析器是 Solr 中的默认解析器。您不需要为此指定defType参数。标准查询解析器适用于结构化查询,并且是形成复杂查询的优秀解析器,但是它有一些限制。它希望您严格遵守语法,如果遇到任何意外字符,它将抛出异常。特殊字符和符号应该正确转义,否则 Solr 将抛出语法错误:

<lst name="error">

<str name="msg">org.apache.solr.search.SyntaxError:

Cannot parse ’article:mock:bird’: Encountered " ":" ":

"" at line 1, column 12.

Was expecting one of:

<EOF>

<AND> ...

<OR> ...

<NOT> ...

"+" ...

"-" ...

<PREFIXTERM> ...

<LPARAMS> ...

...

<NUMBER> ...

</str>

<int name="code">400</int>

DisMax 查询解析器

标准的查询解析器是默认的 Solr 解析器,但是它提供的灵活性很小,因为它是基于结构的,并且接受像title:(buffalo OR soldier) OR singer:(bob OR marley)这样语法上必须正确的布尔查询。如果语法不正确,或者用户查询包含在语法中有特殊含义的符号,Solr 将抛出异常。

Solr 提供了 DisMax 解析器来处理类似人类的自然查询和短语。它允许跨字段的有机搜索,从而不受特定字段中的值的限制。它还允许您为字段增加权重,这是查询任何搜索域的更实用的方法。例如,对于典型的电子商务网站,品牌将具有高的相关搜索相关性。

使用 DisMax 查询解析器

DisMax 解析器更适合 Google 这样的搜索引擎,标准解析器更适合高级搜索,在高级搜索中,用户可以明确指定在哪个字段上搜索什么。例如,在法律搜索中,辩护律师可能更喜欢在一个框中指定诉讼细节,在另一个框中指定请愿者细节,而在另一个框中指定法院名称。

因为 DisMax 是针对自然用户查询的,所以它尽最大努力优雅地处理错误场景,几乎不会抛出任何查询解析异常。

另一个区别是,标准解析器将所有子查询的分数相加,而 DisMax 考虑所有子查询返回的最大分数——因此命名为 DisMax,这意味着析取最大值。如果您希望 DisMax 计分像标准解析器一样,那么您可以应用一个 tiebreaker,这可以使它像标准解析器一样计分。

如果你正在构建你的第一个搜索引擎,DisMax 是一个比 standard 更容易的选择。

下面是一个 DisMax 查询示例:

$ curl``http://localhost:8983/solr/hellosolr/select

q=to kill a mockingbird&qf=movie² artist¹.2 description

&rows=10&fl=score,*&defType=dismax

查询参数

除了前面提到的通用查询参数,DisMax 还支持其他特定于它的参数。这些参数可用于控制文档的匹配和排序,而无需对主查询进行任何更改。Solr 管理控制台中的 Query 选项卡为 DisMax 解析器提供了一个复选框;单击它之后,特定于解析器的参数将自动变得可见。以下是 DisMax 支持的附加参数:

q

指定用户查询或短语。为该参数提供的值是自然查询,其结构不同于标准查询。

速射的

该参数指定应应用q参数中提供的查询的字段列表。所有查询术语都在所有指定的字段中匹配,您不能有选择地指定不同的术语来匹配不同的字段。如果一个查询包含一个字符串,但是您的qf参数指定了一个intdate字段,不用担心;这将被很好地处理,只有查询中的整数会与一个int字段匹配。

qf参数中的所有字段都应该用空格分隔,并且可以有选择地增加,如下所示。如果您的列表指定了一个未在schema.xml中定义的字段,它将被忽略。

qf=album² title² description

q .老

DisMax 有一个用于查询的回退机制。如果缺少q参数,它将使用标准查询解析器解析在q.alt参数中指定的查询。

毫米

在标准的查询解析器中,您可以在术语和子查询上显式应用布尔运算符。mm参数意味着最小值应该匹配,它通过允许您控制应该匹配的子句的数量,提供了一种更实用的解决问题的方法。它的值可以是整数、百分比或复杂表达式。以下是详细情况:

  • 整数:指定必须匹配的最小子句数。还可以指定负值,这意味着可接受的不匹配可选子句的最大数量。例如,如果一个查询包含四个子句,mm=1意味着至少有一个子句必须匹配,mm=-1意味着至少有三个子句必须匹配。值越高,搜索越严格,值越低,搜索越宽松。
  • 百分比:整数应用硬值,并且是固定的。百分比指定与可选条款总数相匹配的最小条款数。例如,如果一个查询包含四个子句,mm=25%表示至少应该匹配一个子句,mm=-25%表示至少应该匹配三个子句(75%)。指定mm=100%意味着所有的子句必须匹配,其行为与对所有查询子句应用AND操作符相同。指定mm=0%意味着没有最小期望值,其行为将与对所有查询子句应用OR操作符相同。如果从百分比中计算出的数字包含一个十进制数字,它将被舍入到最接近的较小整数。
  • 表达式:您可以应用一个或多个由空格分隔的条件表达式,格式为n<integer|percent。一个表达式指定,如果可选子句的数量大于n,则指定的条件适用;否则,所有条款都是强制性的。如果指定了多个条件,每个条件仅在n的值大于其前面条件中指定的值时有效。例如,表达式2<25% 4<50%指定如果查询有两个可选子句,它们都是强制的;如果它有三个或四个可选条款,至少有 25%应该匹配;如果它有四个以上的子句,至少 50%应该匹配。
全国工业产品生产许可证

查询短语 slop 允许您控制短语搜索中的接近程度。在标准的查询解析器中,通过指定一个波浪号()后跟一个邻近因子来执行邻近搜索。DisMax 解析器提供了一个额外的参数qs,用于控制邻近因子并保持主查询简单。下面是一个使用标准和 DisMax 解析器时应用相同近似性的示例:

q="bob marley"∼3&defType=standard

q="bob marley"&qs=3&defType=dismax

脉波频率(Pulse Frequency 的缩写)

DisMax 解析器使您能够提升匹配的文档。您可以在pf参数中指定一个字段列表以及 boost,这些字段中的匹配短语会相应地得到增强。重要的是要记住,这个参数的作用是提高匹配短语的排名,而不影响匹配文档的数量。pf参数可以对结果重新排序,但是总的结果计数将始终保持不变。

pf=album² title^ 2

著名图象处理软件

该参数允许您对短语字段应用邻近系数,没有pf参数则没有意义。由于pf只对匹配的文档排序有贡献,对决定匹配计数没有作用,同样的规则也适用于ps

领带

标准解析器将子查询的分数相加,但是 DisMax 取子查询分数的最大值,因此命名为(dis)max。一旦应用了 tie,DisMax 就开始对分数求和,计算行为类似于标准解析器。允许 0.0 到 1.0 范围内的浮点值。tie=0.0取子查询中的最大值,tie=1.0将所有子查询的得分相加。DisMax 使用以下算法来计算分数:

score = max(subquery1,..,subqueryn) + (tie * sum(other subqueries)

如果两个文档得到相同的分数,您可以使用该参数让子查询影响最终分数并打破它们之间的平局。

贝克勒尔

boost 查询参数使您能够添加一个查询子句来提高匹配文档的分数。可以提供多个bq参数,每个参数中子句的分数会被添加到主查询的分数中。例如,您可以使用bq来提高titles的评级:

q=bob marley&bq=rating:[8 TO *]

板英尺(board foot)

通过应用函数查询,可以使用增强函数来增强文档。升压功能在bf请求参数中提供。和bq一样,可以多次指定这个参数,分数是累加的。以下是一个 boost 函数的示例,该函数使用sqrt()函数查询,通过计算评级的平方根来计算助推因子。第七章更详细地介绍了函数查询。

q=bob marley&bf=sqrt(rating)

DisMax 查询示例

下面是一个几乎完全成熟的 DisMax 查询的示例:

$ curl``http://localhost:8983/solr/hellosolr/select

q=to kill a mockingbird&qf=album² title² description

&rows=10&fl=album,title&defType=dismax&qs=3&mm-25%

&pf=album³ title³&ps=2&tie=0.3

&bf=sqrt(rating)&bq=rating:[8 TO *]

eDisMax 查询解析器

顾名思义,扩展的 DisMax 查询解析器是 DisMax 查询解析器的扩展。它支持 DisMax 提供的特性,增加了其中一些特性的智能性,并提供了额外的特性。您可以通过设置defType=edismax来启用这个查询解析器。

eDisMax 查询解析器支持 Lucene 查询解析器语法,而 DisMax 不支持。DisMax 不允许您在特定字段中搜索特定令牌。您在qf参数中指定要搜索的字段集,您的查询将应用于所有这些字段。但是,因为 eDisMax 支持 Lucene 查询,所以您也可以在特定的字段上执行特定的令牌。以下是一个查询示例:

Query

$ curl``http://localhost:8983/solr/hellosolr/select

q=buffalo soldier artist:(bob marley)&debugQuery=true

&defType=dismax&qf=album title

DebugQuery

<str name="parsedquery">

(+(DisjunctionMaxQuery((title:buffalo | album:buffalo))

DisjunctionMaxQuery((title:soldier | album:soldier))

DisjunctionMaxQuery(((title:artist title:bob) | (album:artist album:bob)))

DisjunctionMaxQuery((title:marley | album:marley))) ())/no_coord

</str>

在这个调试查询中,您可以看到 DisMax 将artist字段视为一个查询标记,并在qf参数中指定的所有字段上搜索它。我们期望解析器在字段artist中搜索bob marley。因为 eDisMax 支持 Lucene 查询,所以它将满足您的期望。

Query

$ curl``http://localhost:8983/solr/hellosolr/select

q=buffalo soldier artist:(bob and marley)

&debugQuery=true&defType=edismax&qf=album title

DebugQuery

<str name="parsedquery">

(+(DisjunctionMaxQuery((title:buffalo | album:buffalo)) DisjunctionMaxQuery((title:soldier | album:soldier)) (+artist:bob +artist:marley)))/no_coord

</str>

此外,您可以注意到 eDisMax 将and解析为布尔运算符,并通过在标记bobmarley之前应用+运算符来强制标记bob和【】。eDisMax 支持ANDORNOT+-等布尔运算符。Lucene 查询解析器不把小写标记and/or视为布尔操作符,但是 eDisMax 把它们视为有效的布尔操作符(相当于AND/OR)。

eDisMax 支持 DisMax 的请求参数。以下是 eDisMax 提供的附加请求参数:

低级运算符

在前面的例子中,您看到 eDisMax 将小写字母and视为布尔运算符AND。如果你想禁用这个特性,让 eDisMax 把andor当作其他令牌,你可以设置lowercaseOperators=false。默认情况下,该布尔参数设置为true

促进

boost参数的功能与bf相似,但是它的分数与主查询的分数相乘,而bf的分数相加。eDisMax 允许多个boost参数,并使用BoostedQuery将每个参数的分数乘以主查询的分数。

Query

boost=log(popularity)&boost=sum(popularity)

DebugQuery

<str name="parsedquery">

BoostedQuery(boost...,

product(log(int(popularity)),sum(int(popularity)))))

</str>

pf2/pf3

pf参数提高与精确短语匹配的文档的分数。pf2pf3分别创建大小为 2 和 3 的瓦片区,并对文档进行匹配以增强它们。对于查询top songs of bob marley,创建的短语如下:

pf=[’top songs of bob marley’]

pf2=[’top songs’,’songs of’,’of bob’,’bob marley’]

pf3=[’top songs of’,’songs of bob’,’of bob marley’]

pf一样,在请求中可以指定多个pf2 / pf3参数。

ps2/ps3

pf参数一样,可以通过使用ps2ps3分别在pf2pf3上应用斜坡。如果未指定该参数,ps参数的值将成为其默认值。

停止言语

eDisMax 允许您通过设置布尔参数stopwords=false绕过字段分析链中配置的StopFilterFactory

超滤膜

此参数指定用户字段(允许用户查询的字段)。该值可以是特定的字段名称或字段的通配符模式。任何别名也可以用作字段名。

别名

正如 SQL 数据库允许您为列提供别名一样,eDisMax 支持字段的别名。别名可以映射到在schema.xml中定义的单个字段或一组字段。该参数可配置如下:

f.song.qf=title

当您想要将与某个概念相关的字段组合在一起时,这也证明是有帮助的,如下例所示:

f.artist.qf=singer,musician,actor, actress

JSON 请求 API

在本章中,您已经使用请求参数来查询结果,但是这种方法有以下限制:

  • 它是非结构化的,因此指定适用于特定字段的参数不方便。您将在 faceting 和 terms 组件等功能中看到字段级参数,在这些功能中,您通常希望以不同的方式搜索不同的字段。第七章展示了这些查询的例子。
  • 创建带有大量参数的请求是不方便的,而且由于缺乏可读性,您可能会多次添加同一个参数。
  • 它是非类型化的,将所有内容都视为字符串。
  • 错误的参数名会被忽略,没有办法验证它。

为了解决查询参数的所有这些限制,Solr 5.1 引入了 JSON 请求 API。这个 API 允许您指定 JSON 格式的请求,可以在请求体中发送,也可以作为json请求参数的一部分发送。Solr 还允许在一个查询中组合 JSON 和请求参数,这样一些参数可以在 JSON 主体中,而另一些可以在请求参数中,用一个&符号(&)隔开。

表 6-2 为 API 支持的每个 JSON 请求提供了一个示例查询。表中的第一个请求基于标准请求参数,以帮助您比较这两种类型的请求。此外,示例使用了/query端点而不是/select,因为默认情况下在solrconfig.xml中配置它来返回 JSON 响应。注意,表中的所有请求都返回相同的 JSON 响应。

表 6-2。

Approaches for Search Queries

| 请求类型 | 示例查询 | 描述 | | --- | --- | --- | | 请求参数 | `$ curl http://localhost:8983/solr/hellosolr/query?q=bob` `marley&rows=20` | 我们在本书中一直使用的标准方法。 | | 获取请求正文 | `$ curl http://localhost:8983/solr/hellosolr/query` `-d` `’{` `"query":"bob marley",` `"limit":20` | 通过使用`GET`方法在请求体中指定 JSON 格式的查询。如果您使用任何其他客户端,您可能需要在请求中指定`Content-Type: application/json`。 | | 发布请求正文 | `$ curl -H "Content-Type: application/json" -X POST` `http://localhost:8983/solr/hellosolr/query -d` `’{` `"query":"bob marley",` `"limit":20` | 通过使用`POST`方法在请求体中指定 JSON 格式的查询。如果您使用的是 Postman 之类的 REST 客户端,这种方法非常有用。 | | 请求参数中的 JSON | `$ curl http://localhost:8983/solr/hellosolr/query`?json='{ `"query":"bob marley",` `"limit":20` `}’` | 查询在`json`请求参数中指定。 | | 两者的结合 | `$ curl``http://localhost:8983/solr/hellosolr/query``?json=’{``"query":"bob marley"` | 查询使用 JSON API 和请求参数的组合。 | | JSON 中的请求参数 | `$ curl``http://localhost:8983/solr/hellosolr/query``-d``’{``params: {``q:"bob marley",``rows:20``}``}’` | 使用 JSON 主体进行查询,该主体在`params`块中指定标准请求参数。`params`块允许您在 JSON 主体中指定任何标准请求参数。 | | 参数代换 | `$ curl``http://localhost:8983/solr/hellosolr/query?QUERY=bob``marley&RESULTCOUNT=20 -d``’{``"query":"${QUERY}",``"limit":${RESULTCOUNT}` | JSON 主体使用参数替换来填充参数值。JSON 请求体完全兼容参数替换,你将在第七章中了解到。 |

JSON 请求 API 还没有成熟到可以包含所有的请求参数。到目前为止,它只支持 Solr 支持的参数子集。此外,JSON 主体参数的名称不同于标准的请求参数。例如,如果在 JSON 主体中指定了defType参数,这是一个有效的请求参数,Solr 将报告以下异常:

error":{

"msg":"Unknown top-level key in JSON request : defType",

"code":400

}

表 6-3 列出了 JSON 主体参数和它们对应的标准请求参数。如果您提供一个无效的 JSON 参数,Solr 将抛出前面提到的同样的异常。

表 6-3。

JSON API Parameter Name Mapping

| 标准请求参数 | JSON API 参数 | | --- | --- | | `q` | `Query` | | `fq` | `Filter` | | `fl` | `Fields` | | `start` | `Offset` | | `rows` | `Limit` | | `sort` | `Sort` |

定制 Solr

在本章中,您学习了 Solr 提供的搜索处理程序、搜索组件和查询解析器及其实现。这些实现非常适合标准用例,Solr 试图使它们高度可配置,以适应各种各样的需求。然而,您可能仍然有一个特定于您需求的案例,或者您可能正在开发一些对 Solr 有很大贡献的东西。在这些场景中,您可能想要编写一个定制组件并将其与 Solr 挂钩。

Solr 为挂钩你的插件提供了一个广泛的选择,一个你已经在第五章中看到的例子。你可以在 https://cwiki.apache.org/confluence/display/solr/Solr+Plugins 的官方文档中找到 Solr 中可插拔类的列表。集成过程相当简单,不需要修改 Solr 源代码。您可以将您的插件视为类似于 Solr contrib 模块提供的插件。

以下是向 Solr 添加任何自定义插件的高级步骤:

Create a Java project and define your class, which extends an API provided by Solr based on where you want to hook your implementation in Solr. You need to add the required Solr dependencies to your project to access the API classes.   Add your custom functionality by overriding the API methods. This requires you to understand the input parameters of the API, to read the required values from them, and to add your functionality for processing. You may also need to consider other factors such as how your custom functionality would affect the other components downstream in the chain, if applicable.   Package your project as a JAR file and add it to lib directory, which is available in Solr’s classpath.   Define the feature in either solrconfig.xml, schema.xml, or another file where it fits.   Wire your named definition to the appropriate component. As Solr assembles the customizable components through XML configuration to build a feature, you either need to replace the existing name with what you defined in step 4 or add your name to the chain, if applicable.  

在下一节中,您将通过一个例子学习如何在 Solr 中挂接一个定制的SearchComponent

自定义搜索组件

假设您想要根据上传到文件中的常见拼写错误执行拼写检查。在这种情况下,表 6-1 中描述的 Solr 提供的SpellCheckComponent将没有帮助。在这种情况下,您可能希望为拼写检查器编写自己的实现。您可以通过扩展SearchComponent API 并将其插入 Solr 来将这个定制功能与 Solr 挂钩。

以下是插入自定义SearchComponent的步骤。

扩展搜索组件

创建一个 Java 类并扩展SearchComponent抽象类。您需要将所需的依赖项添加到项目中。

public class CustomSearchComponent extends SearchComponent {

}

您可以将$SOLR_DIST/dist目录中的solr-core-5.3.1添加到您的项目类路径或 Maven 依赖项中,具体视情况而定,如下所示:

<dependency>

<groupId>org.apache.solr</groupId>

<artifactId>solr-core</artifactId>

<version>5.3.1</version>

</dependency>

重写抽象方法

覆盖prepare()process()getDescription()方法。在getDescription()方法中,提供组件的简短描述。任何注册组件的prepare()方法都在process()方法之前执行。如果您的组件的目的是修改任何其他组件的处理,那么prepare()方法是修改请求参数的好地方,该请求参数由您想要更改其行为的组件的 process 方法使用。process()方法是您可以编写所有定制逻辑的地方。

@Override

public String getDescription() {

return null;

}

@Override

public void prepare(ResponseBuilder rb) throws IOException {

}

@Override

public void process(ResponseBuilder rb) throws IOException {

}

获取请求和响应对象

ResponseBuilder获取请求和响应对象,以获取定制处理所需的信息:

@Override

public void process(ResponseBuilder rb) throws IOException {

SolrQueryRequest req = rb.req;

SolrQueryResponse rsp = rb.rsp;

// your custom logic goes here

}

将 JAR 添加到库中

将可执行文件 JAR 添加到 Solr 库中。

向处理程序注册

要执行定制组件,应该在solrconfig.xml中定义它,并注册到所需的处理程序。

样本组件

下面是一个简单的要求 JSON 响应的SearchComponent实现。如果请求中没有指定wt参数,它会将其设置为 JSON。如果它被指定为其他东西,它会将其重置为 JSON,并在 JSON 中响应相应的消息。消息在单独的部分中返回。如果您正在实现一个新特性,您可以类似地在一个单独的部分中返回结果。

以下示例还修改了请求参数。从请求对象中检索的SolrParams是只读的,因此您需要创建一个ModifiableSolrParams的实例,复制所有现有的参数,并在请求中进行所有的修改和设置,替换现有的值。

下例中的所有逻辑都写在prepare()方法中,因为您希望它在请求的实际处理完成之前执行。

Java 源代码

下面是 Java 源代码:

package com.apress.solr.pa.chapter 06.component;

import java.io.IOException;

import org.apache.solr.common.params.CommonParams;

import org.apache.solr.common.params.ModifiableSolrParams;

import org.apache.solr.common.params.SolrParams;

import org.apache.solr.common.util.NamedList;

import org.apache.solr.common.util.SimpleOrderedMap;

import org.apache.solr.handler.component.ResponseBuilder;

import org.apache.solr.handler.component.SearchComponent;

import org.apache.solr.request.SolrQueryRequest;

import org.apache.solr.response.SolrQueryResponse;

public class JsonMandatorComponent extends SearchComponent {

public static final String COMPONENT_NAME = "jsonmandator";

@Override

public String getDescription() {

return "jsonmandator: mandates JSON response.";

}

@Override

public void prepare(ResponseBuilder rb) throws IOException {

SolrQueryRequest req = rb.req;

SolrQueryResponse rsp = rb.rsp;

SolrParams params = req.getParams();

ModifiableSolrParams mParams

= new ModifiableSolrParams(params);

String wt = mParams.get(CommonParams.WT);

if(null != wt && !"json".equals(wt)) {

NamedList nl = new SimpleOrderedMap<>();

nl.add("error",

"Only JSON response supported. Ignoring wt parameter!");

rsp.add(COMPONENT_NAME, nl);

}

mParams.set(CommonParams.WT, "json");

req.setParams(mParams);

}

@Override

public void process(ResponseBuilder rb) throws IOException {

}

}

solrconfig.xml

以下是要在solrconfig.xml中完成的更改:

<lib dir="directory" regex="solr-practical-approach-1.0.0.jar" />

<searchComponent name="jsonmandator"

class="com.apress.solr.pa.``ch

<requestHandler name="/select" class="solr.SearchHandler">

<!-- default values for query parameters can be specified, these

will be overridden by parameters in the request

-->

<lst name="defaults">

<str name="echoParams">explicit</str>

<int name="rows">10</int>

</lst>

<arr name="first-components">

<str>jsonmandator</str>

</arr>

</requestHandler>

询问

通过在wt参数中将响应格式指定为 XML(搜索请求不支持 XML)来创建查询:

$ curl http://localhost:8983/solr/hellosolr/select?q=product:shirt&wt=xml&indent=true

反应

定制组件强制 JSON 响应,并添加一个带有适当错误消息的部分。图 6-4 显示了 Solr 为前面的查询返回的响应。

A978-1-4842-1070-3_6_Fig4_HTML.jpg

图 6-4。

Search request manipulation by the custom SearchComponent

常见问题

本节介绍了搜索结果时的一些常见问题。

我在 fieldType 定义中使用了 KeywordTokenizerFactory,但是为什么我的查询字符串在空白上被标记化了?

意外的标记化可能是由于查询解析器,在查询时字段分析中应该没有问题。查询解析器对空白进行标记,以识别查询子句和操作符,所以当查询到达过滤器时,它已经被标记了。您可以通过转义查询字符串中的空格来解决这个问题。下面是一个转义查询示例,展示了您的预期行为。

q=bob\ marley&debugQuery=true&defType=edismax&qf=album title

如何找到所有没有价值的文档?

通过使用求反运算符,可以找到所有包含空白或 null 值的文档。这里有一个例子:

-field:* or -field:[’’ TO *]

我如何对条款应用负提升?

Solr 中的默认提升是 1.0,任何高于这个值的都是正提升。Solr 不支持负 boosting,你也不能指定一个 boost 比如music:jazz^-5.0

如果您对诸如music:rock¹⁰⁰ music:metal¹⁰⁰ music:jazz⁰.02之类的术语应用较低的提升,包含 jazz 的文档仍将排在仅包含 rock 和 metal 的文档之上,因为您仍在对 jazz 进行一些提升。解决方案是给不包含 jazz 的文档一个非常高的提升。查询应该按如下方式增强:

q=music:rock¹⁰⁰ music:metal¹⁰⁰ (*:* -music:jazz)¹⁰⁰⁰

它们是查询字符串中的特殊字符。应该如何处理它们?

特殊字符是在 Solr 中具有特殊含义的字符。例如,子句前的加号(+)表示它是强制性的。如果您的查询也包含一个+符号呢?查询解析器不能确定它是一个有效的术语。为了让解析器理解这个符号是一个有效的术语,而不是一个特殊的指令,应该对它进行转义。您可以在这些字符前使用斜线(\))来转义它们。以下是有效特殊字符的当前列表:

+ - && || ! ( ) { } [ ] ^ " ∼ * ? : \ /

如果您使用的是 Lucene 查询解析器,那么在发出请求之前,您需要对这些特殊字符进行转义。否则,Solr 可能会抛出异常。但是 DisMax 查询解析器很好地处理了这些异常,只有当您的查询包含引号或+ / -操作符时,您才需要转义。下面是一个转义特殊字符的示例:

a \+ b

摘要

本章涵盖了 Solr 最重要的方面:搜索结果。您看到了 Solr 如何处理查询、涉及的组件以及请求流。您了解了各种类型的查询及其重要性,以及查询解析器及其请求参数。您看到了如何使用 DisMax 和 e DisMax 解析器构建类似 Google 的搜索引擎,以及如何使用 Lucene 解析器获得更好的控制。您学习了扩展SearchComponent来定制搜索过程。和前几章一样,你学习了一些实际问题及其解决方案。

下一章将更详细地介绍搜索过程,并介绍搜索的其他重要方面。

七、搜索数据:第二部分

这一章是前一章的延伸。在上一章中,你学习了搜索数据。您看到了在搜索过程中扮演重要角色的各种组件、Solr 支持的各种类型的查询和解析器,以及可以用来控制搜索行为的请求参数。

本章涵盖了搜索文档的其他重要方面。除了核心功能之外,搜索引擎还需要提供其他功能,以满足实际使用案例和用户需求。此外,每个领域都有自己独特的挑战。网络搜索引擎应该将来自同一网站的结果进行分组。电子商务网站通常提供分面导航,以便用户可以根据产品的特性轻松浏览目录。Solr 为此类常见问题提供了开箱即用的解决方案。

最重要的是,文档之间的文本相似性并不总是计算相关性的最佳标准。音乐发现网站通常认为流行和流行歌曲比具有相似分数的其他歌曲更相关。Solr 提供了函数 query 来计算这些数值的分数。Solr 允许你结合所有这些因素——包括文本相似性、受欢迎程度和评分——来推断实际的相关性排名和驯服现实世界的搜索问题。

本章描述了 Solr 的搜索特性以及实际的用例和先决条件,以便您可以轻松地分析某个特性是否可以解决您的业务问题,并了解在您的项目中实现它所面临的挑战。因为范例是最好的学习方法,所以每一节都以范例结尾。

本章涵盖以下主题:

  • 局部参数
  • 结果分组
  • 统计数字
  • 分面搜索
  • 重新排序查询
  • 连接查询和块连接
  • 函数查询
  • ExternalFileField

局部参数

本地参数,也称为LocalParams,允许您在请求参数中提供额外的属性,并定制它们的行为。LocalParams 仅受某些参数支持,并且它们的效果仅限于该参数。例如,您可以更改q参数使用的QParser,而无需更改其他参数,如fq。记住,Solr 只允许每个请求参数有一个LocalParam

句法

要使用LocalParam,它应该加在参数值的开头。LocalParams 应该以{!开始,以}结束,所有的本地参数应该在它们之间指定为由空格分隔的键值对。以下是在请求参数中使用LocalParam的语法:

<query-parameter>={!<key1>=<value1> <key2>=<value2> .. <keyN>=<valueN>}<query>

指定查询解析器

要在本地参数部分使用的查询解析器的名称可以在type参数中指定。Solr 还为它提供了一个简短的表示,允许您只指定值,Solr 为它指定了一个隐式名称type。以下是语法:

{!type=<query-parser> <key1>=<value1> .. <keyN>=<valueN>}<query> // explicit

{!<query-parser> <key1>=<value1> .. <keyN>=<valueN>}<query> // implicit

在 LocalParams 部分中指定查询

除了在右括号}后指定查询,您还可以通过使用v键在LocalParam中指定查询。

<query-paramter>={!<query-parser> <key1>=<value1> .. <keyN>=<valueN> v=<query>}

使用参数解引用

使用参数解引用,可以进一步简化查询。您在同一个请求参数中指定查询,要么跟在右花括号后面,要么作为键v的值,但是参数解引用允许您从另一个参数读取查询。这种方法为客户端应用程序提供了便利,它可以只提供 LocalParams 引用的附加参数的值,并在solrconfig.xml中配置 LocalParams。参数取消引用的语法如下:

<query-paramter>={!<query-parser> <key>=<value> v=$<param>}&param=query

例子

在本节中,您将看到一些 LocalParams 及其语法的示例。

所有请求的默认操作符是OR,但是您可以使用 LocalParams 将q参数的操作符改为AND,如下所示。当您使用这个语法时,fq参数的操作符仍然保持不变(OR):

$ curl``http://localhost:8983/solr/music/select

q={!q.op=AND}singer:(bob marley)&fq=genre:(reggae beat)

假设您想要查找关键字bob marley的结果,确保两个标记必须匹配,并且使用的解析器是 Lucene。以下示例显示了解决同一问题的各种方法:

q={!type=lucene q.op=AND}bob marley // type parameter specifies the parser

q={!lucene q.op=AND}bob marley // shortened form for specifying the parser

q={!lucene q.op=AND v=’bob marley’} // v key for specifying query

q={!lucene q.op=AND v=$qq}&qq=bob marley // query dereferencing

结果分组

结果分组是一种基于公共值对结果进行分组的功能。在 Solr 中,文档可以根据字段的值或函数查询的结果进行分组。对于查询,结果分组返回前 N 个组和每个组中的前 N 个文档。此功能也称为字段折叠,因为从概念上讲,您是基于一个公共值折叠结果的。

假设您正在构建一个 web 搜索引擎,对于一个查询,所有的顶级结果都来自同一个网站。这将导致糟糕的用户体验,而且你会无缘无故地惩罚所有其他网站。字段折叠对于消除重复和将每个网站的结果折叠成几个条目非常有用。连谷歌都这么干!

电子商务网站也经常使用结果分组来处理在不同类别中找到匹配的查询——例如,shirt可以是formalcasualparty wear类型。结果分组允许您返回每个类别的最佳结果。

先决条件

以下是使用结果分组的先决条件:

  • 如果使用函数查询进行分组,则被分组的字段应该有索引,并且应该支持函数查询。
  • 该字段必须是单值的。
  • 该字段不应被标记化。

Note

到目前为止,函数查询的结果分组在分布式搜索中不起作用。

请求参数

Solr 为启用结果分组和控制其行为提供了额外的请求参数。支持的参数在表 7-1 中指定。

表 7-1。

Result Grouping Request Parameters

| 参数 | 描述 | | --- | --- | | `group` | 默认情况下,结果分组是禁用的。可以通过指定`group=true`来启用。 | | `group.field` | 指定共享公共属性的 Solr 字段,并根据该字段对结果进行分组。可以多次指定该参数。 | | `group.query` | 所有匹配查询的文档作为一个组返回。可以多次指定该参数。 | | `group.func` | 指定函数查询,根据其唯一输出对结果进行分组。可以多次指定。分布式搜索不支持此功能。 | | `start` | 指定组的初始偏移。 | | `rows` | 指定要返回的组数。默认情况下,它返回 10 个组。 | | `group.offset` | 指定每个组中文档的初始偏移量。 | | `group.limit` | 默认情况下,只返回组的顶部文档。此参数指定每组要返回的文档数。 | | `sort` | 默认情况下,组按`score desc`排序。您可以使用此参数更改组排序顺序。 | | `group.sort` | 此参数对每个组中的文档进行排序。默认情况下,这也按`score desc`排序。 | | `group.format` | 结果分组支持两种响应格式:`grouped`和`simple`。默认情况下,结果是`grouped`。`simple`格式提供了一个扁平的结果,便于解析。在此功能中,`rows`和`start`执行`group.limit`和`group.offset`的任务。 | | `group.ngroups` | 如果设置为`true`,该参数返回匹配组的数量。如果一个查询有两个匹配的组,附加信息将被添加到响应中,如下所示:`2`在分布式搜索中,只有当组中的所有文档都存在于同一个 shard 中时,该参数才会给出正确的计数。 | | `group.facet` | 该布尔参数默认为`false`。如果启用,在第一个指定组的基础上,在`facet.field`参数中指定的字段上执行分组刻面。在分布式环境中,只有当组中的所有文档都存在于同一个碎片中时,它才会给出正确的计数。 | | `group.truncate` | 该布尔参数默认为`false`。如果启用,刻面计数基于每组中的顶部文档。 | | `group.cache.percent` | 如果该值大于 0,结果分组将在第二阶段为查询启用缓存。默认值 0 禁用缓存分组。根据 Solr 官方参考指南,缓存可以提高布尔查询、通配符查询和模糊查询的性能。对于其他类型的查询,它可能会对性能产生负面影响。 | | `group.main` | 如果`true`,第一个字段分组命令的结果被用作响应中的主结果列表,使用`group.format=simple`。必须提供至少一个分组条款(应提供`group.field`或`group.query`或`group.func`参数)。如果所有这些参数都丢失,Solr 将报告错误:`` `` `Specify at least one field, function or query to group by.` `` `400` `` |

例子

本节提供了每种结果分组类型的示例:字段分组、查询分组和函数查询分组。该示例假设您有一个名为ecommerce的核心,它包含满足所有分组先决条件的各个字段。

Group the results of each brand that manufactures a shirt, using field grouping

$ curl http://localhost:8983/solr/ecommerce/select?q=product:shirt&wt=xml&group=true&group.field=brand

<lst name="grouped">

<lst name="brand">

<int name="matches">5</int>

<arr name="groups">

<lst>

<str name="groupValue">wrangler</str>

<result name="doclist" numFound="2" start="0">

<doc>

<str name="id">2</str>

<str name="sku">A111</str>

<str name="product">Blue Shirt</str>

<str name="category">Shirt</str>

<str name="brand">wrangler</str>

<str name="size">M</str>

<int name="price">1500</int>

</doc>

</result>

</lst>

<lst>

<str name="groupValue">adidas</str>

<result name="doclist" numFound="3" start="0">

<doc>

<str name="id">1</str>

<str name="sku">A110</str>

<str name="product">Blue T-Shirt</str>

<str name="category">T-shirt</str>

<str name="brand">adidas</str>

<str name="size">M</str>

<int name="price">1000</int>

</doc>

</result>

</lst>

</arr>

</lst>

</lst>

Group documents in two price ranges using a group.query for the product “shirt”

$ curl``http://localhost:8983/solr/ecommerce/select

q=product:shirt&wt=xml&group=true&group.query=price:[1 TO 1000]

&group.query=price:[1001 TO *]

<lst name="grouped">

<lst name="price:[1 TO 1000]">

<int name="matches">5</int>

<result name="doclist" numFound="2" start="0">

<doc>

<str name="id">1</str>

<str name="sku">A110</str>

<str name="product">Blue T-Shirt</str>

<str name="category">T-shirt</str>

<str name="brand">adidas</str>

<str name="size">M</str>

<int name="price">1000</int>

</doc>

</result>

</lst>

<lst name="price:[1001 TO *]">

<int name="matches">5</int>

<result name="doclist" numFound="3" start="0">

<doc>

<str name="id">2</str>

<str name="sku">A111</str>

<str name="product">Blue Shirt</str>

<str name="category">Shirt</str>

<str name="brand">wrangler</str>

<str name="size">M</str>

<int name="price">1500</int>

</doc>

</result>

</lst>

</lst>

Group documents on price range in multiples of 1,000, using a group function query

$ curl``http://localhost:8983/solr/ecommerce/select?q=product:shirt&wt=xml&group=true&group.func=ceil(div(price,1000

<lst name="grouped">

<lst name="ceil(div(price,1000))">

<int name="matches">5</int>

<arr name="groups">

<lst>

<double name="groupValue">2.0</double>

<result name="doclist" numFound="3" start="0">

<doc>

<str name="id">2</str>

<str name="sku">A111</str>

<str name="product">Blue Shirt</str>

<str name="category">Shirt</str>

<str name="brand">wrangler</str>

<str name="size">M</str>

<int name="price">1500</int>

</doc>

</result>

</lst>

<lst>

<double name="groupValue">1.0</double>

<result name="doclist" numFound="2" start="0">

<doc>

<str name="id">1</str>

<str name="sku">A110</str>

<str name="product">Blue T-Shirt</str>

<str name="category">T-shirt</str>

<str name="brand">adidas</str>

<str name="size">M</str>

<int name="price">1000</int>

</doc>

</result>

</lst>

</arr>

</lst>

</lst>

统计数字

Solr 的StatsComponent允许您生成数字、日期或字符串字段的统计数据。并非所有统计数据都支持字符串和日期字段。

请求参数

在讨论这些方法之前,让我们看一下统计组件支持的请求参数。参数在表 7-2 中指定。

表 7-2。

Statistics Request Parameters

| 参数 | 描述 | | --- | --- | | `stats` | 您可以通过将此布尔参数设置为`true`来启用`StatsComponent`。 | | `stats.field` | 指定应生成统计数据的字段名称。可以多次指定该参数,以生成多个字段的统计数据。 | | `stats.facet` | 指定将为其生成统计信息的每个唯一值的方面。该参数的一个更好的替代方法是使用带枢轴刻面的`stats.field`。在本章的后面,你将会了解到旋转面。 | | `stats.calcdistinct` | 当该布尔参数设置为`true`时,该字段中的所有不同值将与其计数一起返回。如果不同值的数量很大,此操作的成本可能会很高。默认设置为`false`。该请求参数已被弃用,建议使用`countDistinct`和`distinctValues LocalParams`。 |

支持的方法

StatsComponent支持多种统计方法。除了percentilescountDistinctdistinctValuescardinality之外的所有方法都是默认计算的。您可以通过将其值指定为LocalParam来启用该方法。如果您显式启用任何统计信息,其他默认统计信息将被禁用。percentiles方法接受数值,比如 99.9,而其他所有方法都是布尔型的。表 7-3 列出了主要统计数据。

表 7-3。

Statistical Methods and Types Supported

| 局部参数 | 描述 | 支持的类型 | | --- | --- | --- | | `min` | 查找输入集中的最小值 | 全部 | | `max` | 查找输入集中的最大值 | 全部 | | `sum` | 计算输入集中所有值的总和 | 日期/号码 | | `count` | 计算输入集中值的数量 | 全部 | | `missing` | 计算缺少值的文档数 | 全部 | | `mean` | 计算输入集中所有值的平均值 | 数据/数字 | | `stddev` | 计算输入集中值的标准差 | 数据/数字 | | `sumOfSquares` | 通过对每个值求平方然后求和来计算输出 | 数据/数字 | | `percentiles` | 基于指定的截止值计算百分位数 | 数字 | | `countDistinct` | 返回文档集中字段或函数中不同值的计数。这种计算可能很昂贵。 | 全部 | | `distinctValues` | 返回文档集中字段或函数的所有不同值。这种计算可能很昂贵。 | 全部 | | `cardinality` | 统计方法不能很好地扩展,因为它需要将所有东西都放入内存。`cardinality`方法使用 HyperLogLog,这是一种计算不同值的概率方法,它估计计数而不是返回确切的值。参见 [`http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf`](http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf) 了解超对数算法的详细信息。可以提供介于 0.0 和 1.0 之间的浮点数作为输入值,以指定算法的积极程度,其中 0.0 表示较不积极,1.0 表示较积极。高积极性提供了更准确的计数,但导致更高的内存消耗。或者,可以指定布尔值`true`来指示浮点值 0.3。 | 全部 |

局部参数

除了前面的统计,StatsComponent还支持表 7-4 中的 LocalParams。

表 7-4。

StatsComponent LocalParams

| 参数 | 描述 | | --- | --- | | `ex` | 排除过滤器的本地参数 | | `key` | 参数来更改字段的显示名称 | | `tag` | 用于标记统计数据的参数,以便它可以与透视分面一起使用 |

例子

本节给出了在 Solr 中生成各种类型的统计数据的例子。

Generate statistics on the “price” field

$ curl http://localhost:8983/solr/ecommerce/select?q=*:*&wt=xml&rows=0&indent=true&stats=true&stats.field=price

<lst name="stats">

<lst name="stats_fields">

<lst name="price">

<double name="min">1000.0</double>

<double name="max">5000.0</double>

<long name="count">6</long>

<long name="missing">0</long>

<double name="sum">11500.0</double>

<double name="sumOfSquares">3.393E7</double>

<double name="mean">1916.6666666666667</double>

<double name="stddev">1541.968438933387</double>

<lst name="facets"/>

</lst>

</lst>

</lst>

Generate facet on the field “size” and for each value of it, get the statistics. The statistics will be generated as a subsection in stats on the field “price”.

$ curl http://localhost:8983/solr/ecommerce/select?q=*:*&wt=xml&rows=0

&indent=true&stats=true&stats.field=price&stats.facet=size

<lst name="facets">

<lst name="size">

<lst name="L">

<double name="min">1000.0</double>

<double name="max">5000.0</double>

<long name="count">2</long>

<long name="missing">0</long>

<double name="sum">6000.0</double>

<double name="sumOfSquares">2.6E7</double>

<double name="mean">3000.0</double>

<double name="stddev">2828.42712474619</double>

<lst name="facets"/>

</lst>

<lst name="M">

<double name="min">1000.0</double>

<double name="max">1800.0</double>

<long name="count">4</long>

<long name="missing">0</long>

<double name="sum">5500.0</double>

<double name="sumOfSquares">7930000.0</double>

<double name="mean">1375.0</double>

<double name="stddev">350.0</double>

<lst name="facets"/>

</lst>

</lst>

</lst>

Change the display name for the price field to “mrp”

$ curl http://localhost:8983/solr/ecommerce/select?q=*:*&wt=xml&rows=0&indent=true&stats=true&stats.field={!key=mrp}price

<lst name="stats">

<lst name="stats_fields">

<lst name="mrp">

...

</lst>

</lst>

</lst>

Get the mininum value for the “price” field using min method

$ curl http://localhost:8983/solr/ecommerce/select?q=*:*&wt=xml&rows=0&indent=true&stats=true&stats.field={!min=true}price

<lst name="stats">

<lst name="stats_fields">

<lst name="price">

<double name="min">1000.0</double>

</lst>

</lst>

</lst>

刻面

Solr 的刻面特性不亚于一把瑞士军刀,这是一个可以做很多事情的特性——尤其是在 Solr 5.1 中,当组件被修改时,以及在 5.2 版本中,当更多的特性被添加时。分面是一种将搜索结果分为几个类别的功能,允许用户过滤掉不想要的结果或深入到更具体的结果。此功能提供了更好的用户体验,使用户能够轻松导航到特定的文档。

分面是电子商务网站的必备功能,尤其是那些有大量产品目录的网站。用户发出搜索请求,响应在页面的左边或右边显示类别,这就是方面。每个类别中的项目要么是可点击的,要么带有一个复选框来过滤结果。对于像蓝色正式衬衫这样的用户查询,网站可能会返回数百或数千个结果。例如,用户可以通过筛选首选品牌、最喜欢的颜色或合适的尺寸来轻松导航到自己选择的产品。图 7-1 显示了 Amazon.com 分面搜索的一个例子;小平面显示在矩形框中。

A978-1-4842-1070-3_7_Fig1_HTML.jpg

图 7-1。

Faceting in an e-commerce web site

刻面在 Solr 中很容易使用。例如,字段分面是一种基于字段数据的分面类型,它从字段中获取索引术语,并显示分面值和搜索结果。因为值是从索引中检索的,所以 Solr 总是返回该字段的唯一值。当用户选择一个分面值时,可以用它来创建一个带有附加过滤条件的新查询,比如fq=<facet-field>:’<user selected facet value>’,并获得一个新的结果,它将是原始结果的子集。类似地,如果用户取消选择一个方面值,可以使用它进行新的搜索请求,在过滤查询中不使用该子句,以扩大搜索结果。这种方法允许用户轻松浏览产品。

分面广泛用于以下情况:

  • 结果分类
  • 引导导航
  • 自动完成
  • 决策图表
  • 分析和聚合
  • 趋势识别

在 5.1 版之前,Solr 只提供了刻面的计数。但是现在它支持聚合,聚合运行一个分面函数来生成分类结果的统计数据。使用分面函数,可以检索统计信息,如平均值、总和以及唯一值。Solr 5.1 还支持新的 JSON API 进行请求,这有助于提供特定于字段的参数和嵌套命令。

先决条件

以下是在 Solr 中使用刻面的先决条件:

  • 该字段必须有索引或者应该有docValues
  • 分面也适用于多值字段。

由于分面处理索引术语,返回的分面将是索引值(索引时文本分析后发出的标记)。因此,您需要以不同的方式分析 facet 字段,以便这些术语符合您的响应需求。您可以使用copyField将数据从可搜索字段复制到 facet 字段。接下来提供一些刻面的标准实践。

标记化

如果对字段进行标记,分面将返回每个标记,这对用户来说可能没有意义。例如,一个标记化的brand字段将生成像"facet_fields":{"brand":["Giorgio",6,"Armani",8]}这样的方面,但是用户希望得到"facet_fields":{"brand":["Giorgio Armani",5]},这样一旦他选择了这个品牌,他就只能看到这个品牌的结果。因此,对于刻面,通常避免标记化。

用小写字体书写

如果你应用LowerCaseFilterFactory,结果将是乔治·阿玛尼,而不是你想要的乔治·阿玛尼。因此,刻面时通常避免使用小写。如果您的数据格式不佳,您可以进行一些清理并应用标准化来确保格式的一致性。

句法

Solr 为分面搜索提供了两个条款:传统的请求参数和 JSON API。

传统请求参数

您可以通过指定附加的请求参数来启用分面,就像您对其他特性所做的那样。这里提供了语法:

facet.<parameter>=<value>

一些请求参数可以在每个字段的基础上指定。它们遵循语法f.<fieldname>.facet.<parameter>。例如,如果刻面参数facet.count特别应用于字段brand,则请求参数将为f.brand.facet.count

JSON API(JSON API)

JSON 的嵌套结构为构造请求提供了易用性,特别是对于特定于字段的请求参数。Solr 提供了新的基于 JSON 的 API。JSON 被指定为json.facet请求参数的一部分:

json.facet={

<facet_name>:{

<facet_type>:{

<param1>:<value1>,

<param2>:<value2>,

..

<paramN>:<valueN>

}

}

}

例子

本节提供了两种分面方法的示例。

Traditional request parameter–based approach

$ curl http://localhost:8983/solr/ecommerce/select?q=*:*&facet=true

&facet.field={!key=brand}ut_brand&facet.limit=10&facet.mincount=5

New JSON API–based approach

$ curl ’``http://localhost:8983/solr/ecommerce/select?q=*:*&json.facet

brand: {

type : "term"

field : "ut_brand",

limit : 10,

mincount : 5

}

}’

刻面类型

本节描述了 Solr 支持的刻面类型。每种分面类型都支持一组请求参数,其中一些是通用的,适用于所有分面类型,还有一些是特定于分面类型的。JSON API 基于键type识别刻面类型,键的值对应于刻面的类型。

一般参数

本节指定了适用于所有类型的分面请求的分面参数。

方面

刻面是在SearchHandler中注册的默认组件之一,但其执行被禁用。可以通过提供参数facet=true来启用。除了这个参数之外,还应该提供另一个支持参数,它包含有关刻面类型或如何刻面的信息。

现场刻面

字段分面也称为术语分面,允许您在字段中的索引值上构建分面。您指定要刻面的字段名称,Solr 返回其中所有唯一的术语。请记住,您可能希望将构建方面的字段保持为未标记的。

特定参数

以下是特定于字段分面的请求参数。

facet.field

这指定了刻面的字段。可以多次指定它,以便在多个字段上生成方面。

facet .前缀

这将返回与指定前缀匹配的方面,对于构建自动建议等功能非常有用。该参数可以在每个字段的基础上应用。在第九章中,你将学习使用 facets 来构建一个自动建议功能。

facet.contains

这将返回包含指定文本的方面。该参数可以在每个字段的基础上应用。

facet.contains.ignoreCase

将该布尔参数设置为true以在执行facet.contains匹配时忽略大小写。

刻面.排序

该参数支持两个值:indexcount。默认情况下,结果按index排序(按索引词的字典顺序),但是如果facet.limit参数设置为大于 0 的值,结果将按count排序。该参数也可以在每个字段的基础上指定。

facet.offset

这指定了多面结果的偏移。该参数可以在每个字段的基础上指定。该参数与facet.limit一起可用于启用分页。

方面.限制

这指定了为每个字段返回的分面结果的最大数量。负值指定无限制的结果。默认情况下,返回 100 个结果。该参数可以在每个字段的基础上指定。

facet.mincount

默认情况下,分面返回字段中所有唯一的术语。使用此参数,您可以过滤掉频率小于最小计数的术语。它允许 faceting 只返回那些最不突出的术语。默认值为 0。该参数可以在每个字段的基础上指定。

面. missing

在设置facet.missing=true时,faceting 返回那些没有方面值的文档的计数(匹配用户查询的文档,但是缺少该方面字段的值)。默认情况下,该参数设置为false。该参数可以在每个字段的基础上指定。

facet .方法

facet.method参数允许您选择刻面的算法。该参数可以在每个字段的基础上指定。以下是支持的方法:

  • enum:这个方法获取一个字段中的所有术语,并对匹配它的所有文档和匹配查询的所有文档进行交集运算。字段中的每个唯一术语都会创建一个过滤器缓存,因此当唯一术语的数量有限时,应该使用这种方法。使用这种方法时,确保filterCache的尺寸足够大。
  • fc:名字fc代表FieldCache。这个方法遍历所有匹配的文档,计算 faceted 字段中的术语。这种方法对于字段中不同术语的数量很高但文档中术语的数量很低的索引更有效。此外,它比其他方法消耗更少的内存,并且是默认方法。该方法为匹配创建字段值的未转换表示,因此第一次请求会很慢。
  • fcs:这个方法只对单值字符串字段有效,并支持对频繁变化的索引进行更快的分面,因为fc在索引变化时不进行反版本化。
facet.enum.cache.minDF

该参数仅适用于enum刻面方法,并允许您调整缓存。默认情况下,enum方法对所有术语使用filterCache。如果将此参数设置为值 N,则文档频率小于 N 的术语将不会被缓存。较高的值会向filterCache添加较少的文档,因此所需的内存会较少,但您会有一个性能上的折衷。

刻面.线程

这指定了为分面生成的最大线程数。默认情况下,它使用主请求的线程。如果该值为负,将创建最多到Integer.MAX_VALUE的线程。如果值为 0,请求将由主线程处理。

facet.overrequest.count

在分布式环境中,返回的方面计数不准确。为了提高准确性,您需要从每个碎片中请求比facet.limit指定的更多的面。超额请求基于以下公式:

facet.overrequest.count +  (facet.limit * facet.overrequest.ratio)

facet.overrequest.count的默认值是 10。

方面。过度要求。比率

正如您在前面的参数中看到的,facet.overrequest.ratio通过乘以facet.limit也有助于控制从每个碎片获取的面的数量。该参数的默认值为 1.5。

如果您没有指定这些参数中的任何一个,Solr 默认为每个字段获取 25 个方面,基于计算 10 + (10 * 1.5),其中第一个 10 是facet.overrequest.count的默认值,第二个 10 是facet.limit的默认值,1.5 是facet.overrequest.ratio的默认值。

查询分面

Query faceting 允许您为 faceting 提供 Lucene 查询。您可以多次指定此参数来生成多个面。

特定参数

以下是查询分面的请求参数。

facet.query

这指定了用于生成方面的 Lucene 查询。如果您想使用另一个查询解析器,可以使用 LocalParams 来指定。以下是一个方面查询的示例:

facet=true&facet.query=singer:"bob marley"&facet.query=genre:pop // lucene query

facet=true&facet.query={!dismax}bob marley // faceting with dismax qparser

范围刻面

假设您想要基于产品的日期或价格构建一个方面。显示所有唯一的日期或价格并不是一个好主意。相反,您应该显示年份范围,如 2001–2010,或者价格,如$ 201–300。使用字段方面建立这种关系的一种方法是在单独的字段中索引该范围。但是,要在将来更改范围,您需要重新索引所有文档。嗯,这似乎不是一个好主意。您可以使用分面查询来实现这一点,但是您需要指定多个查询。

Solr 通过距离分面为这个问题提供了一个内置的解决方案。这可以应用于支持范围查询的任何字段。

特定参数

以下是特定于距离分面的请求参数。

方面.范围

这指定了应该应用范围查询的字段。可以多次提供它来对多个字段执行范围查询。

面.范围.开始

这指定了范围的下限。低于该范围的索引值将被忽略。它可以在每个字段的基础上指定。

面.范围.结束

这指定了范围的上限。超出该范围的索引值将被忽略。它可以在每个字段的基础上指定。

刻面.范围.间隙

这指定了范围的大小。对于日期字段,范围应该符合DataMathParser语法。该参数可以在每个字段的基础上指定。

刻面.范围.硬化

假设你设置起点为 1,终点为 500,设置缺口为 200。生成的面将是 1–200 和 201–400,但是对于第三个范围,范围应该是 401–500 还是 401–600 并不明确。

布尔参数facet.range.hardend允许您指定是否应该严格遵循结尾。如果将此参数设置为true,最后的范围将是 401–500;如果您将其设置为false,最后的范围将是 401–600。该参数的默认值为false,允许基于每个字段。

面.范围.包含

此参数允许您处理下限值和上限值。以下是可用的选项:

  • lower:包含下边界。
  • upper:包含上边界。
  • edge:包含边缘边界(包含第一个范围的下边界和最后一个范围的上边界)。
  • outer:当facet.range.other参数被指定时,其前后范围将包括边界值。
  • all:应用前面的所有选项。

可以多次指定该参数来设置多个选项。默认情况下,刻面包括下边界,不包括上边界。如果您同时设置了lowerupperouterall,这些值将会重叠,因此请相应地进行设置。该参数可以在每个字段的基础上应用。

facet.range .其他

此参数允许您获得额外的计数。以下是可用的选项:

  • below:低于最低范围(facet.range.start)的所有值的刻面。
  • above:超出最高范围的所有值的刻面。
  • between:上下边界之间所有记录上的面。
  • all:应用前面的所有选项。
  • none:不要应用这些选项中的任何一个。

可以多次指定该参数来设置多个选项,但none选项会覆盖所有选项。它可以在每个字段的基础上应用。

facet.mincount

此参数指定计数,在该计数以下的范围方面应被忽略。您也可以在现场刻面中看到该参数。

例子

本节提供了一个范围分面的示例。在本例中,您将对价格字段应用范围分面,以获得价格范围为 201–400 的产品数量;401–600;601–800;801–1,000;以及 1001–1200。您设置了hardend=false,因此最高刻面是 1001–1200,而不是 1001–1100。为所有低于最小范围的值和所有高于最大范围的值创建一个方面。只包括最小计数为 5 的术语。

$ curl http://localhost:8983/solr/ecommerce/select?q=*:*

&facet.range=price&facet.range.start=201&facet.range.end=1100

&facet.range.gap=200&facet.mincount=5&facet=true

&facet.range.hardend=false&f.price.facet.range.include=lower

&f.price.facet.range.other=below&f.price.facet.range.other=above

间隔刻面

这种形式的分面是使用范围查询执行分面查询的一种替代方式,但实现方式不同。间隔分面在docValues上起作用,因此需要在您分面的字段上启用docValues,并根据内容提供不同的性能。您需要运行一些测试来确定是区间分面查询还是具有范围查询的分面查询为您提供了更好的性能。当获取同一个字段的多个范围时,区间分面提供了更好的性能,但是分面查询对于具有有效缓存的系统来说是很好的。

特定参数

以下是特定于间隔 faceting.facet.interval 的请求参数

此参数指定应应用间隔分面的字段名称。该参数可以在每个字段的基础上应用。

刻面.间隔.集

在此参数中,您可以指定构建区间分面的语法。您可以为多个时间间隔多次指定它,并且它可以应用于每个字段。以下是设置间隔的语法:

[<start-value>,<end-value>]

方括号可以用圆括号代替。方括号表示值包含在内,圆括号表示值不包含在内。左方括号可以以右括号结束,反之亦然。你选择的托槽将取决于你的情况。以下是几个例子:

  • (1,100):大于 1 小于 100
  • [1,100):大于等于 1,小于 100
  • [1,100]:大于等于 1,小于等于 100
  • (1,100]:大于 1,小于等于 100

对于无界区间,可以使用特殊字符*作为值。

例子

本节给出了间隔刻面的例子。

Example of interval faceting

$ curl http://localhost:8983/solr/ecommerce/select?q=*:*

&facet=true&facet.interval=mrp&f.mrp.facet.interval.set=[1,500]

&f.mrp.facet.interval.set=[501,1000]&f.mrp.facet.interval.set=[1001,*]

Interval faceting also supports key replacement. The same query can be written as follows:

$ curl http://localhost:8983/solr/ecommerce/select?q=*:*&facet=true

&facet.interval={!key=price}mrp&f.mrp.facet.interval.set={!key=less than 500}[1,500]&f.mrp.facet.interval.set={!key=500 to 1000}[501,1000]&f.mrp.facet.interval.set={!key=more than 1000}[1001,*]

透视刻面:决策树

枢轴刻面帮助您构建多级刻面(刻面中的刻面)。前面提到的所有方面都只提供顶级方面。现在,要在这个方面上构建另一个方面,需要向 Solr 发送另一个请求。但是支点面拯救了这一天。您可以构建一个决策树,而不需要向 Solr 发送带有额外过滤查询的额外请求。因此,枢轴刻面减少了往返次数。

特定参数

以下是特定于 pivot 刻面的请求参数。

facet.pivot

此参数指定要透视的字段的逗号分隔列表。这个参数可以被多次指定,每个参数将在响应中添加一个单独的facet_pivot部分。

facet.pivot=brand,size

facet.pivot.mincount

此参数指定下限阈值。计数小于mincount的术语将被忽略。

枢纽分面还支持以下字段分面的请求参数(详情请参考字段分面部分):

  • facet.limit
  • facet.offset
  • facet.sort
  • facet.overrequest.count
  • facet.overrequest.ratio
例子

本节提供了一个透视刻面的示例。

In a single request, build a facet on a brand, and for each brand build a facet on size

$ curl http://localhost:8983/solr/ecommerce/select?q=*:*&facet=true&facet.pivot=brand,size

<lst name="facet_pivot">

<arr name="brand,size">

<lst>

<str name="field">brand</str>

<str name="value">adidas</str>

<int name="count">3</int>

<arr name="pivot">

<lst>

<str name="field">size</str>

<str name="value">M</str>

<int name="count">2</int>

</lst>

<lst>

<str name="field">size</str>

<str name="value">L</str>

<int name="count">1</int>

</lst>

</arr>

</lst>

...

</arr>

</lst>

重新排序查询

Solr 4.9 引入了重新排序查询,它允许您提供一个附加查询来重新排序主查询返回的前 N 个结果。如果您希望运行一个代价太高而无法在所有文档上运行的查询,或者希望根据自定义要求对前 N 个结果进行重新排序,则此功能非常有用。有可能对 N 个结果之外的文档感兴趣,而重新排序的查询可能会错过它。但这是对性能的一种权衡。

可以使用附加请求参数rq中的本地参数指定重新排序属性。Solr 提供了一个查询解析器 ReRankQParserPlugin,用于解析重排序查询。这个解析器由名称rerank标识。

当您设置debug=true时,重新排序查询提供调试和自定义解释信息。您可以使用它来分析文档的重新分级分数。重排序查询也可以很好地与 Solr 的其他特性配合使用,您可以一起使用它们。

请求参数

以下是ReRankQParserPlugin支持的请求参数。

重新查询

这个强制参数指定了由ReRankQParser解析的查询,以重新排列顶部的文档。

reRankDocs

该参数指定要考虑进行重新排序的主查询中的最少顶级文档数。默认值为 200。

重新称重

此参数指定重新排序查询的分数所乘以的因子。结果分数被添加到主查询的分数中。默认值为 2.0。

例子

本节提供了一个重新排序查询的示例。

这个例子使用ReRankQParser来重新排列由主查询返回的前 200 个文档,主查询在q参数中提供。通过解引用在reRankQuery参数中提供重新排序查询,为了方便起见使用了rrq参数。该示例查询所有摇滚歌曲并对类型为reggaebeat的摇滚音乐进行重新排序:

$ curl http://localhost:8983/solr/music/select?q=genre:rock

&rq={!rerank reRankQuery=$rrq reRankDocs=200 reRankWeight=3.0}

&rrq=genre:(regge beat)

连接查询

通常,在将数据索引到 Solr 之前,您需要对其进行反规范化,但是反规范化并不总是一个选项。假设您有一个经常变化的值,对于每个变化,您需要找到该值的所有出现,并更新包含它的文档。对于这样的场景,Solr 提供了连接查询,您可以使用它来连接两个索引。

Solr 通过使用JoinQParser实现这个特性,它解析作为 LocalParam 提供的用户查询并返回JoinQuery。下面是JoinQuery的语法:

{!join fromIndex=<core-name> from=<from-field> to=<to-field>}

限制

Solr 中的连接查询不同于 SQL 连接,它有以下限制:

  • 您可以连接到from文档,但是不能将来自from文档的值与来自to文档的结果一起返回。
  • from文档的分数不能用于计算to文档的分数。
  • fromto字段应该兼容,并且应该经过类似的分析,以便文档匹配。
  • fromto索引应该存在于同一个节点上。

例子

本节给出了一个连接查询的例子。

Find a list of all titles sung by the singer Bob Marley, where the song metadata is in the music core and the singer information is in the singer core

$ curl``http://localhost:8983/solr/music/select?q

singer:"Bob Marley"&fl=title

块连接

联接查询在查询时执行联接,以匹配不同索引中的相关字段。使用块连接,您可以索引嵌套文档并使用BlockJoinQuery查询它们。块连接可以提供更好的查询时性能,因为关系作为父子关系存储在块中,并且不需要在查询时执行文档匹配。

先决条件

以下是块连接的先决条件:

  • schema.xml中定义附加字段_root_
  • 索引附加字段,用于区分父文档和子文档。
  • 在索引过程中,子文档应该嵌套在父文档中。
  • 更新嵌套文档时,需要一次重新索引整个块。您不能仅重新索引子文档或父文档。

Solr 提供了两个解析器来支持块连接:BlockJoinParentQParserBlockJoinChildQParser

BlockJoinChildQParser将查询与父文档进行匹配,并返回该块中的子文档。下面是语法:

{!child of=<field:value>}<parent-condition>

LocalParam of包含区分父文档和子文档的字段名和值。右括号后面的条件可以是针对父文档的 Lucene 查询,用于查找匹配的子文档。返回的结果集将是匹配块中的嵌套文档。

BlockJoinParentQParser根据子文档匹配查询,并返回该块的父文档。下面是语法:

{!parent which=<field:value>}<children-condition>

LocalParam which包含字段名和值,用于区分父文档和子文档。右括号后面的条件可以是针对子文档的 Lucene 查询,用于查找匹配的父文档。返回的结果集将是匹配块中的嵌套文档。

例子

本节提供了一个使用块连接子查询和块连接父查询来索引块文档和执行搜索的示例。

Index an album as a parent document and all its tracks as children documents. Add the Boolean field isParent to differentiate the parent document from the child. The following is an XML document.

<add>

<doc>

<field name="id">1</field>

<field name="album">Imagine</field>

<field name="singer">John Lennon</field>

<field name="isParent">true</field>

<doc>

<field name="id">2</field>

<field name="title">Imagine</field>

<field name="length">3.01</field>

</doc>

<doc>

<field name="id">3</field>

<field name="title">Crippled Inside</field>

<field name="length">3.47</field>

</doc>

</doc>

<doc>

<field name="id">4</field>

<field name="album">Confrontation</field>

<field name="singer">Bob Marley</field>

<field name="isParent">true</field>

<doc>

<field name="id">5</field>

<field name="title">Buffalo Soldier</field>

<field name="length">3.01</field>

</doc>

</doc>

</add>

Solr also supports indexing nested documents in native JSON format or using SolrJ. For indexing in JSON format, you need to specify key childDocuments to differentiate the child documents. A sample structure of nested JSON is provided here.

[

{

"id":"1",

...

"_childDocuments_": [

{

...

}

]

}

]

Search for an album named “imagine” by using a block-join child query

$ curl http://localhost:8983/solr/music/select?q={!child of=isParent:true}album:imagine

Search for a singer named “bob marley” by using a block-join parent query

$ curl``http://localhost:8983/solr/music/select

q={!parent which=isParent=true}singer:(“bob marley”)

函数查询

当一个搜索查询提交给 Solr 来检索文档时,它会执行一个排名公式来计算每个匹配查询词的文档的分数。得到的文档根据相关性排序。

Lucene 使用布尔模型(BM)和向量空间模型(VSM)的修改形式的组合来对文档进行评分。你在第三章中学习了这些信息检索模型的基础知识。分数主要基于术语频率(表示包含查询术语的文档更频繁地被排名更高)和逆文档频率(表示越少的术语排名越高)。第八章涵盖了 Solr 评分的更多细节。

现在,假设您已经定义了所需的可搜索字段,对它们进行了适当的提升,并创建了适当的文本分析链,Solr 将使用其评分公式将用户查询与文档术语进行匹配。这种评分算法在确定文档的相关性方面可能做得很好,但是它有一个限制:分数是在文本相似性的基础上计算的。从该分数获得的排名可能没有考虑实际重要性和用户需求的因素,尤其是在需要考虑其他因素的真实世界的情况下。以下是来自不同领域的例子,其中其他因素发挥了重要作用:

  • 音乐搜索引擎通常在决定歌曲的相关性时考虑流行度和用户评级。这个想法是,对于两个文本相似的文档,具有较高流行度和评级的文档应该被排列得较高。
  • 在新闻网站中,新闻的新鲜度至关重要,因此新近性和创建时间成为重要因素。
  • 餐馆发现应用程序为查询订购比萨饼的用户将附近的餐馆排名更高。在任何地理搜索系统中,位置和坐标是比文本相似性更重要的因素。

函数查询允许您根据外部因素(如数值或数学表达式)计算文档的分数,这可以补充现有的 Solr 分数,从而得出文档的最终分数。

函数查询使用函数来计算分数,可以是表 7-5 中列出的任何类型。

表 7-5。

Function Types

| 类型 | 描述 | 例子 | | --- | --- | --- | | 不变价值 | 分配一个常数分数。 | `_val_:2.0` | | 字符串文字 | 使用字符串值计算分数。并非所有函数都支持字符串文字。 | `literal("A literal value")` | | Solr 字段 | 可以指定 Solr 字段。该字段必须存在于`schema.xml`中,并遵循下一节提到的先决条件。 | `sqrt(popularity)` | | 功能 | Solr 允许您在一个函数中使用另一个函数。 | `sqrt(sum(x,100))` | | 参数代换 | Solr 允许您将参数替换用作函数。 | `q=_val_:sqrt($val1)&val1=100` |

DisMax、e DisMax 和标准查询解析器支持函数查询。

先决条件

如果在函数查询中使用 Solr 字段,它具有以下先决条件:

  • 该字段必须是索引的单值字段。
  • 它必须从文本分析中只发出一个术语。

使用

函数查询不限于补充文档相关性排序的分数。这些查询还可以用于对结果进行排序,或者计算响应中要返回的值。Solr 中可以通过以下方式使用函数查询:

  • FunctionQParserPlugin : Solr 提供了FunctionQParserPlugin,一个专用的QParserPlugin,用于从输入值创建函数查询。可以通过使用 LocalParams 或者通过在defType参数中指定解析器来调用它,如下例所示:q={!func}sqrt(clicks) // LocalParams q=sqrt(clicks)&defType=func // defType
  • FunctionRangeQParserPlugin:这与FunctionQParserPlugin相似,但是创建了一个函数的范围查询。 https://lucene.apache.org/solr/5_3_1/solr-core/org/apache/solr/search/FunctionRangeQParserPlugin.html 处的 Javadoc 提供了支持的参数列表。可以通过 LocalParams 在过滤查询中使用,如下所示:fq={!frange l=0 u=2.0}sum(user_rating,expert_rating)
  • q参数:可以使用_val_钩子将函数查询嵌入到常规的搜索查询中。_val_的所有规则都适用于它。q=singer:(bob marley) _val_:"sqrt(clicks)"
  • bf参数:DisMax 和扩展 DisMax 中的bf参数允许您指定由空格分隔的函数查询列表。您可以选择为查询分配一个提升。下面是一个使用bf参数的函数查询的例子。q=singer:(bob marley)&bf="sqrt(clicks)⁰.5 recip(rord(price),1,1000,1000)⁰.3"
  • boost参数:与bf类似,函数查询可以应用在 boost 参数或 boost 查询解析器上,如下图所示。q=singer:(bob marley)&boost="sqrt(clicks)⁰.5 recip(rord(price),1,1000,1000)⁰.3"
  • sort参数:Solr 允许您使用函数查询对结果进行排序。以下是根据user_ratingexpert_rating字段之和计算得出的降序结果排序示例:q=singer:(bob marley)&sort=sum(user_rating,expert_rating) desc
  • fl参数:可以在fl参数中使用函数查询来创建一个伪字段,并返回函数查询的输出作为响应。以下示例返回通过取user_ratingexpert_rating : q=singer:(bob marley)&fl=title,album, div(sum(user_rating,expert_rating),2),score的平均值计算的标题等级

功能类别

在上一节中,您看到了一些函数的例子,如sqrt()sum()rord()。Solr 支持的功能可以大致分为以下几组:

  • 数学函数:这些函数支持数学计算,如sum()div()log()sin()cos()。有关这些数学函数的详细信息,请参考java.util.Math Javadocs。
  • 日期功能:对于像新闻和博客这样的用例,文档需要根据最近的时间来增加。Solr 提供了ms()函数来返回从 UTC 1970 年 1 月 1 日午夜开始的毫秒差。在下一节中,您将看到一个提升最近文档的示例。
  • 布尔函数:如果满足某个条件,布尔函数可用于采取适当的行动。下面的示例修改了前面的FunctionQParserPlugin示例,如果字段fq={!frange l=0 u=2.0}sum(def(user_rating,5),expert_rating)中缺少值,则指定默认值user_rating为 5
  • 关联函数:Solr 提供了一组关联函数来检索关于索引术语的信息,比如文档频率和术语频率。q=idf(color,’red’)
  • 距离函数:Solr 提供了计算两点之间距离的函数,例如,使用欧几里德距离或曼哈顿距离。它还允许您通过使用诸如 Levenshtein 距离或 Jaro-Winkler 之类的公式来计算两个字符串之间的距离。以下示例使用 Jaro-Winkler 编辑距离公式计算 red 和 raid 这两个词之间的距离。strdist("red","raid",jw)
  • 地理空间搜索:Solr 支持位置数据和地理空间搜索,并提供功能查询,如geodist()。用于计算点之间距离的前述函数也广泛用于空间搜索。有关空间搜索的更多详细信息,请参考位于 https://cwiki.apache.org/confluence/display/solr/Spatial+Search 的 Solr 文档。
  • 其他函数:Solr 支持一些额外的函数,比如ord(),它返回索引字段值在该字段的术语索引列表中的序号。

Note

参考 Solr 官方文档 https://cwiki.apache.org/confluence/display/solr/Function+Queries 获取支持方法及其用途的完整列表。

例子

在我们关于函数查询的讨论中,您已经看到了几个例子。让我们再看几个例子,说明使用函数查询调整文档相关性排序的一些常见需求。

Function query to specify that the recently released titles are more relevant

$ curl``http://localhost:8983/solr/music/select

q=bob marley&boost=recip(ms(NOW,releasedate),3.16e-11,1,1)

&defType=edismax

Function query to specify that titles that have been played more than a certain number of times are more relevant

$ curl

http://localhost:8983/solr/music/select ?

q=bob marley&boost=div(log(sum(clicks,1),10))&defType=edismax

警告

函数查询是解决现实世界排名需求的一个很好的工具,但是需要谨慎使用。以下是使用它时需要注意的一些重要因素:

  • 函数查询应该是公平的,它的影响应该被优化设置。如果函数查询的输出或提升值非常高,则可能导致文本相似性差的文档排名也非常高。在最坏的情况下,无论您给出什么查询,一个具有很高功能分数的文档总是作为最相关的文档出现。
  • 对每个匹配的文档运行函数查询。因此,应避免代价高昂的计算,因为它会极大地影响性能。Solr 推荐快速随机存取的函数。
  • 在写数学表达式时,应该适当地处理默认值。例如,如果任一输入值为 0,则product(x,y)的得分为 0。

Note

如果索引中没有字段值,则在函数查询中使用 0 代替。

自定义函数查询

在上一节中,您了解了 Solr 提供的用于函数查询的函数。有时 Solr 提供的函数可能不适合您的业务需求,您可能希望将自己的命名函数插入 Solr。

Lucene 在包org.apache.lucene.queries.function.*中为函数查询定义了类。要编写您的自定义函数,您需要扩展以下两个 Lucene 类:

  • ValueSourceParser:解析用户查询生成ValueSource实例的工厂。
  • ValueSource:创建函数查询的抽象类。自定义函数将在扩展它的类中定义。

在本节中,您将学习编写一个自定义函数。

假设你在一家非常时尚的电子商务公司工作,在那里销售的大部分产品都是最流行的产品。你已经分析过,在你的公司里,每个趋势都是从小处开始,慢慢增长,达到一个峰值,然后开始下跌,慢慢脱离图表。趋势像钟形曲线一样移动。对于这一特定需求,您决定使用高斯公式开发一个函数,该函数提供一个遵循钟形曲线的分数。以下是您需要遵循的步骤:

Extend the ValueSourceParser abstract class. public class GaussianValueSourceParser extends ValueSourceParser { }   Implement the parse() abstract method and return an instance of the custom ValueSource implementation, which you will create in the next step. Also, get the ValueSource from FunctionQParser and pass it to the custom ValueSource. You will need it in your custom algorithm. You can optionally pass additional parameters, as shown in this example. @Override public ValueSource parse(FunctionQParser fp) throws SyntaxError {   ValueSource vs = fp.parseValueSource();   return new GaussianValueSource(vs, stdDeviation, mean); }   If you want to read parameters from the factory definition in solrconfig.xml, you can optionally extend the init() method. You read the standard deviation and mean values and provide them to the constructor of the custom ValueSource. public void init(NamedList namedList) {   this.stdDeviation = (Float) namedList.get(STD_DEVIATION_PARAM);   this.mean = (Float) namedList.get(MEAN_PARAM); }   Create a custom class by extending the ValueSource abstract class. Define the constructor for setting the instance variables. protected final ValueSource source; protected float stdDeviation = 1.0f; protected float mean = 1.0f; protected float variance = 1.0f; public GaussianValueSource(ValueSource source, float stdDeviation, float mean) {   this.source = source;   this.stdDeviation = stdDeviation;   this.variance = stdDeviation * stdDeviation;   this.mean = mean; }   Override the getValues() method of ValueSource. The method should return an implementation of FloatDocValues, an abstract class to support float values. In this method, also get the FunctionValues, using the getValues() method of the ValueSource instance provided by the factory. @Override public FunctionValues getValues(Map context, LeafReaderContext readerContext)       throws IOException {   final FunctionValues aVals =  source.getValues(context, readerContext);   return new FloatDocValues(this) { ..   }; }   Override the floatVal() method of the FloatDocValues class and get the value from the floatVal() method of the FunctionValues class that you instantiated in the previous step. This value is based on the function query type, such as the value of a Solr field. @Override public float floatVal(int doc) {   float val1 = aVals.floatVal(doc); .. }   Write your custom logic that acts as the value and return the float score. The Gaussian formula is shown here; you can replace it with any other formula. float score = (float) Math.pow(Math.exp(-(((val1 - mean)   * (val1 - mean)) / ((2 * variance)))), 1 /              (stdDeviation * Math.sqrt(2 * Math.PI))); return score;   Override the abstract methods toString() in the FloatDocValues implementation. @Override public String toString(int doc) {   return "Gaussian function query (" + aVals.toString(doc) + ’)’; }   Override the abstract method hashCode() in the ValueSource implementation. @Override public int hashCode() {   return source.hashCode() + "Gaussian function query".hashCode(); }   Build the program and add the path of Java binary JAR to the lib definition in solrconfig.xml. <lib dir="./lib" />   Register the custom ValueSourceParser in solrconfig.xml. <valueSourceParser name="gaussian" class="com.apress.solr.pa.chapter07 .functionquery.GaussianValueSourceParser" >   <float name="stdDeviation">1.0</float>   <float name="mean">8</float> </valueSourceParser>   Use the new named function in your search, gaussian in this case. $ curl http://localhost:8983/solr/ecommerce/select?q=blazer&defType=edismax &fl=score,product,trendcount&boost=gaussian(trendcount)  

Java 源代码

下面是本节中讨论的 Gaussian ValueSource的完整 Java 源代码。

GaussianValueSourceParser.java

package com.apress.solr.pa.chapter07 .functionquery;

import org.apache.lucene.queries.function.ValueSource;

import org.apache.solr.common.util.NamedList;

import org.apache.solr.common.util.Utils;

import org.apache.solr.search.FunctionQParser;

import org.apache.solr.search.SyntaxError;

import org.apache.solr.search.ValueSourceParser;

public class GaussianValueSourceParser extends ValueSourceParser {

private static final String STD_DEVIATION_PARAM = "stdDeviation";

private static final String MEAN_PARAM = "mean";

private float stdDeviation;

private float mean;

public void init(NamedList namedList) {

this.stdDeviation = (Float) namedList.get(STD_DEVIATION_PARAM);

this.mean = (Float) namedList.get(MEAN_PARAM);

}

@Override

public ValueSource parse(FunctionQParser fp) throws SyntaxError {

ValueSource vs = fp.parseValueSource();

return new GaussianValueSource(vs, stdDeviation, mean);

}

}

GaussianValueSource.java

package com.apress.solr.pa.chapter``7

import java.io.IOException;

import java.util.Map;

import org.apache.lucene.index.LeafReaderContext;

import org.apache.lucene.queries.function.FunctionValues;

import org.apache.lucene.queries.function.ValueSource;

import org.apache.lucene.queries.function.docvalues.FloatDocValues;

import org.apache.lucene.queries.function.valuesource.FloatFieldSource;

public class GaussianValueSource extends ValueSource {

protected final ValueSource source;

protected float stdDeviation = 1.0f;

protected float mean = 1.0f;

protected float variance = 1.0f;

public GaussianValueSource(ValueSource source, float stdDeviation, float mean) {

this.source = source;

this.stdDeviation = stdDeviation;

this.variance = stdDeviation * stdDeviation;

this.mean = mean;

}

@Override

public String description() {

return "Gaussian function query (" + source.description() + ")";

}

@Override

public boolean equals(Object arg0) {

return false;

}

@Override

public FunctionValues getValues(Map context, LeafReaderContext readerContext)

throws IOException {

final FunctionValues aVals =  source.getValues(context, readerContext);

return new FloatDocValues(this) {

@Override

public float floatVal(int doc) {

float val1 = aVals.floatVal(doc);

float score = (float) Math.pow(Math.exp(-(((val1 - mean) * (val1 - mean)) / ((2 * variance)))), 1 / (stdDeviation * Math.sqrt(2 * Math.PI)));

return score;

}

@Override

public String toString(int doc) {

return "Gaussian function query (" + aVals.toString(doc) + ’)’;

}

};

}

@Override

public int hashCode() {

return source.hashCode() + "Gaussian function query".hashCode();

}

}

引用外部文件

函数查询使用用户评级、下载计数或趋势计数等信息来计算函数得分。如果观察这些值,它们比其他字段中的值变化得更频繁。例如,像 YouTube 这样的视频分享网站需要每隔几个小时更新一次趋势计数,因为如今一个视频一天内获得一百万次点击是很常见的。

索引这种频繁变化的信息还需要更新其他字段,这些字段的变化不太频繁。为了解决这样的用例,Solr 提供了ExternalFileField,这是FieldType的一个特殊实现,允许您在外部文件中指定字段值。外部文件包含从文档中的关键字段到外部值的映射。Solr 允许您以期望的频率修改文件,而无需重新索引其他字段。

注意,外部字段是不可搜索的。它们只能用于函数查询或返回响应。

使用

以下是在搜索引擎中配置ExternalFileField的步骤:

Define the FieldType for ExternalFileField in schema.xml and specify the appropriate parameters. The following are the special parameters provided by ExternalFileField:

  • keyField:指定用于映射值的 Solr 字段。
  • defVal:如果外部文件中缺少密钥条目,则定义默认值。
  • valType:指定文件中数值的类型。该属性的有效值为floatpfloattfloat

A sample fieldType definition is provided here: <fieldType name="trendingCount" stored="false" indexed="false" keyField="trackId" defVal="0" class="solr.ExternalFileField" valType="pfloat"/>   Create the external file with the name external_<fieldname> in Solr’s index directory, which is $SOLR_HOME/<core>/data by default. The file should contain a key-value pair delimited by =. The external file for the trendingCount field mentioned previously should look like this: $ cat external_ trendingCount doc1=1.0 doc2=0.8 doc3=2 The external file name can also end with an extension, such as external_trendingCount.txt. If Solr discovers multiple files with a .* pattern, it will sort the files on name and refer the last one.   Solr allows you to define an event listener, which activates whenever a new searcher is opened or an existing searcher is reloaded. Register the ExternalFileField to the desired listener. The following is an example for registering to the listener: <listener event="newSearcher" class="org.apache.solr.schema.ExternalFileFieldReloader"/> <listener event="firstSearcher" class="org.apache.solr.schema.ExternalFileFieldReloader"/>  

完成这些步骤后,ExternalFileField就可以在任何字段定义中配置了,它可以像您见过的任何其他字段一样在函数查询中使用。

摘要

前一章关注 Solr 的核心组件:查询。在本章中,您学习了如何通过使用 LocalParams 和 reranking 查询来获得对查询的粒度控制,如何通过使用联接查询来联接索引,以及如何通过使用块联接来索引分层数据。然后您学习了其他特性,比如结果分组、分面和统计。

您看到了流行度、新近度和趋势等非文本信息对于决定文档的相关性排序是多么重要,以及如何在 Solr 中使用这些信息。您还学习了如何编写自定义命名函数,以便在函数查询中使用。

在下一章,你将学习 Solr 评分。在本章结束时,您将理解一个文档如何在结果集中的特定位置排名,以及为什么一个文档出现在另一个文档之前或之后。