Solr-教程-全-

Solr 教程(全)

原文:Apache Solr

协议:CC BY-NC-SA 4.0

一、Apache Solr:简介

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1070-3_​1) contains supplementary material, which is available to authorized users.

搜索无处不在,已经成为我们数字生活的一部分!你打开浏览器,很有可能你的主页是你首选的搜索引擎。你智能手机的搜索引擎只是轻轻一扫。打开一个电子商务网站,搜索框就出现在页面的中央或顶部。基本上,每个需要公开数据的门户都需要一个搜索引擎。这证明了一个搜索引擎对于一个企业或者它正在构建的产品是多么的重要。

当今的企业有大量的信息要公开,而且这一数量还将继续增长。浏览所有数据以找到相关信息几乎是不可能的,也是乏味的。这个问题的唯一解决方案是搜索引擎。

如果你有大量的数据,却很难在需要的时候以你需要的方式找到你想要的信息,那么这些数据几乎毫无价值,是时候呼吁开发或升级你的搜索引擎了。因为您正在阅读这一章,我假设您正在构建或计划构建一个搜索引擎,并且您理解为什么您的企业需要一个搜索引擎。所以事不宜迟,我们继续吧。

本书的目标是帮助你使用开源的 Apache Solr 开发一个搜索引擎。首先,你需要明白每个搜索引擎都是不同的,都有自己独特的挑战,需要以不同的方式解决。我们将探讨常见问题及其解决方案,并提供解决本书流程中特定问题的方法。

本章首先介绍了 Apache Solr,并提供了其特性的高级视图。它涉及到 Solr 的几个方面,以便您在开始概念证明或开发之前有一个全面的了解。

本章涵盖以下主题:

  • Solr 的重要特性
  • Solr 的重要组成部分
  • Solr 的使用超出了它的搜索引擎功能
  • Solr 与其他解决方案的比较
  • Solr 生态系统中使用的技术

概观

Solr(发音为 solar)是一个企业级的、速度惊人的、高度可伸缩的搜索平台,使用 Apache Lucene 构建。经过多年的发展,有了这样一个充满活力的社区,Solr 已经成熟到了这样一个水平,它提供了开箱即用的所有急需的特性以及定制它的条款。它是云就绪型的,具有强大的健壮性、容错性和可靠性。

Solr 是用 Java 编写的,作为独立的服务器运行。入门极其容易。您所需要做的就是运行一个带有启动/停止命令的脚本。由于 Solr 完全基于配置,所以它只要求您互相注册组件。即使没有任何 Java 知识,你也可以构建一个像样的搜索引擎,只要你不需要定制。Solr 还提供了一个易于监控的图形管理员界面,可以通过指向运行搜索引擎的端口从浏览器访问该界面。

Note

Apache Lucene 是一个流行的完全用 Java 编写的开源搜索库。它广泛用于索引大量文档和支持全文搜索。

内部 Solr

要选择一个框架,您需要从内部、特性、功能、可用性、性能和可伸缩性等方面进行评估。这一节将回答您的一些问题,包括 Solr 内部有什么,如何配置它,以及如何使用它。本节介绍的特性可能不是 Solr 如此受欢迎的原因,但是作为您对 Solr 基本理解的一部分,了解这些特性是很重要的。下一节将介绍使 Solr 流行的特性,这样您将对 Solr 的特性有全面的了解。以下几点提供了 Solr 特性的快速概述:

  • 倒排索引:Lucene 为您添加到 Solr 中的文档构建倒排索引,并在查询时搜索匹配的文档。你可以把倒排索引想象成类似于本书末尾的索引。
  • 向量空间模型:默认情况下,Lucene 使用向量空间模型(VSM)和布尔模型来确定文档与用户查询的相关性。简而言之,布尔模型认可,VSM 排名。
  • 基于配置:solrconfig.xmlschema.xml是 Solr 的两个主要配置文件。schema.xml文件主要定义模式中的字段以及这些字段的行为(在索引和查询时文本将如何被标记)。其他的几乎都进solrconfig.xml了。您也可以不使用模式,让 Solr 在索引数据时自动创建字段。这些配置可以手动编辑,也可以通过调用相应的 API 来动态修改。从 Solr 5.0 开始,您甚至可以通过 API 调用上传 JAR 文件。
  • 分析链:您的搜索查询和被索引的文档经过一系列分析器和标记器(一个标记器的输出被提供给链中的另一个标记器)。最后一个标记器的输出是被索引和匹配的术语。
  • Java: Solr 和 Lucene 都是用 Java 写的。Solr 5.0 需要 Java 7+才能运行。要定制 Solr 的任何特性,您需要扩展适当的 Java 类。
  • SolrJ: Solr 捆绑了一个 Java 客户端库,可以用来索引文档和查询结果。库也可用于其他语言,如 Perl 和 Python。

是什么让 Apache Solr 如此受欢迎

Apache Solr 是使用最广泛的搜索解决方案之一,每月有数百万次下载,数万个应用程序在生产中,数百次提交。以下是使它如此受欢迎的一些因素:

  • Lucene: Solr 在其核心使用 Lucene 搜索库,并对其进行包装以添加特性,并将其公开为可以通过 HTTP 访问的 RESTful 服务。Solr 和 Lucene 的开发在 2010 年 3 月合并,两者的代码库都驻留在 Apache Subversion (SVN)的同一个主干中;因此,你一定会在最新的 Solr 版本中获得所有最新的 Lucene 特性。
  • 高可伸缩性和容错性:您可以添加或删除 Solr 的计算能力,只需根据需要添加或删除实例的副本。SolrCloud 甚至将您的应用程序抽象为不知道数据是如何分布的,并使您免于陷入进一步的细微差别,如负载平衡和分片。索引到 Solr 的数据可以在多个实例之间复制;因此,即使一个实例关闭,数据仍然可以访问。
  • 企业就绪:Solr 在搜索需求和处理大量负载方面得到了许多领先组织的充分证明和信任。它可以部署在独立的传统分布式架构中,也可以根据组织的需求部署在云模式中,无论组织规模大小。
  • 全文搜索:由于 Solr 构建在 Lucene 之上,它提供了所有需要的匹配功能,包括令牌、短语、模糊、通配符、拼写检查和自动完成。
  • 基于 HTTP 的 RESTful XML/JSON:Solr 作为 RESTful web 服务公开,可以通过 HTTP 访问。数据可以以 XML、JSON、CSV 和二进制格式交换。
  • 灵活且可扩展:Solr 具有开箱即用的多种功能。尽管如此,如果它不符合你的需求,也不用担心;Solr 是灵活和可扩展的。您可以通过扩展 Java 类并在适当的文件中添加所创建类的配置来修改和定制组件的行为。
  • 容易配置:如果你知道你的模式,你可以预定义它;否则,您可以选择无模式,让 Solr 定义字段。此外,您可以通过 API 调用来修改您的配置。
  • 全面的管理界面:这是另一个使 Solr 相当容易使用的特性。Solr 提供了一个功能强大、用户友好的界面,可以在浏览器中通过 HTTP 访问。它提供了几乎所有需要的洞察力,使您能够查看配置文件;检查文本分析;添加/删除碎片;实时管理日志;搜索、添加、删除和更新文档。触发数据导入流程;查看线程和系统属性;还有很多。
  • 充满活力的社区:Solr 是 Apache Software Foundation (ASF)的一个项目,拥有一个由 100 多名开发人员和提交人员组成的充满活力且不断发展的社区。每个版本都添加了许多功能,并进行了改进。该项目已经成熟到这样一个水平,它有一个令人难以置信的多才多艺的功能集。

主要构件

Solr 和 Lucene 的类是有组织和分层的。每个都被设计成执行特定的工作。您所需要做的就是在配置文件中对它们进行适当的配置,这样它们就可以根据需要相互注册。Solr 还允许您编写自定义组件并将其插入。概括地说,以下是 Solr 的一些主要构建块和重要组件:

  • 请求处理程序:所有对 Solr 的请求都由实现SolrRequestHandler的一个类来处理。您将处理程序配置为映射到一个特定的 URI 端点,向该端点发出的请求开始由它提供服务。
  • 搜索组件:搜索组件定义了实现搜索处理程序提供的特性的逻辑。这些组件应该在一个SearchHandler中注册,这是一个服务于用户查询的请求处理器。例如,查询、拼写检查、分面和点击突出显示等功能作为组件实现,并注册到 SearchHandler。多个组件可以注册到一个搜索处理程序。
  • 查询解析器:它将用户查询翻译成 Lucene 能够理解的指令。它们通常注册在SearchComponent中,这个组件定义了执行搜索的逻辑。
  • 相似性:这个类决定 Lucene 如何对术语进行加权和对文档进行评分。如果你想改变评分行为,这就是要扩展的类。
  • 响应编写器:这决定了应该如何格式化对用户查询的响应。对于每种响应类型,比如 XML、JSON 或 Velocity,都有一个单独的响应编写器。
  • 分析器/标记器:Lucene 理解的最小数据单元是一个标记。AnalyzertokenizerTokenFilter的实现决定如何将文本分解成记号。
  • 更新请求处理器:在索引文档时,您可以调用一组UpdateRequestProcessor作为链的一部分,对数据执行定制操作。

历史

2004 年,Solr 作为一个内部项目由 CNET 的 Yonik Seeley 创建。2006 年初,CNET 将这个项目捐赠给了 Apache 软件基金会。经过一段时间的孵化,Solr 被释放。随着时间的推移,分布式搜索、点击突出显示、增强的搜索和索引功能、分面、拼写检查、云功能和无数其他功能已经添加进来,使其用户、贡献者和提交者社区成为当今最有活力的社区之一。以下说明了 Solr 是如何成熟的:

  • Solr 1.x:这是该产品的企业级版本。Solr 1.3 于 2007 年 1 月发布。Solr 1.4 于 2009 年 11 月发布,增强了索引、搜索、复制、富文档索引和更好的数据库集成。
  • Solr 3 . x:2011 年 3 月,Solr 和 Lucene 项目的开发合并,所以没有 Solr 的 2.x 版本。Solr 的下一个版本直接标记为 3.1,以匹配 Lucene 版本。Solr 3.x 专注于空间搜索等新特性,以及处理管道的 UIMA、UI 的 Velocity 等集成。
  • Solr 4.x: Solr 4.0 主要是关于分布式搜索和 SolrCloud,使 Solr 更加可靠、容错和可伸缩。
  • Solr 5.x: Solr 5.0 于 2015 年 2 月发布,重点是易用性和硬化。最近的版本集中在安全性、分析和扩展 API 上。有关详细信息,请参考下一节。

Solr 5.x 的新特性

Solr 4.x 引入了 SolrCloud,这是一种面向分布式搜索和可伸缩性的新方法和设计。5.x 版将 Solr 作为一个独立的服务器,有以下主要变化:

  • 独立服务器:如果你是 Apache Solr 的现有用户,是时候从新的角度来看待它了。它不再以 WAR (web archive)文件的形式分发,您应该将它部署在您最喜欢的 servlet 容器中,而是作为一个独立的服务器,您应该使用它来解决您的业务问题,而不必担心部署细节。这样做是为了更好地利用容器的网络堆栈功能。
  • 易用性:Solr 5.0 更容易上手和使用。启动、停止和将 Solr 公开为服务就像运行一个命令一样简单。它还提供了更好的自我发现,更有组织性,甚至更可配置。通过使用 AngularJS 对管理 UI 进行了重构,以获得更好的用户体验。
  • 分布式 IDF:反向文档频率,Solr 评分中最重要的因素之一,现在可以全局计算。
  • 新的 API:引入了新的 API,并且扩展了现有的 API。JSON 请求 API 允许搜索请求有一个 JSON 主体。
  • 安全性:Solr 5.2.0 引入了一个可插拔的认证模块,并提供了一个基于 Kerberos 的插件。还提供了授权框架。
  • 分析:facets 模块已经扩展到支持分析和聚合。
  • Clusterstate 拆分:将clusterstate拆分为每个集合使得 SolrCloud 更具可伸缩性。集群将只知道它需要知道的东西,而不是所有东西。

超越搜索器

Solr 的发展已经超越了它的搜索引擎能力,可以作为一个数据存储来使用。如果您将 Solr 用于索引和搜索需求,并且还需要一个数据存储,那么将 Solr 作为一个替代的 NoSQL 解决方案是值得考虑的。MongoDB 等 NoSQL 解决方案支持全文搜索,因此它们可以满足您简单的搜索需求。类似地,Solr 可以用作数据存储,尽管这不是它的主要用例。Lucene 中的更新是以删除和添加的方式实现的,可能不适合需要频繁更新的系统。此外,在选择 NoSQL 数据库时,会对许多参数进行评估。不幸的是,这方面的讨论超出了本书的范围。

此外,Solr 可以用作简单需求的分析工具,比如计数、切片和分组。一个数据分析系统必须能够处理大量的数据,Solr 是一种经过验证的快速技术。因此,您可以使用 Solr 对索引数据执行一些分析。Solr 4.9 允许您通过 AnalyticsQuery API 插入自定义的分析算法。Solr 5.0 通过搜索分析组件支持开箱即用的 OLAP 操作。Solr 5.2 支持 HyperLogLog,这是一种计算不同值的概率方法。

Solr 与其他选项

本节将 Solr 与关系数据库和 Elasticsearch 进行比较。我选择将 Solr 与关系数据库进行比较,以便刚接触搜索和 NoSQL 的开发人员能够从数据库的角度理解和理解 Solr。相比之下,Elasticsearch 是另一个流行的开源搜索引擎,它的比较可以帮助你了解 Solr 与它的关系,以及你是否应该评估 Elasticsearch 以供你使用。

关系数据库

传统的 SQL 数据库被设计为查询表以找到匹配的结果,并且不具有对这些结果进行排序的能力。当你开发你的搜索引擎时,你希望你的搜索结果在一定的基础上被排序,并解释为什么一个特定的文档排在一个特定的位置或在另一个文档之上。例如,基于诸如文档中匹配的标记的最大数量、语料库中标记的稀有性、文档是最新的、或其他参数的组合等逻辑,文档可以排在结果集的顶部。以下是 Solr 和传统数据库之间的一些主要区别:

  • 相关性排序:Solr 排序基于布尔模型和向量空间模型的组合。布尔模型批准文档,并使用 VSM 对那些批准的文档进行排名。相比之下,传统数据库只找到应该构成结果集一部分的文档;他们只有审批流程,没有对文档进行排序的规定。
  • 规范化与非规范化:关系数据库中的数据应该是规范化的,并且在表之间建立一种关系。但是 Solr 中的数据是非规范化和扁平化的。Solr 确实支持连接,但是其行为和实现是不同的,并且不如表连接强大。如果数据存储在一个表中,并且要导入到 Solr 中,那么应该由索引器程序或 Solr 的 dataimport contrib 模块对其进行反规范化。
  • 模式与无模式:关系数据库需要在创建表时定义一个模式。如果您想要添加一个新列,则需要修改该表。但是 Solr 是灵活的。如果您知道您的模式,您可以在配置文件中定义它,或者您可以选择无模式。
  • 纵向扩展与横向扩展:Solr 在企业系统中表现出色并取得优势的一个方面是它易于横向扩展。关系数据库很难向外扩展;它们通常会扩大规模,但成本很高。在传统的 Solr 架构中,数据可以被分发和分片。SolrCloud 使向外扩展变得更加容易,并且可以在商用机器集群上运行。
  • 文本分析和导航:Solr 提供了灵活的文本分析和导航特性,这对于开发搜索引擎非常有意义。这些特性在传统数据库中是完全没有的。

这里需要理解的重要一点是,最好将 Solr 用作从主数据存储(如传统数据库)导入数据的辅助数据存储。每当您对索引过程进行更改或升级 Solr 版本(或类似的版本)时,您可能需要从头开始构建索引。拥有主数据存储简化了重新编制索引的过程。

弹性搜索

Elasticsearch 是一个流行且广泛使用的开源搜索引擎。Solr 和 Elasticsearch 都建立在 Lucene 之上,拥有活跃的社区,并且有商业背景。不同之处在于它们在 Lucene 之上构建包装器和实现特性的方式。以下是它们的一些主要区别:

  • Lucene:根据 Lucene 搜索 Solr aces Elasticsearch。因为 Solr 和 Lucene 的开发是合并的,所以 Solr 总是和 Lucene 有相同的版本。有了 Solr,你总能得到最新版本的 Lucene,而 Elasticsearch 可能会落后几个版本。在撰写本章时,Solr 5.3.1 运行 Lucene 5.3.1,而 Elasticsearch 1.6.0 运行 Lucene 4.10.4。
  • 开源:Solr 和 Elasticsearch 都是开源的。然而,Solr 是 Apache 软件基金会的一个项目,因此是真正意义上的开源;甚至你也可以是一个委托人。Elasticsearch 由它的创始公司 Elastic 提供支持,只有他们的员工才能提交。
  • 历史:Solr 于 2006 年开源,当时是唯一流行的开源搜索引擎。Elasticsearch 于 2010 年发布,尽管它比较年轻,但它已经变得非常流行和广泛使用。
  • 分布式和可伸缩性:Solr 最初关注的是与查询、分面等相关的核心搜索问题和特性。它支持传统主从结构形式的分布式搜索。Elasticsearch 发布时采用了真正分布式和可伸缩的架构,这是它的主要优势。后来 Solr 4.0 开始用 SolrCloud 支持分布式和可伸缩的架构。
  • 集群管理:Solr 使用 Apache ZooKeeper,这是一个非常流行和成熟的配置维护解决方案。反过来,Elasticsearch 有一个内置的框架,名为 Zen Discovery,更容易受到裂脑问题的影响。在裂脑场景中,一个集群指定两个主服务器,这会导致数据丢失。
  • REST 端点:两者都公开 REST 端点。Elasticsearch 纯粹是 JSON in 和 JSON out,而 Solr 支持多种格式,包括 XML、JSON、CSV 和 binary。
  • 推送查询:过滤是 Elasticsearch 的一个特性,当新文档符合您指定的条件时,它会通知应用程序。Solr 不支持推送查询。
  • 分析、监控和度量:Solr 更关注文本搜索和支持特性。但在分析和监控方面,Elasticsearch 表现出色。它提供了无数的监控选项,而 Solr 中的支持是有限的。ELS 堆栈(Elasticsearch、Logstash 和 Kibana 组合在一起)允许您实时获得可操作的见解。栈广泛用于与日志管理相关的需求。
  • 文本分析:在 Solr 中,应该使用托管 API 来预定义或配置分析器。在 Elasticsearch 中,甚至可以在查询时设置分析器。
  • 易用性:Elasticsearch 一直很容易上手和运行,但最近的 Solr 5.0 也不甘落后。如果您在无模式模式下运行 Solr,索引和搜索只是一个两步过程:下载和开始。但是 Solr 一般期望你配置两个 XML 文件,schema.xmlsolrconfig.xml。这些文件有很好的文档记录,但需要一些理解,从长远来看,这可以提供更好的可管理性。

相关技术

通过本书的课程,在为你的需求开发解决方案时,你会遇到一些相关的项目。我在整本书中尽可能详细地介绍了这些项目。如果您想更深入地了解它们,请参考它们各自的手册。这些项目如下:

  • 阿帕奇动物园管理员:这个控制着 SolrCloud 的心跳。它是一个中央服务,用于以容错和高度可用的方式维护集群配置、存储状态和同步信息。要使 SolrCloud 正常工作,至少应该有一个 ZooKeeper 实例启动并运行。
  • Apache OpenNLP:这是一个 Java 库,用于文本的自然语言处理。它支持理解和解释人类语言文本的任务。在这本书里,你将学会应用这些技术来构建一个强大的搜索引擎。
  • 阿帕奇·UIMA:如果你的数据是非结构化的,Solr UIMA 贡献模块可以用来给数据结构化。UIMA 允许我们定义一个定制的管道来分析大量的非结构化文本,并对提取的元数据进行注释,这些元数据可以添加到 Solr 字段中。
  • Carrot clustering:通过使用 Carrot2,可以将相似或语义相关的搜索结果聚集在一起,只需对 XML 配置做一些修改就可以做到这一点。
  • Apache Tika:这个工具包提供了一个框架,用于从许多不同格式的文件(如 Word、PPT 和 PDF)中解析和提取内容和元数据。Solr 提供了一个 Solr Cell 框架,它使用这个工具包来索引这些文件的内容和元数据。

摘要

本章简要介绍了 Apache Solr,概述了 Solr 的内容,Solr 如此受欢迎的原因,以及为什么应该在项目中使用它。我还简要介绍了 Solr 的新特性。随着 Solr 目前的成熟,它不仅仅局限于作为一个搜索引擎,还被广泛用作 NoSQL 数据库和分析引擎。

下一章将介绍如何设置 Solr 以及内部的工作原理。第三章是关于信息检索的概念和搜索引擎如何工作的内部原理,独立于其他章节,可以不按顺序阅读。

资源

Apache Solr 的官方网站是 http://lucene.apache.org/solr/ 。Solr 的最新版本可以从这个位置下载。随着每个二进制版本的发布,Solr 提供了特定于该版本的官方文档、变更的细节和 Javadocs。

Solr cwiki ( https://cwiki.apache.org/confluence/display/solr/Apache+Solr+Reference+Guide )和 wiki ( https://wiki.apache.org/solr/ )是在线 Solr 信息的两个主要来源。cwiki 是一个由 ASF 社区维护的合流站点,所有的提交都由 Solr 提交者执行。现有的信息是经过官方批准和核实的。如果你对任何 cwiki 页面有任何建议,请随意添加评论。提交者将根据建议做出响应或做出适当的更改。

Solr wiki 是一个易于编辑的网页集合,由社区维护,托管在 MoinMoin web 引擎上。任何有权限的人都可以编辑页面。如果你想要编辑权限,写信给 Solr 邮件列表或 IRC 频道。

如需提问、讨论问题或为社区做出贡献,请参考位于 http://lucene.apache.org/solr/resources.html 的 Solr 资源页面了解更多信息。

二、Solr 设置和管理

索引和搜索是 Solr 中执行的两个主要操作。索引是将内容添加到 Solr 索引以使其可搜索的过程。但是在索引之前,首先需要设置 Solr。版本 5.0 的主要焦点是提高易用性,这使得 Solr 的设置过程变得非常容易。这些步骤非常简单,如果您在无模式模式下运行 Solr,使 Solr 能够动态猜测字段类型并定义字段,整个设置过程可以在几分钟内完成。

本章首先介绍 Solr 基础知识,包括它的术语和目录结构。然后,您将设置 Solr 并简单地索引和搜索示例文档。对于初次使用或从 4.x 版本迁移过来的用户,本章采用了一种简单的演示方法。在阅读这些章节的过程中,您将会看到更高级的例子,并获得一些关于 Solr 特性和组件的背景知识。

此外,您将了解 Solr 的主要用户选项和管理特性。通过这些解释,您将看到 Solr 在早期版本中是如何工作的。为了易于理解和方便,本章假设一个环境只有一个 Solr 实例(或服务器)。第十章重点介绍分布式方法和 SolrCloud,它支持复制、可伸缩性和容错。

本章涵盖以下主题:

  • Solr 作为独立服务器
  • 重要术语和目录/文件结构
  • 实用程序脚本
  • Web 管理界面
  • 存储器管理
  • 实例管理
  • 常见例外

独立服务器

Solr 作为一个独立的服务器运行,预先捆绑了启动和运行实例所需的一切。在 5.0 版之前,Solr 是作为 WAR 文件分发的。要部署它,要么使用预绑定的 Jetty 实例,要么将dist/solr-<version>.war复制到 Tomcat 或另一个 servlet 容器。但是现在 Solr 抽象了部署过程,需要您将它视为一个独立的服务器,而不必担心在哪里以及如何部署。如果您使用的是以前的版本,您需要从记忆中抹去这些知识,并开始使用为管理提供的脚本。对于那些对它现在是如何部署的感到好奇的人,Solr 在内部使用 Jetty servlet 容器——但那是一个实现细节,你应该把它看作一个独立的服务器。在其他容器中部署 Solr 的灵活性已经被取消,以便更好地利用容器的网络堆栈特性。

图 2-1 用一个 Tomcat 的例子来描述 Solr 作为一个服务器以及在 4.x 和 5.x 版本中的部署有何不同。

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

图 2-1。

Solr versions 4.x and 5.x

在 Solr 5.3.0 之前的版本中,您可以嗅探到server/webapps目录中的solr.war

Note

术语“独立服务器”在这里指的是您看待 Solr 的新视角,而不是指只有一个 Solr 实例的环境。在 SolrCloud 模式下,这里提到的每个独立服务器都是一个实例(或节点)。

先决条件

Apache Solr 是用 Java 编写的,安装 Solr 5.3.1 的唯一先决条件是 Java 7 或更高版本。建议使用更新版本 u55 或更高版本。强烈建议不要使用以前的版本。如果你的机器运行的是 Java 8,那就更好了,因为它可以提高性能。继续之前,请检查您的 Java 版本,如下所示:

$ java –version

java version "1.8.0_31"

Java(TM) SE Runtime Environment (build 1.8.0_31-b13)

Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)

对于 4.8.x 之前的 Solr 版本,可以使用 Java 6。如果前面的命令没有在您的机器上找到 Java 安装,您可以从 www.oracle.com/technetwork/java/javase/downloads/index.html 下载一个副本。安装步骤参见 www.java.com/en/download/help/download_options.xml

[计] 下载

可以从 http://lucene.apache.org/solr/downloads.html 下载 Solr 的最新二进制。下载页面包含对 Apache 镜像站点的引用。单击建议的选项或任何备份站点,下载最新的二进制文件(zip 或 tar)。

下载完成后,使用适当的工具从压缩的二进制文件中提取目录,如下所示:

$ tar –xvf solr-5.3.1.tgz (*nix)

$ unzip solr-5.3.1.zip (Windows)

术语

为了避免歧义并更好地理解 Solr,了解一些术语以及 Solr 上下文中使用的文件是很重要的。在我们进一步讨论之前,本节先介绍常见术语,然后是 SolrCloud 特定术语,最后是一些重要的文件。

通用术语

以下是所有类型的 Solr 设置中使用的通用术语列表:

  • Solr 实例:这个通用术语用在应用服务器的上下文中——例如,Tomcat 实例或 Jetty 实例。这里它指的是运行在 Java 虚拟机(JVM)内部的 Solr 服务器。可以配置零个或多个内核在 Solr 实例中运行。每个 Solr 实例都需要引用一个单独的 Solr 主目录。
  • Solr 核心:您的每个索引和该索引所需的文件构成一个核心。因此,如果您的应用程序需要多个索引,您可以在一个 Solr 实例中运行多个内核(而不是多个 Solr 实例,每个实例包含一个内核)。物理上,每个内核映射到 Solr 主目录中的一个子目录。
  • Solr home:这是 Solr 几乎所有内容都参考的目录。它包含关于核心及其索引、配置和依赖性的所有信息。本书使用术语$SOLR_HOME来指代这个目录。
  • Solr shard:这个术语用在分布式环境中,在这种环境中,您在多个 Solr 实例之间划分数据。特定实例上的每个数据块称为一个碎片。碎片包含整个索引的子集。例如,假设您有 3000 万个文档,并计划将它们分成三份,每份包含 1000 万个文档。您将需要三个 Solr 实例,每个都有一个具有相同名称和模式的核心。在提供查询服务时,任何一个碎片都可以接收请求,并将其分发给其他两个碎片进行处理,获得所有结果,然后将合并后的结果返回给客户端。

太阳云术语

在 Solr 中,可以有两种类型的分布式架构:传统和 SolrCloud。从 1.3 版开始,分布式搜索的传统方法使用主从架构。在这种方法中,索引是在主服务器上创建的,它被复制到一个或多个专用于搜索的从服务器上。这种方法有几个限制,你会在第十章中了解到。

为了解决这些限制,SolrCloud 发布了 4.0 版。这种革命性的方法建立了一个 Solr 服务器集群,以提供容错和高可用性,并提供分布式索引、集中式配置、自动负载平衡和故障转移等功能。

SolrCloud 引入了与传统架构略有不同的新术语。如果您正在使用 SolrCloud,记住这些术语很重要。为了简单起见,本书使用与传统架构相关的术语,除非我们正在做一些特定于 SolrCloud 的事情。以下列表定义了与 SolrCloud 相关的关键术语:

  • 节点:Solr 的单个实例在 SolrCloud 中称为一个节点。
  • 集群:环境中的所有节点。
  • 集合:簇中的完整逻辑索引。集合中的所有碎片都使用同一套配置文件。
  • 碎片:一个集合的逻辑部分或切片。每个碎片由一个或多个索引副本组成,以实现冗余。
  • 副本:碎片的物理副本,作为 Solr 核心在节点中运行。
  • 首领:在一个碎片的所有复制品中,有一个被选为首领。SolrCloud 将所有请求转发给 shard 的领导者,后者将请求分发给副本。
  • ZooKeeper: ZooKeeper 是一个 Apache 项目,被分布式系统广泛用于集中配置和协调。SolrCloud 使用它来管理集群和选举领导者。

重要的配置文件

您可以开发一个成熟的搜索引擎,并将其投入生产,甚至不用编写一行 Java 代码。Solr 使这成为可能,它允许您只通过修改 XML 就可以配置所有的特性和模式。您可以通过注册组件并在基于 XML 的配置文件中适当地链接它们来实现一个特性。以下是一些主要的配置文件:

  • solr.xml:在启动 Solr 实例时,solr.xml是 Solr 在$SOLR_HOME目录中寻找的第一个文件。该文件主要包含与 SolrCloud 相关的信息。它的配置适用于 Solr 实例,而其他文件特定于一个核心。Solr 曾经引用这个文件来识别要加载的内核。在 4.4 版中,引入了自动核心发现,在solr.xml中创建条目的规定已被否决,从 5.0 版起不再受支持。
  • solrconfig.xml:该文件包含与请求处理、响应格式化、索引、配置其他组件、管理内存、缓存和提交相关的特定于核心的配置和定义。
  • schema.xml : C包含整个模式——字段的定义、类型(也称为fieldType)和评分策略(如果有的话)。fieldType定义了要在字段上执行的文本分析,以控制索引和搜索时的匹配行为。
  • core.properties : Solr 引用该文件进行核心发现。这是一个 Java 属性文件,包含特定于核心的配置,例如核心的名称和数据目录的路径。您还可以在该文件中将自定义属性定义为键值对,并在 Solr 配置文件中引用它来进行值替换。它可以位于任何子目录和任何深度,该目录被视为核心目录。唯一的限制是同一个目录层次不能有多个core.properties(不能在另一个核心中创建一个核心)。

目录结构

要设置和使用 Solr,您需要理解两组目录结构。一个是 Solr 发行版(从下载的 tar 或 zip 中提取的目录),另一个是 Solr 主目录,其中包含所有内核的索引和配置。

太阳能装置

Solr 5.x 发行版的目录结构不同于以前的版本。这一改变是为了让 Solr 更有条理,更易于使用。图 2-2 显示了这个新的目录结构。省略号(...)表示该目录包含其他文件或目录。

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

图 2-2。

Solr installation directory structure

以下是 Solr 发行目录中所有目录和重要文件的描述:

  • bin:包含启动、停止和管理 Solr 的实用程序脚本。该目录在 4.8 版中引入,包含适用于 Windows 和*nix 机器的脚本。以下列表描述了这些脚本:
    • 用于启动、停止和其他管理目的的脚本。接下来的章节将对此进行更详细的介绍。
    • solr.in.sh/solr.in.cmd:特定于实例的设置的环境文件。
    • 使用这个脚本,您可以强制终止在特定端口上运行的 Solr 实例。Windows 没有相应的实用程序。
    • solr.<port>.port:当您使用脚本启动 Solr 实例时,会创建这个文件。它供内部使用,在停止实例时会自动删除。
    • post:将文档索引到 Solr 的脚本。
    • 如果你想把 Solr 作为一个服务来安装,这个脚本可以让你以一种交互的方式来完成。
    • init.d:如果想在 Unix 环境下使用init进程运行 Solr,可以参考的目录。
  • 除了核心库之外,Solr 还包含了其他的库,它们是 Solr 的一部分,但是是来自其他项目的贡献,在需要的时候使用。该目录包含以下子目录,每个子目录构成一个模块:
    • analysis-extras:这包含了额外的国际化和多语言支持的分析器。
    • clustering:上一章提到了 Carrot2。该目录包含在 Solr 中使用 Carrot2 时实现集群的插件。
    • 如果 Solr 是您的二级存储,而 SQL 数据库是您的一级存储,那么您不必担心编写自己的客户端程序来从数据库中获取数据并对其进行索引。DataImportHandler是一个贡献的插件,允许您从各种来源导入数据,只需少量的 XML 更改。它不限于数据库,还可以用于从 XML 和 HTTP 数据源导入数据。
    • dataimporthandler-extras:包含使用DataImportHandler导入数据时可能需要的附加插件。
    • extraction:要从 Microsoft Word 文档或 Adobe PDFs 等富文档中提取数据,可以使用 Apache Solr 内容提取库(Solr Cell)。这个插件使用 Apache Tika 进行提取,我们在第一章的中提到过。
    • 如果你的文本不是英文的,你的项目可能需要这个模块。Apache Solr 语言标识符插件允许您识别文本的语言并适当地处理它。
    • map-reduce:这个实验模块使您能够使用 map-reduce 作业建立索引。
    • morphlines-cell:这是一个内容提取的实验模块。
    • morphlines-core:这是另一个实验性的插件,它依赖于 Kite Morphlines 框架,在加载到 Solr 时,可以快速轻松地执行海量数据的提取、转换和加载(ETL)。
    • 这个世界上并不是一切都是完美的,你的数据可能就是如此。如果您的数据不是结构化的,您可以在索引数据时使用非结构化信息管理体系结构(UIMA)项目。
    • 如果你想为你的搜索引擎设计一个用户界面,你可以在 Solr 中使用 Apache Velocity 模板引擎。
  • docs:这包含了你下载的 Solr 版本的特定文档。
  • licenses:这包含了 Solr 提供的所有库的许可信息。
  • 默认情况下,这个目录是与 Solr 实例相关的所有内容的中心位置。它包含服务器、Solr 主目录和日志。它还包含 Jetty 配置文件,可以修改这些文件来改变服务器的行为;但是,您应该避免这样做,因为 Solr 希望您忽略实现细节。对于任何定制,您都应该调整bin脚本中可用的属性。对于生产环境,建议将 Solr 主目录和日志位置配置为不同于服务器目录。以下是服务器目录中的子目录:
    • contexts : Solr 内部使用 Jetty,这个目录包含 Jetty 为 Solr 所需要的部署描述符。
    • etc:该目录包含 Jetty 配置文件。
    • lib:这包含了 Solr 实例所需的 Jetty 和其他库。
    • logs:默认情况下,Solr 日志在此目录下创建。
    • modules:Jetty 使用它来包含模块定义。
    • resources:包含log4j.properties等配置文件。
    • scripts:包含 SolrCloud 的实用脚本。
    • 这是默认的 Solr 主目录,您在“通用术语”一节中已经了解了。我们将在下一节更详细地讨论它。
    • solr-webapps : Jetty 提取这个目录中的 Solr。
    • start.jar:用于启动 Jetty 的可执行 JAR。Solr 5.3.0 已经过内部升级,可以在 Jetty 9 上运行。Solr 不再支持使用“java -jar start.jar”直接运行它,bin/solr 脚本是唯一的方法。

Note

在本书中,术语$SOLR_DIST指的是本节讨论的 Solr 发行目录。

Solr Home

Solr 主目录包含 Solr 配置文件和索引,是与 Solr 实例相关的所有内容的主目录。一个 Solr 实例可以有多个内核,它们都在这个目录中定义。Solr 服务器需要一个对主目录的引用,如果没有提供,将采用配置的默认路径。为了成功启动核心,Solr 要求配置和库以适当的方式可用。在$SOLR_HOME中创建一个lib目录或者包含相关库的核心目录也是一个很好的实践。

图 2-3 显示了一个样本 Solr 主目录,包含三个内核,分别位于core1core2/sub-dir1core3,由相应的core.properties文件检测。您还可以在$SOLR_HOME中看到一个lib目录,在所有已定义的内核之间共享依赖关系。

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

图 2-3。

Solr home directory structure

以下是 Solr 主目录中所有子目录和重要文件的描述:

  • solr.xml:这已经在“术语”一节中介绍过了。
  • zoo.cfg:包含 ZooKeeper 的配置。第十章采访动物园管理员。如果没有使用 SolrCloud,可以忽略这个文件。
  • configsets:如果您想在实例上的多个内核之间共享配置,您可以使用 Solr 4.8 中引入的配置集。即将到来的“核心管理”部分将更详细地介绍这一点。
  • 将所有的公共库保存在这个目录中,以便在所有内核之间共享。
  • 核心(core1core2/sub-dir1core3):在这个例子中,我们有三个索引,所以我们有三个核心。该目录是所有核心相关文件的根目录。
    • core.properties : Solr 引用该文件进行核心的自动发现。它可以出现在目录树中的任何级别,包含该文件的目录将是核心根目录。在图 2-3 中,core2的目录为core2/sub-dir1。因此confdata,以及核心特有的一切都应该存在于sub-dir1。该文件是一个 Java 属性文件,您可以在其中配置特定于核心的属性。
    • conf:该目录包含所有内核特有的配置文件,包括solrconfig.xmlschema.xml
    • 这个目录是为你的索引准备的。index子目录包含 Solr 索引,它是在索引文档时创建的。tlog子目录包含您的事务日志,这对确保文档写入的一致性至关重要。
    • lib:该目录包含所有特定于核心的依赖项。

动手练习

您一定渴望快速启动 Solr 实例并开始搜索结果。本节采用一种简单的方法来启动 Solr、索引文档和搜索结果。在我们在接下来的章节中讨论更详细的例子之前,对整个过程的概述将对您的实验有所帮助。在下一节中,您将更详细地研究如何启动和管理 Solr。

开始 Solr

您可以使用发行包中提供的脚本(从 4.10 版开始)来启动 Solr。

$ cd bin

$ ./solr start

Started Solr server on port 8983 (pid=2108). Happy searching!

现在,您的 Solr 实例已经启动,您可以将浏览器指向http://localhost:8983/solr。图 2-4 显示了 Solr 管理界面。

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

图 2-4。

Dashboard, the home page of the Solr admin interface

如果您的仪表板显示如图 2-4 所示,则 Solr 已经成功启动。如果出现错误,要么不显示该页面,要么在仪表板顶部高亮显示错误信息,如图 2-5 所示。如果错误出现在某个特定的内核中,如图 2-5 所示,其他内核将按预期工作。

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

图 2-5。

Core initialization exception

在图 2-4 左侧选项卡的底部,您会看到“无内核可用”这是因为我们还没有配置任何核心。如您所知,要创建一个索引,您需要定义一个核心,而核心在物理上位于SOLR_HOME目录中。所以首先你应该知道你的SOLR_HOME在哪里,或者让服务器把你的首选目录称为SOLR_HOME。在下一节中,您将在默认的SOLR_HOME目录中创建一个核心,这个目录是分布目录中的server/solr

如果您想在特定端口上运行 Solr,可以按如下方式进行:

$ ./solr start –p 8980

指定希望 Solr 运行的端口。前面的命令在端口 8980 上运行 Solr。该参数是可选的。默认情况下,Solr 运行在端口 8983 上。

同样默认情况下,脚本在后台模式下运行 Solr,输出被发送到solr-<PORT>-console.log。如果您运行 Solr 的时间很短,并且希望在控制台上打印输出,那么您可以使用–f选项来运行它:

$ ./solr start –f –p 8983 –V

-V是可选的详细模式,即使在后台模式下运行 Solr 时也可以使用。

创建一个核心

在 Solr 5.x 中创建内核最简单的方法是无模式化。在无模式模式下,字段是在索引文档时由字段猜测机制动态定义的。下面是一个快速创建无模式核心的例子,默认情况下,当您没有提供配置目录时,Solr 会创建它:

$ ./solr create -c hellosolr

Setup new core instance directory:

/home/applications/solr-5.3.1/server/solr/hellosolr

Creating new core ’hellosolr’ using command:

http://localhost:8983/solr/admin/cores?action=CREATE&name=hellosolr&instanceDir=hellosolr

{

"responseHeader":{

"status":0,

"QTime":2461},

"core":"hellosolr"}

-c <corename>是您要创建的核心的名称。

刷新您的 Solr 管理页面,您将看到“No cores available”消息被一个列出您创建的核心的下拉菜单所取代。

索引一些数据

选择 hellosolr 核心并转到 Documents 选项卡。此选项卡允许您直接从界面中索引文档。图 2-6 显示了屏幕快照。要索引文档,您需要选择一种文档类型并以该格式提供数据。让我们将 Solr 附带的样本数据编入索引,并在example/exampledocs中提供。因为我们的核心是无模式的,所以我们可以索引目录中的任何 XML 或 JSON 文件,而不用担心字段定义。使用以下步骤对 Solr 提供的money.xml文件中的文档进行索引:

From the Document Type drop-down list, select File Upload.   Browse the money.xml file.   Click the Submit Document button to index the documents in that file.

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

图 2-6。

Uploading the file for indexing

或者,从“文档类型”下拉列表中,您可以选择 XML 选项并粘贴文档进行索引。成功上传或提交后,您将看到回复状态“成功”此状态表示文档已被索引,但只有在提交更改后才能满足搜索请求。默认情况下,管理界面触发此提交,因此您可以开始搜索结果。

Note

example/exampledocs中的文件包含 Solr 指定格式的文档,不要随意上传文档。要索引您自己的数据,请创建一个遵循示例文件中约定的文档。

搜索结果

访问查询选项卡,然后单击执行查询按钮。这将显示您索引的文档。默认情况下,Solr 从索引中获取前 10 个文档。在“查询”窗格中,q 文本框包含您的查询,您需要以“字段名:查询”的格式指定该查询。这里,* *表示所有字段中的所有值。如果您想要特定查询的结果,例如在manu字段中包含短语“Bank of America”的所有文档,您可以在 q 文本框中键入 manu:“Bank of America”。wt参数指定了响应格式。图 2-7 显示了 json 格式的结果,JSON 被选为 wt 值。

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

图 2-7。

Search results

既然您已经理解了索引和搜索的简单过程,那么您可以跳过任何不需要的部分,直接跳到与您相关的部分。但是,我建议你按照顺序来。在接下来的部分中,您将深入了解设置和管理任务的细节。

Solr 脚本

Solr 脚本是在 4.10 版中引入的,从 Solr 5.3.0 开始,脚本是启动 Solr 的唯一条款。对 start.jar 的支持——用 Jetty 运行 Solr 的传统方式——已经被撤销,以支持 HTTP 和 HTTPS Jetty 模块。因此,要启动和停止 Solr,应该使用以下脚本:

bin/solr – *nix

bin/solr.cmd – Windows

Note

为了避免出现重复的代码,本书使用名称bin/solr来指代这两个脚本。这两个脚本的目的是相同的。每一种都有其特定的环境。

在本节中,您将详细了解如何使用bin/solr脚本来启动、停止和管理 Solr。这里提供的例子假设您在 Windows 机器上运行来自bin目录的脚本。

启动 Solr

启动 Solr 很容易!你已经在“动手练习”部分完成了。在这一节中,您将了解更多关于它的细节。

Solr 可以两种模式启动:单机和 SolrCloud。我们忽略了传统的分布式架构,因为它的启动机制与独立模式相同。默认情况下,Solr 以独立模式运行,为了简单起见,本章将对此进行介绍。以下命令使用默认端口 8983 上的默认设置启动 Solr。

$ solr start

您可以向 Solr 提供额外的参数来覆盖默认设置,如下例所示。

$ solr start -p 8983 -h localhost -m 4g

  • -p指定服务器端口
  • -h指定主机名
  • -m指定 JVM 的堆大小。此参数为-Xms 和-Xmx 指定了相同的值

如果您正在运行 Solr 的早期版本,您可以使用下面指定的server目录中的start.jar来启动它:

$ java -jar start.jar

了解脚本支持的命令及其提供的选项的最简单方法是利用bin/solr脚本的help命令。您将在下一节学习使用帮助菜单。

使用 Solr 帮助

bin/solr脚本冗长且有据可查。最好的方法是使用 help选项。这提供了所有可用的命令以及一些示例:

$ solr -help

Usage: solr COMMAND OPTIONS

where COMMAND is one of: start, stop, restart, healthcheck, create, create_core, create_collection, delete

下面是一个独立的服务器示例(它在后台通过端口 8984 启动 Solr):

$ solr start -p 8984

下面是一个 SolrCloud 示例(它使用 localhost:2181 在云模式下启动 Solr 以连接到 ZooKeeper,最大堆大小为 1GB,并启用了远程 Java 调试选项):

$ solr start -c -m 1g -z localhost:2181 -a "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1044"

Pass -help after any COMMAND to see command-specific usage information,

such as:    solr start -help or solr stop -help

现在,如果您知道想要使用的命令,您可以通过在命令名称后键入–help来获得关于其用法的更多信息:

$ solr stop -help

Usage: solr stop [-k key] [-p port]

-k key      Stop key; default is solrrocks

-p port     Specify the port the Solr HTTP listener is bound to

-all        Find and stop all running Solr servers on this host

停止 Solr

您可以通过运行如下脚本来停止 Solr:

$ solr stop –p 8983

这将停止在端口 8983 上运行的 Solr 实例。必须提供–p–all参数。如果指定–all,该命令将停止所有正在运行的节点。此外,只有当 Solr 已经用bin/solr脚本启动时,前面的命令才会停止 Solr。

当您使用bin/solr脚本启动 Solr 时,它会在bin目录中创建一个solr-<port>.port文件,以标识正在运行的 Solr 实例,然后stop命令会删除这个文件。

重启 Solr

您可以使用restart命令重启一个正在运行的 Solr 实例。start 选项的所有参数都与 restart 一起使用;唯一的区别是–p <port>选项是强制的。以下是重启在端口 8983 上运行的 Solr 实例的示例命令:

$ solr restart –p 8983

确定 Solr 状态

使用status命令,您可以在机器中验证 Solr 的所有运行实例。该命令提供实例运行的端口号、Solr 主目录的路径、分配的堆内存量及其消耗状态:

$ solr status

Found Solr process 8076 running on port 8983

{

"solr_home":"D:\\applications\\solr-5.3.1\\server\\solr\\",

"version":"5.3.1 1703449 - noble - 2015-09-17 01:48:15",

"startTime":"2015-11-05T18:07:32.916Z",

"uptime":"0 days, 0 hours, 57 minutes, 44 seconds",

"memory":"80.7 MB (%16.4) of 490.7 MB"}

}

配置 Solr 启动

在启动 Solr 之前,您可能希望定制一些属性,比如服务器主机/端口、堆空间分配和 Solr home。以下是这种自定义的两种常用方法:

  • solr.in环境文件中设置属性
  • 将这些值作为参数传递给solr脚本

环境文件是设置属性的推荐位置,也是持久的。在多次重新启动之间,环境会保留下来。如果通过传递参数来启动 Solr,它将覆盖或附加到环境文件中定义的属性值。

您可以通读环境文件中的注释,或者运行这个命令来查找所有的 Solr 启动选项:

$ solr start -help

管理 Web 界面

在运行 Solr 的过程中,您使用了 Solr 管理界面进行索引和搜索。Solr admin UI 是一个非常强大的工具,它为 Solr 管理员和开发人员提供了关于他们的核心、实例以及整个集群和环境的深刻见解。它为用户提供了一个丰富的界面来查看配置文件、运行查询、分析文档等等。以下是使用 Solr 管理界面的主要原因:

  • JVM 属性和线程转储:Java Properties 选项卡提供了运行 Solr 的 JVM 的属性,比如类路径和内存设置。“线程转储”选项卡允许您检查服务器线程信息,如线程状态、执行时间和堆栈跟踪。
  • 核心管理:控制台中的核心管理选项卡允许您添加、卸载、重新加载、重命名、优化和交换核心。
  • 日志管理:Logging 选项卡用一种颜色方案显示日志消息。每种颜色代表不同的日志记录级别。为了方便起见,这些消息是折叠的,可以单击以展开为详细视图。该选项卡还允许您动态更改包和类的日志记录级别。
  • Core overview:选择 Core,默认情况下会加载“overview”页面,提供重要的统计信息,包括索引中的文档数、索引的总大小、上次索引时间、复制信息以及与 core 目录相关的信息。
  • 搜索:选择一个核心,然后单击 Query 选项卡进行查询、facet、拼写检查或调用任何自定义搜索处理程序。此页面经常用于运行、调试和评估即席查询。
  • 索引:选择一个核心并单击 Documents,通过提交表单来索引 XML、JSON 或 CSV 文档,或者您可以从本地驱动器上传一个文件。如果在solrconfig.xml中配置了数据导入,您可以从数据库中导入数据。对于此任务,“数据导入”选项卡提供了一个专用页面。
  • 分析:程序员经常使用管理界面来分析字段或字段类型的行为。您可以单击分析选项卡,在相应的字段中输入要索引或查询的文本,然后单击分析值按钮。该系统提供逐步的文本分析,在索引和查询时执行,并在两个标记匹配的步骤处突出显示文本。
  • 复制:如果您在传统的主从环境中运行 Solr,您可以通过 Replication 选项卡启用、禁用或强制核心的复制。
  • 浏览文件:文件选项卡列出了<core>/conf目录中的所有文件和目录。
  • 查看组件统计数据:插件/统计数据选项卡允许您查看重要的统计数据。
  • 使用模式浏览器:此屏幕允许您选择一个字段或字段类型,并浏览其模式信息和索引标记。这个屏幕对于探索索引术语非常有用。例如,如果要为字段生成停用词列表,可以使用浏览器提供的索引词和计数来评估候选停用词。
  • 可视化段信息:底层索引可以由一组子索引或段组成。“段信息”页面将这些段可视化,并提供有关它的元数据,如索引文档数和删除的文档数。

在 Solr 5.1 中,UI 已经使用 AngularJS 进行了重构。

存储器管理

在 Solr 中,核心包含一个索引,它的配置文件如solrconfig.xmlschema.xml,以及其他相关文件。每个核心可以有不同的索引结构,并可用于不同的搜索需求。Solr 实例可以有多个内核,它提供了对所有内核的统一管理。Solr 允许你动态地创建一个内核,重命名它,卸载它,等等。在本节中,您将学习如何在不重启 Solr 服务器的情况下执行所有这些任务。

配置集

Solr 4.8 引入了创建配置集的规定,配置集是一组可共享的配置文件,可用于创建新的内核。这些在分布式环境中很有用。Solr 5.x 发行版还捆绑了一些预配置的配置集,您可以参考这些配置集来创建您的核心,而不是从头开始编写。这些命名的配置集可以在server/solr/configsets目录中找到。以下是 Solr 5.3.1 中预绑定的配置集列表:

  • basic_configs:这包含运行 Solr 所需的最低配置。
  • 如果你想无模式化,你可以使用这个配置。它被配置为支持带有字段猜测的托管模式。
  • 这是一个成熟的配置,启用了大多数可选功能。您可以使用这个配置集作为核心配置的起点。

创建配置集

配置集目录的默认主目录是SOLR_HOME。您可以按如下方式创建配置集:

Create a configset directory in SOLR_HOME: $ cd $SOLR_HOME $ mkdir configsets   The configsets directory is your configset base directory. In this directory, you can create as many subdirectories as you need. The name of each subdirectory indicates the name of that configset: $ cd configsets $ mkdir configset1 configset2 configset3   Your named configset subdirectory should contain a conf subdirectory with solrconfig.xml, schema.xml, and other configuration files.   If you want to change the name of your configset base directory, you can do it in solr.xml: <solr>   <configSetBaseDir>sharedConfigSets</configSetBaseDir> </solr>

图 2-8 显示了一个配置集目录结构。

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

图 2-8。

Config Sets directory structure Note

在即将到来的版本中,Solr 将引入 REST API 来管理配置集。这个 API 将允许您将配置集上传到一个模板位置,并使用它们来创建核心。

创建核心

假设您有一个新的搜索需求,它需要一个具有不同结构的新模式定义。在上一节中,您使用默认模式创建了一个核心。在本节中,您将学习如何使用现有的配置集创建核心。在第四章中,您将学习如何创建模式和定义字段。

如果您的 Solr 实例已经包含一个核心,并且您希望对新的核心使用相同的配置,那么您可以使用该配置创建一个配置集。如果新模式是不同的,我建议您使用与 Solr 预绑定的 configset。命名配置集sample_techproducts_config是任何模式设计的良好起点,因为它定义了大多数 Solr 特性。

以下是在 Solr 中创建新内核的方法。创建核心后,通过获取状态或使用 Solr admin UI 来验证它。

bin/solr 脚本

bin/solr脚本可以在机器上运行的任何 Solr 实例中创建一个新的内核。创建核心的命令如下:

solr create –c <corename> -d <confdir> -p <port>

corename是强制参数。Solr 自动在SOLR_HOME中创建指定的核心目录,以及核心发现所需的core.properties文件。

confdir是您要使用的命名配置集之一或 conf 目录的路径。Solr 将配置文件从这个目录复制到核心目录。如果没有指定–d,Solr 将使用data_driven_schema_configs配置集。

port标识要在其中创建核心的本地 Solr 实例。如果没有指定port,将在脚本找到的第一个运行实例中创建核心。

下面是一个示例命令,它使用sample_techproducts_configs配置集在端口 8983 上运行的 solr 实例中创建一个名为 hellosolr 的核心。

$ ./solr create –c hellosolr -d sample_techproducts_configs -p 8983

或者,您可以使用带有相同选项的create_core命令:

solr create_core –c <core-name> -d <conf-dir> -p <path>

create选项检查 Solr 在指定端口上是以独立模式还是 SolrCloud 模式运行,并基于此调用create_corecreate_collectioncreate_collection选项用于 SolrCloud 模式。你会在第十章中看到一个系列创作的例子。

当使用脚本创建核心时,您不应该手动创建相应的目录,因为脚本会处理它。

核心管理 REST API

创建核心的另一种方法是调用核心管理 API。因为它是一个 REST API,你可以从你的程序中调用它,使用curl甚至浏览器。如果您想使用 API 创建一个核心,可以按如下方式进行:

http://host:port/solr/admin/cores?action=CREATE&name=corename&instanceDir=path/to/instance&config=solrconfig.xml&schema=schema.xml&dataDir=data

corenameinstanceDir是强制参数。如果不提供其他参数,Solr 会提供默认值。

您可以使用现有配置集创建核心,如下所示:

http://host:port/solr/admin/cores?action=CREATE&name=corename&instanceDir=path/to/instance&configSet=configset1

以下是使用核心管理 API 创建 hellosolr 核心的示例:

$ curl``http://localhost:8983/solr/admin/cores?action=CREATE&name=hellosolr

管理界面

Solr admin UI 提供了一个创建核心的屏幕——以及核心管理支持的所有操作。尽管本章涵盖了核心管理 API 提供的所有管理选项,但它仅涵盖了用于创建核心的 UI。

在浏览器中打开管理界面。打开“核心管理员”选项卡,然后单击“添加核心”按钮。在显示的表单中填入适当的值,然后单击添加核心。图 2-9 显示了创建核心的屏幕快照。

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

图 2-9。

Solr admin screen for core creation

用手

您甚至可以手动创建一个核心,但是这需要 Solr 重启。以下是手动创建核心需要遵循的步骤:

Create a core directory in SOLR_HOME.   Create a core.properties file in that directory and define a property name=<corename> inside the file. corename is the name of your core.   Copy the conf directory in the newly created core directory.   Restart the Solr instance.

同样,您可以手动执行其他核心管理操作。

我介绍这种方法是为了帮助您更好地理解核心管理。您可以在试验和开发过程中采用这种方法。但是在生产中这样做会导致 Solr 实例停机,所以不推荐这样做。

核心状态

您可以通过使用核心管理 API 来获取注册的核心的状态。下面的调用给出了所有核心的状态。即使您尚未注册任何核心,也可以进行此呼叫:

http://localhost:port/solr/admin/cores?action=STATUS

如果您对特定内核的状态感兴趣,您需要用<corename>传递一个额外的core参数。

http://localhost:port/solr/admin/cores?action=STATUS&core=<corename >

bin/solr脚本没有为您提供获得核心状态的条款。通过使用这个脚本,您可以只获得 Solr 实例的状态。

卸载核心

如果不再需要核心,可以使用核心管理 API 卸载它。此处显示了卸载的终点以及所需的参数:

http://localhost:port/solr/admin/cores?action=UNLOAD&core=<corename>

只有在所有现有请求都得到服务后,才卸载核心。所有对核心的新请求都将抛出 404 错误。卸载还提供了通过分别传递参数deleteIndex=truedeleteDataDir=truedeleteInstanceDir=true来删除索引、数据和核心目录的选项。

deleteInstanceDir操作也删除了它的子目录。在这种情况下,您不需要在数据目录或索引目录上调用 delete。

Solr 脚本没有为卸载核心提供任何准备。

删除核心

如果你不再需要一个核心,可以用以下方法删除。

您可以使用bin/solr脚本:

solr delete -c <corename> -p <port>

或者,您可以使用核心管理 REST API:

http://localhost:port/solr/admin/cores?action=UNLOAD&core=<corename>&deleteInstanceDir=true

有关 API 的详细信息,请参考前面的“卸载核心”一节。

核心重命名

如果要更改活动核心的名称,可以使用核心管理 API,如下所示:

http://host:port/solr/admin/cores?action=RENAME&core=old&other=new

这会将核心从旧名称重命名为新名称。

核心互换

如果您想要交换两个核心的名称,您可以使用核心管理交换 API。例如,您可能需要在活动核心和备用核心之间切换:

http://host:port/solr/admin/cores?action=SWAP&core=core1&other=core2

核心分裂

Solr 允许您将一个索引拆分成两个或多个索引。当索引变得很大时,您可能希望这样做。这通常在 SolrCloud 中进行分片。以下是分割核心的端点和支持的参数:

http://host:port/solr/admin/cores?action=SPLIT&core=sourcecore&targetCore=core1&targetCore=core2

core参数表示您想要分割的源核心。为了写入目标内核,您需要提供targetCorepath参数。如果提供了path,索引的相等子集将被写入该路径。如果提供了targetCore,那么内核必须可用。索引块将被合并到目标核心。pathtargetCore参数都是多值的。前面的示例在内核core1core2之间拆分源内核中的索引。

索引备份

失败确实会发生,所以做备份总是让你感到安全。您可以在 Solr 中通过调用ReplicationHandler,组件将数据从主实例复制到从实例来进行备份。此处提供了用于备份的 API:

http://host:port/solr/core/replication?command=backup &name=backup-name&location=backup-location

默认情况下,Solr 会在数据目录中拍摄一个带有当前时间戳的索引快照。如果提供了 location 参数,快照将在绝对目录或 Solr 实例目录的相对目录中拍摄。快照目录以提供的名称或当前时间戳作为后缀。如果您的 Solr 版本早于 5.0,请确保在调用此 API 之前,在solrconfig.xml中配置了ReplicationHandler

您还可以配置 Solr 在commitoptimize期间自动备份索引,并可以指定要维护的备份数量:

<requestHandler name="/replication" >

<lst name="master">

<str name="replicateAfter">startup</str>

<str name="replicateAfter">optimize</str>

<str name="backupAfter">commit</str>

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

</lst>

</requestHandler>

Solr 允许您通过使用details命令来跟踪备份操作的状态:

http://host:port/solr/core/replication?command=details

索引恢复

Solr 5.2 引入了一个用于恢复备份索引的 API。该 API 将命名备份恢复到当前核心中,恢复完成后,搜索将开始显示快照数据。用于恢复备份的 API 如下:

http://host:port/solr/core/replication?command=restore&name=<backup-name >

Solr 不允许您在一个操作正在进行时执行另一个备份或恢复操作。

Solr 允许您通过使用restorestatus命令来跟踪恢复操作的状态:

http://host:port/solr/core/replication?command=restorestatus

在 Solr 5.2 之前的版本中,您需要手动恢复索引。手动过程要求您停止 Solr,重命名/删除索引目录,将快照目录重命名为索引,然后启动 Solr。

实例管理

在本节中,您将学习管理和定制应用于 Solr 实例的特性和属性。

设置 Solr 主页

你已经知道SOLR_HOME是什么,但是直到现在我们一直在谈论默认的 Solr home。从 Solr 5.0 开始,默认的主目录是server/solr,在更早的版本中是example/solr。最好让SOLR_HOME路径不同于您的服务器路径,这样升级就不会那么痛苦。如果您希望您的 Solr 服务器为您的SOLR_HOME指向一个不同的位置,您可以使用以下方法之一:

  • –s–solr.home选项启动 Solr 脚本:$ ./solr start -s /home/ solr
  • solr.in环境文件中设置SOLR_HOME属性:SOLR_HOME=/home/solr (*nix) set SOLR_HOME=D:\playground\solr (Windows)

您可以在 Solr admin UI 中通过在 JVM args 部分查找- Dsolr.solr.home来验证SOLR_HOME的路径。或者,您可以运行solr脚本来获取状态:

$ ./solr status

当使用脚本运行 Solr 时,不能使用–Dsolr.solr.home=/path/to/home设置 home 路径,因为如果 Solr 没有找到前面提到的 home 路径,它将引用默认的SOLR_HOME

内存管理

堆内存是 Solr 运行和完成所有处理所需的空间。因为 Solr 是用 Java 编写的,所以可以传递 JVM 参数–Xms–Xmx,这两个参数分别设置内存的初始大小和最大大小。默认情况下,Solr 5.3.1 将固定堆大小设置为 512MB,这对于您的初始实验来说已经足够合理了。当您计划将 Solr 投入生产时,您应该以最佳方式设置该值。建议将–Xms设置为与–Xmx相同的值,以避免一系列的堆扩展。确定最佳大小的最佳方法是通过实验,因为如果将最大堆大小设置得太低,系统可能会抛出OutOfMemoryError,如果设置得太高,垃圾收集将需要更长的时间:

SEVERE: java.lang.OutOfMemoryError: Java heap space

如果您收到前面的错误,是时候增加堆的大小了。下面是如何设置堆内存:

  • solr.in环境文件:SOLR_JAVA_MEM="-Xms1024m -Xmx1024m"中设置SOLR_JAVA_MEM属性
  • –m选项启动 Solr。使用此选项,您可以将–Xms–Xmx设置为相同的大小:solr start –m 1g

Caution

确保将最大堆设置为仅占总内存的一部分,并为操作系统留出适当的内存。

即使在设置了堆内存之后,如果您仍然在获取OutOfMemoryError,您可以进行堆转储来确定失败的原因。您需要在 solr.in 脚本中将HeapDumpOnOutOfMemoryErrorHeapDumpPath参数添加到 GC_TUNE 属性中,如下所示:

set GC_TUNE=–XX: +HeapDumpOnOutOfMemoryError ^–XX:HeapDumpPath=/var/log/dump/

每当 Solr 抛出OutOfMemoryError时,它就会转储你的堆。如果接下来出现此错误,请转到在指定路径下创建的文件并对其进行评估。

日志管理

日志对于调试和分析至关重要。生产中的日志设置将与开发或测试中的日志设置有所不同。在本节中,您将看到如何更改日志设置。

Solr 使用 Log4j 进行日志记录,因此依赖于log4j.properties文件。Log4j 配置参见 http://logging.apache.org/log4j/1.2/manual.html

日志位置

默认情况下,Solr 在发行版server/resources目录中提供了一个预配置的log4j.properties文件,它将日志消息写到server/logs/solr.log文件中。如果您的属性文件存在于不同的位置,您可以在solr.in环境文件中设置它。

LOG4J_PROPS=<dir-path>/log4j.properties

SOLR_LOGS_DIR=<log-dir>

LOG4J_PROPS=${solr.solr.home}/path/log4j.properties - relative to $SOLR_HOME

日志级别

默认记录级别设置为INFO。您可能希望根据您的需求和环境来更改级别。您可以对日志记录级别进行永久更改,或者如果您不希望更改持续下去,可以对调试进行临时更改。

如果您想要在 Solr 重启之间保持永久设置,您可以使用以下步骤:

Set the level in log4j.properties: log4j.rootLogger=INFO, file, CONSOLE   Set the threshold in solr.xml. Details are in the upcoming “Log Implementation” section. <int name="threshold">INFO</int>

如果您想临时更改日志级别,可以使用 Solr admin UI。从 Logging/Level 选项卡中,您可以更改根本身或者任何包或者子包的级别。请记住,因为这些更改是暂时的,所以它们会在内核重新加载或 Solr 重启时丢失。

日志实现

solr.xml还允许你管理你的日志。通过对solr.xml进行更改,您可以完全禁用日志记录,或者使用完全不同的日志记录实现。您可以设置大小来指定 Solr 在将事件刷新到日志文件之前应该缓冲的事件数量。您还可以更改日志记录级别。以下是一个配置示例:

<solr>

<logging>

<str name="enabled">true</str>

<str name="class">com.ClassName</str>

<watcher>

<int name="size">50</int>

<int name="threshold">INFO</int>

</watcher>

</logging>

...

</solr>

常见例外

本节介绍了在运行 Solr 和执行管理任务时出现的一些常见异常。

OutOfMemoryError—Java 堆空间

当分配给 JVM 的内存不够时,Solr 会抛出OutOfMemoryError。您需要为 Solr 分配更多的堆空间,或者优化您的设置。有关详细信息,请参考前面的“内存管理”部分。

outofmemoryerror—permgen 允许空间

如果你觉得堆空间和 PermGen 是一样的,那你就错了。您可能有足够的堆空间用于 JVM,但是用完了 PermGen(永久生成堆)。类装入器使用 PermGen 空间来存储所有的元数据,比如类的名称、属性和方法。只有当它们的类装入器准备好进行垃圾收集时,分配给它们的空间才会被回收。只有当加载了太多的类或发生内存泄漏时,才会出现此错误。第一种解决方案是通过将 PermGen 堆作为 Java 选项传递来增加其大小:

-XX:MaxPermSize=256m

如果在增加 PermGen 大小后仍然出现该异常,则需要检查自定义组件中可能存在的漏洞,或者查找配置问题。

太多打开的文件

如果您看到这个错误,您的 Solr 实例没有问题,但是您的机器限制了打开文件的数量。您可以按如下方式解决这个问题:

If you are using a *nix machine, increase the ulimit, as shown next. For a Windows machine, you can use Process Explorer. Refer to https://technet.microsoft.com/en-us/sysinternals/bb896653.aspx for more details. ulimit -n 1000000 – *nix   Set useCompoundFile to true in solrconfig.xml. Enabling this will use fewer descriptions while indexing but will lead to performance degradation: <useCompoundFile>true</useCompoundFile> - solrconfig.xml   Use a lower mergeFactor in solrconfig.xml, which will merge fewer segments and hence fewer open files: <mergeFactor>10</mergeFactor>

UnSupportedClassVersionException

如果 JVM 在加载 Solr 类时发现它的版本号不受支持,它会抛出这个异常。如果您收到这个错误,您可能正在旧版本的 JVM 上运行 Solr。

摘要

本章首先概述了 Solr 领域中使用的概念和术语。您学习了如何启动和运行 Solr 的第一个实例,然后创建核心、索引文档和搜索结果。然后,您简短地浏览了 Solr 管理界面。有了这些,我感觉新手对 Solr 会有一个基本的了解,会开始有家的感觉。然后,您探索了管理任务,如实例和核心管理,并了解了在使用 Solr 时可能遇到的常见异常。您可以将管理部分作为一个手册,在创建核心时或在实例运行之前参考。

下一章是关于信息检索,这对于解决复杂问题和在工作中表现出色是至关重要的。这一章与其他章节关系不大,可以不按顺序查阅。如果你急着学习更多关于索引和搜索文档的知识,你可以跳过下一章,在你喜欢的时候阅读。

三、信息检索

这本书的主要焦点是向你展示如何使用 Solr 开发一个搜索引擎。然而,搜索引擎不再是一个筒仓,需要与其他工具和技术集成,以满足复杂的信息需求。其他章节介绍了 Solr 的特性、它的组件、它们的用法以及相关的例子和用例,但是在您更深入地研究实现和任何更严肃的事情之前,让我们先了解一下信息检索。

理解信息检索(IR)对于预示搜索引擎开发中更大图景的整体观点是很重要的,这一点通常被忽略了。在这一章中,你将学习到信息检索系统开发中涉及的更广泛的概念、组件和阶段。这些知识将有助于将检索问题分类成小的可管理的单元,并通过迭代开发来驯服它。这一章也涵盖了一些工具和技术,这些工具和技术可以用来给搜索引擎带来智能,并使其更上一层楼。尽管本章指出了如何将这些方面与 Solr 联系起来,但是这些概念通常可以应用于任何搜索引擎。

这一章不仅能让开发者和搜索工程师受益,也能让项目经理和任何参与搜索引擎生命周期的人受益。

本章涵盖以下主题:

  • 信息检索导论
  • 数据分类和提取
  • 文本处理
  • 倒排索引
  • 信息检索模型
  • 信息检索过程
  • 结果评估

信息检索导论

信息检索是基于特定需求来表示、存储并最终获取信息的一系列活动。基于机器的信息检索是在 20 世纪 50 年代引入的,它的需求仅限于研究团体。后来在商业产品和垂直搜索引擎中实现。

信息检索是一个广泛的概念,搜索引擎是其主要应用之一。它的其他常见应用领域是推荐系统、文档分类和聚类、过滤和问答系统。要检索的信息可以以任何来源、格式和数量提供,并且可以应用于几乎任何领域,如医疗保健、临床研究、音乐、电子商务和 web 搜索。系统的使用可以局限于律师或医生等一组人,也可以是全世界都可以使用。

信息检索系统正在成为信息访问的主要形式。随着网络搜索引擎的出现,大规模的信息检索系统已经实现,而这仅仅是个开始。在当前 Android 和 iOS 智能手机及其更智能的应用程序的时代,信息需求急剧增加,随着物联网的发展,对信息的需求将会更大,甚至更加关键。有了这些,你可以想象机器需要处理的数据的种类和数量,以支持与这种需求相关的搜索。

搜索引擎

搜索引擎是最广泛使用的信息检索系统之一。像谷歌这样的网络搜索引擎是最明显的例子之一。每个搜索引擎都有自己的挑战,但其中一些挑战更多地与信息检索有关,而不是搜索引擎。对相关文档进行适当的排序和评估排序算法的有效性可以被认为是 IR 关注的问题,而性能和可伸缩性是搜索引擎关注的问题。

搜索引擎可以大致分为几类,每一类在某种程度上都有相似的信息需求。以下是更广泛的类别列表:

  • Web 搜索:这种方法在万维网上爬行,从 HTML 标签中提取内容,并对信息进行索引以使其可搜索。一些引擎还抓取图像和其他相关文件,并在索引之前处理文本以获取其他有趣的信息和链接。DuckDuckGo 是一个使用 Solr 开发的 web 搜索引擎的典型例子。
  • 垂直搜索:垂直搜索针对特定的领域、信息领域或行业,如医疗保健或金融。这些信息通常存在于主数据库或数据源中,如 Word 文档或 PDF 文件。这些类型的搜索通常是为满足特定需求而开发的。
  • 桌面搜索:这是设计来索引用户电脑中的文件,并使其可搜索。一些搜索引擎还会将文件内容和元数据一起编入索引,以使其可被搜索。麦克 OS X 的聚光灯就是一个典型的例子。
  • 其他:信息检索不再局限于文本,而是广泛用于搜索图像、音频指纹和语音识别。

任何信息检索系统的目标都是将数据转化为满足用户需求的可操作信息。在搜索引擎的情况下,信息属于为搜索请求而检索的相关文档。图 3-1 描述了将数据转化为信息的搜索系统中的组件。

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

图 3-1。

A search system

在接下来的部分中,您将了解重要的信息检索组件以及任何搜索引擎开发中通常遵循的过程。

数据及其分类

数据以不同的形式创建,以满足不同的需求——有些供人类使用,如博客和帖子,有些供机器使用,如表格或元数据中的数据。这些数据也有各种格式,如 Word 或 PDF,并保存在不同的数据源中。

根据数据的性质及其格式,系统需要执行不同的内容提取任务,这些任务可以是简单的数据库查询,也可以是复杂的过程,例如从图像中提取数据。

在学习更多关于内容提取的知识之前,您首先需要理解数据类别。要索引的数据可以分为三类:结构化、非结构化和半结构化。接下来将描述每个类别。

结构化的

结构化数据是一条符合预定义结构的信息。信息被组织成易于管理的单元,以便有效地存储、处理和检索。与非结构化数据相比,结构化数据更容易组织、存储和查询。如果关系数据库或表是信息的主要来源,那么数据通常是结构化的。结构化数据的一个简单例子是 web 表单或 Excel 表。这些值在逻辑上是分开的,每个值都可以索引到不同的字段。

结构化数据更容易接收,因为它不需要高度复杂的文本解析,检索技术和相关性规则的应用也更易于管理和有效。此外,它还具有更好的互操作性,方便其他机器使用。你周围的一切都会产生数据:你的相机、手机、GPS 设备和可穿戴设备。他们的数据越结构化,消费就越容易。

无社会组织的

反过来,非结构化数据不能被正式定义为可管理的单元,并且包含机器无法区分的自由格式文本块。这些块很难以编程方式解释,或识别相关联的上下文和概念,并且可能包含歧义和垃圾。它们可以包含基于域的各种信息(如人员、位置、日期、号码、疾病和症状)。非结构化数据的例子包括文本文件、pdf、Word 文档、书籍、日志和电子邮件。

我们周围的大多数文档都是非结构化的,因此对其进行处理和分析以找到其文本中的模式非常重要,这样机器就可以提取实体,解释语义,并过滤掉无关的数据。如果非结构化内容有关联的元数据,这可能是结构化信息的另一个有价值的来源。

值得注意的是,由于自然语言语法,即使是非结构化数据也有一些结构,以章节、段落和句子的形式存在。可以对该文本应用高级处理管道,以提取大量有趣的信息,并从中构建一些结构。看看维基百科:这是一种非结构化的数据形式,也有一些相关的结构。

非结构化文本可以通过使用人工智能等机器处理技术来结构化。您可以使用开源工具(例如,Apache UIMA)构建一个管道来注释文本流中的有用信息。Solr 通过处理器和过滤器为 UIMA 管道提供第一手支持。Apache Stanbol 是另一个允许您执行语义内容管理的项目。如果这对你不起作用,可以通过使用亚马逊机械土耳其人( www.mturk.com/mturk/welcome )等平台手工制作或众包结构。

半结构化的

半结构化数据是第三种形式,介于结构化和非结构化之间。这种数据具有某种结构,这种结构为互操作性提供了便利,但不能受模式的约束。没有规定结构的数量;数量取决于需要。XML 格式的文件是半结构化数据的典型例子。您可能需要执行一些类似于处理非结构化文本的处理,以便从中提取更有意义的信息,但是这个过程相对来说更容易。

内容提取

上下文抽取是信息流的第一步。在这个过程中,您从数据源中提取内容,并将其转换为可索引的文档。在 Solr 中,这个过程可以在 it 内部执行,也可以在索引文档的客户端应用程序中执行。你会在第五章的中看到 Solr 提供的文档索引选项的更多细节。

让我们假设正在为研究人员和学者开发一个搜索引擎。在这种情况下,大多数内容通常是非结构化的,并以 PDF、DOC 和 PPT 格式提供。这种非结构化数据的提取需要相当复杂的处理。要索引这些文档,首先需要检测内容类型,然后提取文本进行索引。解析器可用于从不同文件格式的内容中提取文本。表 3-1 提供了可用于文本处理的常见文件格式和开源工具的列表。

表 3-1。

File Formats and Open Source Tools

| 文件格式 | 开源工具 | | --- | --- | | 便携文档格式 | Apache 然后 Apache PDFBox | | 微软 Word/Excel/PowerPoint | Apache,然后 Apache OpenOffice | | 超文本标记语言 | jsoup HTMLCleaner | | 可扩展置标语言 | Apache[Xerces](http://java-source.net/open-source/xml-parsers/xerces)Java Architecture for XML Binding(JAXB)Dom4j Solr data importhandler 提供了许多其他的 SAX 和 Dom 解析器 | | 光学字符识别 | 宇宙魔方 | | 地理空间的 | 地理空间数据抽象库 | | 电子邮件 | JavaMail API | | 网络爬行 | 阿帕奇努奇 |

提取过程因数据源而异。如果数据在数据库中可用,它通常会是规范化的形式,因此应该在索引前展平。Solr 提供了定义反规范化过程的工具,或者如果您正在使用外部索引过程,您可以在该应用程序中完成。你将在第五章中通过使用 DataImportHandler Solr 模块学习如何从数据库中索引数据。

一个名为 Apache Tika 的开源项目为处理多种格式的文件提供了一个框架。它支持自动检测内容类型,并允许提取内容以及相关的元数据。Tika 并不自己完成所有的检测和提取工作;相反,它通过其接口包装了 Apache POI 和 Apache PDFBox 等库,以提供一种方便的提取方法。Tika 支持对各种文件格式的内容进行解析,例如包格式(.tar.jar.zip)、文本文档(.doc.xml.ppt.pdf.html)、图像(.jpeg.png.bmp)、音频(.mp3.wav)等等。

Tika 解析器通过隐藏文件格式的复杂性和调用单独的解析库来简化您的任务。它为客户端应用程序提取内容和元数据提供了一个简单而强大的机制。它为所有提取任务提供了一个单一的 API,使您不必为不同的提取需求学习和实现不同的 API。

Solr 提供了一个名为 Solr Content Extraction Library(Cell)的框架,它使用 Apache Tika 来暴露ExtractingRequestHandler,这是一个用于从不同格式的文件中提取内容并将其索引到各自的 Solr 字段的处理程序。第五章提供了使用 Solr Cell 提取内容的更多细节。

文本处理

如前一节所述,从数据库接收的原始文档或从二进制文档中提取的文本需要在被索引之前进行处理。处理任务可以是文本的清理、规范化、丰富或聚合。这些过程通常链接在一起以实现所需的输出,管道取决于您的处理需求,这取决于数据的性质和质量以及所需的输出。

在 Solr 中,文本处理可以分两步执行,由分析过程和更新请求处理器执行。这些步骤的目的是满足各种文本处理需求。分析过程满足单个字段级标记化和分析的需求,而更新请求处理器则满足其他文本处理需求,其作用范围是整个文档。更新请求处理器也称为预处理器或文档处理器。

不同系统之间的文本处理各不相同,在索引和搜索时也各不相同。例如,用户查询可能不需要清理或最少清理。接下来讨论各种文本处理类别。

清洗和正常化

并不是所有的数据都是重要的,一些重要的数据在被索引之前需要进行规范化和转换。常见的清理和规范化任务包括删除标点符号、删除停用词、小写、转换为最接近的 ASCII 字符和词干。这些是在任何搜索引擎中都应该执行的一些最基本的任务,并且对于匹配是至关重要的。

改进

文本充实是分析和挖掘内容的阶段,以找到充实、注释、实体提取的模式,并应用各种智能使内容更可用、更可理解和更相关。应用于文本的丰富取决于您的检索需求和您所担心的扩展深度。此外,建议在您的系统在清理和规范化方面表现合理,并检索相关的基于关键字的匹配之后,再尝试丰富。以下是可以应用于文本的一些丰富内容:

  • 实体提取:如果要索引的内容是非结构化的,或者任何字段都是全文的,那么您可能希望提取诸如人员、组织和位置之类的实体,并对内容进行注释。假设您正在索引来自临床试验的文本;您可以从文本中提取疾病、症状和解剖结构等实体,这样索引就更加结构化。然后,如果检测到用户查询是针对特定疾病或解剖结构的,则可以适当地对文档进行排序。
  • 词性标注:任何语言中的单词都可以根据语言的语法分类为预定义的词性(如名词或动词)。这些词类可用于形成短语并识别文本中的重要概念,这些概念可被提升以适当地排列文档。
  • 叙词表:叙词表(复数:叙词表)是一个受控词汇表,通常包含一个单词及其相关单词的列表。相关单词是语义元数据,例如同义词、反义词、更宽泛和更狭窄的概念、部分术语等等,并且支持的元数据列表可以在同义词词典之间变化。WordNet 是一个通用词典,也包含术语注释(定义)。这些词表由定期更新词汇表的社区维护。词汇表可以是通用的(如在 WordNet 中),也可以是特定的(如医学主题标题或 MeSH,它包含更深层次的术语列表,广泛用于医学科学领域)。你选择的词表取决于你的需要,你甚至可以选择使用多个词表。你可以在 http://wordnetweb.princeton.edu/perl/webwn 试试 WordNet 的现场演示。
  • 本体:本体是特定领域中概念及其关系的正式表示,例如更宽和更窄的概念。值得注意的是,一些叙词表也包含关系,但它们是非正式的,可以基于语言词典中的用法。此外,叙词表可以是通用的,而本体则局限于一个领域。

这只是冰山一角,也是一个活跃的研究领域。您所做的处理可以是高效检索和排列文档所需的任何事情。例如,如果文本包含评论,您可能想要进行情感分析;如果您正在索引新闻、期刊或法庭诉讼,您可能想要进行文本摘要。

文本处理不一定只在索引时应用。它还可以应用于用户查询,特别是查询扩展、概念识别和意图挖掘等场景。问答是一个典型的例子,用户查询通过一个管道,如查询类型分类。

表 3-2 列出了常见的预处理类型以及免费提供的工具和资源。

表 3-2。

Tools and Resources for Processing of Text

| 预处理 | 工具/资源 | | --- | --- | | 自然语言处理 | Apache OpenNLP 斯坦福 CoreNLP | | 使聚集 | 胡萝卜 2 阿帕奇驯象人槌 Weka | | 分类 | 阿帕奇驯象员马利特·韦卡 | | 受控词汇 | 国会图书馆主题词(LCSH)医学主题词(MeSH) | | 文本处理框架 | 阿帕奇 UIMA 门阿帕奇斯坦波尔阿帕奇 cTAKES(临床研究) | | 知识库 | DBpedia YAGO |

元数据生成

元数据是关于数据的数据,是访问文档和确定文档相关性的有用信息源。结构化元数据允许更容易的互操作性、交换和机器对数据的处理。

许多系统会自动为内容提供元数据,您无需担心如何生成元数据。网页具有以元标签形式关联的元数据,元标签包含关键字和页面内容的描述,并且被搜索引擎用于主题识别。如果您对摄影感兴趣,您可能知道您的相机会为您点按的每个图像生成 EXIF(可交换图像文件)格式的元数据,其中包括相机和镜头的详细信息、曝光信息和版权。最近的相机和移动电话也通过使用内置的 GPS 接收器以 EXIF 格式存储位置信息。在图 3-2 中,可以看到 JPEG 图像的元数据。想象一下,通过使用这些元数据,您可以根据提供的空间、相机或其他信息来调整文档的相关性。

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

图 3-2。

Metadata of a JPEG file

元数据模式标准已经由 Dublin Core 等社区定义,其中包含描述 web 和物理资源的术语。

如果元数据不是自动可用的,您可能希望手工制作它或者从内容提供商那里获得它。此外,还有半自动的方式来产生它。我们不会涉及这些,因为它们超出了本书的范围。

如果元数据被集成到包含内容的文件中,那么可以通过使用 Apache Tika 在 Solr 中使用它。您将在第五章中看到相关步骤。

倒排索引

从数据源提取的数据应该建立索引,以便快速准确地检索。如果您一直想知道为什么向 Solr 添加文档的过程通常被称为索引,那么倒排索引就是答案。Solr 内部使用 Lucene,当您添加文档时,它会创建一个倒排索引来使信息可搜索。

倒排索引是典型搜索引擎中的主要数据结构,它维护所有唯一术语的字典以及它们出现的所有文档的映射。每个术语都是一个键,它的值是一个发布列表,即该术语出现的文档列表。

Lucene 数据结构被称为倒排索引,因为遍历是向后的(或倒排的):从术语到文档,而不是像前向索引那样从文档到术语。倒排索引类似于本书末尾的索引,包含单词列表和单词出现的相应页面。在 Lucene 的上下文中,单词是术语的同义词,页面是文档的同义词。相比之下,前向索引类似于本书开头的目录。

倒排索引的方法是以术语为中心,而不是像前向索引那样以文档为中心。对于查询中的每个术语,面向文档的数据结构必须顺序扫描所有文档的术语,而倒排索引在索引中查找术语,并从发布列表中找到匹配的文档。这种方法提供了快速的全文搜索和更好的性能。

图 3-3 提供了倒排索引表示的简化视图。您可以看到,当一个文档被索引时,术语被作为关键字添加,它的文档映射在一个发布列表中。当用户启动查询时,倒排索引会提供关于所有匹配文档的信息。

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

图 3-3。

Lucene inverted index

发布列表还包含附加信息,如文档中的术语频率、索引时间提升和有效负载,这提供了便利和更快的搜索。

Note

用户查询和索引需要经过大量复杂的处理,为了简单起见,图 3-3 对这些处理进行了抽象。

检索模型

要搜索相关文档,您需要一个检索模型。检索模型是通过使用数学概念来定义检索过程的框架。它提供了确定与用户需求相关的文档的蓝图,以及一个文档为什么比另一个文档排序更高的推理。您选择的模型在很大程度上取决于您的信息检索需求。每个模型使用不同的方法来确定查询和文档之间的相似性。该模型甚至可以像返回 0 或 1 作为值的布尔模型一样简单。此外,系统的实现可以是解决实际用例的模型的变体或扩展。在第八章中,您将看到 Lucene 如何修改向量空间模型,这将在本节稍后讨论。

本节涵盖了搜索引擎通常使用的信息检索模型。

布尔模型

信息检索的布尔模型是基于集合论和布尔代数的简单模型。顾名思义,它将文档分为真或假(文档匹配或不匹配)。

让我们用 Solr 的说法来理解这一点。建立索引时,从文档中提取术语,并创建索引。因此,我们可以将文档视为一组术语。在搜索时,解析用户查询以确定术语并形成布尔表达式。用户查询可以包含运算符 AND、OR 或 NOT,它们在布尔表达式中具有特殊的含义,分别用于标识合取、联合或析取。Solr 还使用+和-符号将术语指定为“必须发生”或“不得发生”

所有满足表达式的文档都被认为是匹配的,其他的都被丢弃。以下是为指定的布尔表达式匹配和不匹配文档的示例:

Query:

(singer AND dancer) OR actor

Match:

"He started his career as a dancer but he is also a singer"

"Mr. Mark is a famous actor"

No Match:

"Who is the singer of this song?"

"He is an amateur dancer."

这种模型的挑战在于,它认为所有术语都是同等相关的,并且没有提供任何对文档进行排序的规定。因此,您需要一个更复杂的模型来根据相关性对文档进行排序。另一个挑战是将布尔查询转换成布尔表达式。

向量空间模型

布尔模型带你走了一半。对于用户查询,它确定匹配的文档,但是您可能还想知道匹配文档的相关程度。布尔模型在一个消耗成百上千个结果的系统中是可以接受的。但是如果最终用户是人类呢?在谷歌搜索时,你希望最佳匹配出现在第一页。当对文档进行排序时,您知道前几个结果是最相关的,并且您几乎不关心遍历许多结果页面后出现的结果。

向量空间模型(VSM)是一种检索模型,可用于根据用户查询对索引文档进行排序。查询响应包含文档以及分配给它的分数,分数表示与用户查询的相似程度。具有最高分数的文档被认为是最佳匹配。

向量空间模型通过高维空间中的术语权重向量来表示每个文档和查询,并基于查询和文档之间的角度来计算得分。图 3-4 描绘了一个 N 维空间,其中文档用实线表示,查询用强实线表示。VSM 使用下列步骤进行相关性排名:

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

图 3-4。

Vector space model While indexing, represent each document as a weighted vector of term frequency and inverse document frequency   While searching, represent the query as a weighted TF-IDF vector   Compute the cosine similarity score between the query vector and each document vector   Rank documents with respect to the query   Return the matching top N rows in the response

默认的 Lucene 评分算法使用布尔模型和向量空间模型的组合来查找相关文档。由布尔模型批准的文档由向量空间模型排序。因此,Lucene 只为满足布尔查询的文档计算相似度。此外,它使用余弦相似性的修改形式来计算相关性排名。第八章详细介绍了 Lucene 评分。

这个模型非常适合自由文本查询。这个模型的局限性在于它忽略了术语之间的关系,并假设它们是独立的。它认为文本是一个单词包。

概率模型

概率模型通过估计文档相对于查询的相关性概率来对文档进行排序。评估的过程是至关重要的,这也是实现彼此不同的地方。这个模型基于这样一个概念,即对于一个查询,每个文档可以被分类为相关或不相关:P(R|D)或 P(NR|D)。

语言模型

语言模型是概率模型的一个分支,它根据文档生成查询词的概率对文档进行排序。它通常使用最大似然估计来估计概率。评估过程大致分为三个步骤:

Estimate the language model for each document in the corpus and sample them   Calculate the probability of observing the query terms in the sample   Rank the documents in the order of probability

因为每个文档都是用于估计的样本,所以它可能会稀疏,因此应该应用平滑来解决这个问题。语言模型可以是一元或多元的。更常见的 Unigram 模型分割了不同术语的概率,忽略了上下文。N-gram 模型估计每一项的概率,考虑先前上下文的 N-1 项。

信息检索过程

在前面的章节中,您了解了信息检索中的关键概念。现在,这个故事好的一面是 Solr 使您不必担心任何与倒排索引以及检索模型的实现细节有关的事情。Lucene 提供的默认模型非常适合全文搜索和大多数检索需求。如果您想集成任何其他检索模型,您可以扩展现有的 Java 类。

信息检索是一门实验性很强的学科,建议遵循检索过程的迭代开发和改进。图 3-5 描述了信息检索过程的各个阶段,这些阶段最终会导致更好的搜索引擎的开发。在实践中,多个阶段可以同时执行,或者在同一迭代中重复多次。Solr 的分层架构以及可配置和可插拔的组件提供了所需的便利性和灵活性。

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

图 3-5。

Stages in information retrieval process

计划

规划是一项极其重要的任务,尤其是在信息检索领域,这是一个耗时的过程。在开始设计模式和定义搜索特性之前,需要考虑许多因素和策略。你可能会觉得有些因素是不相关的,所以可能会忽略它们,但这可能会极大地影响用户体验和转化率。以下是您在开始开发之前应该考虑的一些因素。

了解垂直

如果你正在开发一个垂直搜索引擎,你的首要任务是理解问题陈述,然后了解领域。大多数领域都有自己独特的功能需求,并带来自己的挑战。例如,在地理搜索中,位置和邻近性对于计算文档的相关性至关重要,而在生物医学领域,跨多个资源的信息链接变得非常重要。

了解最终用户

最终用户是最终使用搜索引擎的人,了解他的真实需求很重要。除了检索相关文档,易用性、便利性和导航能力也应该是优先考虑的。用户通常遵循基于域或地区的模式。例如,用户可能对最相关的结果感兴趣,例如当你在 Google 中搜索时,而其他人可能需要所有匹配的结果,例如法律从业者对所有匹配的案件感兴趣。在音乐发现网站中,用户期望相关文档出现在结果集中的顶部;相比之下,在服装电子商务网站中,用户通常浏览数百个匹配的文档。理解系统的核心需求,并在初始迭代中努力解决它。

一些用户需求可能是具体的和主观的,通常难以执行和评估,优先级较低。在这种情况下,文档排名可能与一个用户高度相关,但与另一个用户无关。这些问题可以通过使用诸如用户特征分析和结果个性化等技术来解决。

了解内容

所有的数据都不重要,只有一部分数据满足了用户的信息需求。不需要的数据应该被丢弃,以避免任何垃圾进入系统。此外,一些数据需要丰富,以满足用户的检索需求。在规划阶段,理解数据是至关重要的,这样就可以应用适当的工具和技术进行提取、清理、丰富和其他所需的文本处理。要搜索的内容可能取决于三个维度,在大数据世界中通常称为 3v:

  • 数量:一些系统需要处理大量的数据,比如在网络搜索或社交网络中。脸书管理着一个超过 300 的数据仓库。搜索引擎应该是可扩展的,以支持高容量,这就是 SolrCloud 的用途。如果您正在编写定制组件,请从 SolrCloud 的角度考虑,并确保您的特性是云就绪的。
  • 多样性:多样性是指内容的不同形式。要索引的内容可以是结构化的,也可以是非结构化的。为了创建索引,可以从多个来源提取或聚合数据。假设您正在构建一个类似于 IMDB 的电影搜索引擎。这些内容可能来自许多来源,例如主数据源中的记录、内容提供商提供的元数据、第三方应用程序的评论以及 Twitter 或 YouTube 等社交媒体的趋势和流行信息。所有这些内容整合在一起将形成识别电影相关性的基础。
  • 速度:如果搜索引擎需要处理接近实时的查询,数据处理的速度很重要。Twitter 等公司每天处理超过 5 亿条推文,需要实时传输数据。在这种情况下,批处理不是一种选择。

了解媒介

媒介是指用户用来查询搜索引擎的设备。这一点通常被忽略,但在信息检索中却很重要。在手机上打字很困难,所以用户很可能会写一些包含重要关键字的简短查询,而且可能不是用自然语言。用户可以优选地使用单词的简短形式。诸如自动完成之类的功能是这类设备必须具备的。使用移动电话的一个好处是,它可以提供用户的额外信息,比如她的当前位置,这些信息可以用来提高本地化结果的排名。在网络上,搜索体验应该是不同的。你可以为诸如建议、诸如此类和推荐等功能提供大量空间。最重要的是,用户倾向于在网上更自然地书写。

不应该忽略搜索所使用的媒介,并且应该相应地规划你所提供的功能。

执行

在此步骤中,您将执行前面步骤中计划的信息检索任务。在得出结论之前,您需要通过多次实验和研究来执行文本处理和检索技术。在满足检索需求的同时,对于一个搜索引擎来说,其他重要的生产准备因素也不应该被忽视。例如,如果您的业务需要高度分布式和可伸缩的系统,请确保 SolrCloud 支持该功能。

评价

您学习了建立和改进搜索系统的信息检索模型和文本处理技术。检索模型为 Lucene 评分奠定了基础,Lucene 评分根据与用户查询的相似性对文档进行排序,文本处理策略在文档的可查找性方面起着关键作用。每当您调整文档的分数时,例如通过应用 boost,或者在文本处理中执行任何更改,您通常会寻求以下一些问题的答案:

  • 我的搜索结果是否显示了最相关的文档?
  • 是否遗漏了任何高度相关的文件?
  • 结果集中是否包含不良或不相关的文档?
  • 我的结果在这次迭代中有所改进吗,我如何度量改进?

有时你可能会发现系统遗漏了一些高度相关的文档,并调整引擎来提高它们的排名。这种活动反过来会影响文档与其他查询的相关性,这可能会被忽视,并可能在产品生命周期的后期被发现。因此,在执行阶段之后,检查和评估搜索结果变得至关重要。在本节中,您将了解文档的各种状态以及适用于信息检索的评估概念。

当您执行搜索时,匹配的文档将具有表 3-3 中提到的四种状态中的任何一种。表中的第一行表示理想情况下的结果状态(无论文档是否被正确识别)。表中的第一列代表您的系统,以及文档是否匹配用户查询。

表 3-3。

Contingency Table (2×2)

|   | 辨认 | 被拒绝 | | --- | --- | --- | | 相配的 | 正确肯定 | 假阳性 | | 不匹配 | 正确否定 | 假阴性 |

列联表中所有值的说明如下。

正确肯定

在这个场景中,为用户查询识别正确的文档。假设用户查询是“cancer”,应用程序检索匹配“cancer”的文档。这是一个真正积极的例子。

假阳性

在这种情况下,文档被错误地识别为匹配。例如,查询“癌症疾病”会得到一个关于癌症的结果,它指的是螃蟹或星座癌症。这种情况通常是由于低效的文本清理(例如,遗漏的停用词删除)而发生的,这会导致不相关的文档被识别为匹配。记住“垃圾进,垃圾出”这句话。这种情况也被称为假警报或 I 型错误。

正确否定

在这种情况下,文档被正确拒绝。例如,查询“癌症疾病”不会得到与螃蟹或星座相关的癌症结果。大多数文档应该属于这一类别,真正的否定对于确定检索结果的质量没有作用。

假阴性

在这种情况下,应该是响应的一部分的文档被错误地拒绝了。例如,如果系统被期望带来语义相关的结果,而与“良性肿瘤”相关的文档在用户查询“癌症疾病”时被遗漏,那么这就是假阳性的情况。这种情况通常是由于文档的不正确的规范化和丰富化而发生的,在这种情况下,即使添加一个标点符号或缺少小写字母也可能拒绝文档。这种类型的错误可以归类为第二类错误。

假阳性和假阴性的情况是不希望的,应该避免并减少到最低限度。误报场景导致结果分页,并影响用户体验和对系统的信心。如果搜索引擎是通过短消息服务这样的媒介访问的,假阳性匹配是完全不可接受的。假阴性场景甚至会变得更糟,直接影响业务;在电子商务网站上寻找产品的用户将无法发现该产品,即使该产品可从卖方处获得。

评估指标

您需要度量来确定检索的结果是否符合用户的期望。此外,您可能有兴趣将现有系统的结果与新的和改进的系统的结果进行比较。精确度和召回率是有助于评估结果的主要指标。精确度是另一个度量标准,但由于其不切实际的计算,通常会被避免。

准确

准确性通过考虑真阳性和真阴性(系统正确识别或拒绝文档的程度)来衡量有效性。下面是评估的算法:

$$ \frac{TP+TN}{TP+FP+TN+FN} $$

其中

TP 是真阳性,

TN 是真负值,

FP 是假阳性,

FN 是假阴性

有趣的是,准确性从来没有被用来评估一个信息检索系统,因为使用它作为衡量标准可能会完全错误,因为它会考虑相关和不相关的文档。假设索引中有 100 个文档,对于一个查询,语料库只包含 10 个相关匹配。假设该系统是最差的系统之一,对于该查询,它检索 10 个文档,所有这些文档碰巧都不相关。以下说明了 accuracy 如何评估结果:

TP = 0 (no relevant document retrieved)

TN = 80 (out of 90 irrelevent documents, 80 were correctly rejected)

FP = 10 (10 irrelevant documents are incorrectly retrieved)

FN = 10 (10 relevant document incorrectly rejected)

$$ \mathrm{Accuracy}\kern0.5em =\kern0.5em \frac{0+80}{0+80+10+10}=80% $$

从这个例子中,您可以看出,即使系统检索到 0 个相关结果,您仍然可以获得 80%的准确率。因此,通常避免将准确性作为衡量标准,而考虑精确度和召回率。

精确度和召回率

查准率和查全率是评价信息检索系统有效性的两个重要指标。Precision 是检索到的相关文档的分数,recall 是检索到的相关文档的分数。精确度和召回率可以使用以下公式计算:

$$ \mathrm{Precision}\kern0.5em =\kern0.5em \frac{Relevant\kern0.5em \cap \kern0.5em Retrieved}{Retrieved} $$

$$ \mathrm{Recall}\kern0.5em =\kern0.5em \frac{Relevant\kern0.5em \cap \kern0.5em Retrieved}{Relevant} $$

假设您的索引有 20 个与音乐相关的文档和 10 个与电影相关的文档。现在,与音乐相关的查询可能返回 10 个文档,其中 5 个与音乐相关,5 个与电影相关。系统的精度将是 5/10 = 1/2,即 50%。召回率将是 5/20 = 1/4,即 25%。

简而言之,我们可以说精确度是质量的衡量标准,而召回率是数量的衡量标准。因此,高召回率意味着算法返回了大部分相关结果,而高精度意味着算法返回了比不相关结果更多的相关结果。

计算精度更容易,因为您需要从检索到的文档中识别相关的文档。假设您在 Solr 中查询 10 个文档,指定rows=10。在这种情况下,您甚至可以手动遍历文档来识别相关的文档。因为召回工作在相关文档上,这在语料库的所有文档中是很难识别的。它的计数可能非常大,手动遍历将是一个挑战。

根据你要解决的问题,这两种方法中的一种可能对你更重要。例如,在法律领域,您可能对检索所有相关文档感兴趣,因此高召回率会让您更感兴趣。如果您正在构建一个 web 搜索引擎,您会对检索最相关的文档感兴趣,因此您会对高精度感兴趣。通常,这两种方法一起使用来达到一种折衷,因为信息检索的一般目标是检索几个相关的文档并将最相关的文档放在最上面。

f-测度

F-measure 可用作系统的单一测量,因为它结合了精确度和召回率来计算结果。它计算精度和召回率的调和平均值如下:

$$ F; Score=\frac{2;*;\mathrm{Precision};*;\mathrm{Recall}}{\mathrm{Precision}+\mathrm{Recall}} $$

摘要

这一章主要讲述与信息检索相关的概念和方法。您了解了信息检索模型和评估搜索结果有效性的方法。您还了解了可以用来开发更有效的搜索引擎的工具和资源。

下一章将介绍模式设计,接下来的章节将介绍文档索引和结果搜索。第十一章包含了与本章讨论的文本处理概念相关的例子。

四、模式设计和文本分析

Solr 中的搜索引擎开发最初遵循四个连续的步骤:Solr 设置、模式设计、索引文档和搜索结果。模式设计是定义数据结构的过程,是索引和搜索过程的先决条件。

本章包括定义字段、指定类型和其他功能。

文本分析在信息检索中的作用至关重要。是文本分析过程决定了术语(最小的信息,用于文档的匹配和评分)。本章涵盖了文本分析过程,包括分析器做什么,Solr 中可用的各种分析器/记号赋予器,以及将它们链接在一起。

在本章中,你将学习在无模式模式下运行 Solr,使用托管模式,以及使用 REST APIs。

本章分为以下几节:

  • 模式设计
  • 文本分析
  • 无模式化
  • 用于管理模式的 REST API
  • solrconfig.xml 文件
  • 常见问题

模式设计

模式设计是构建有效搜索引擎的最基本步骤之一。它是定义数据结构并应用文本分析来控制匹配行为的过程。

在定义模式之前,您应该理解您试图解决的问题,并准备好您的策略。你应该能够回答几个问题,包括这些:你用 Solr 做什么?你会提供什么样的搜索体验?数据的哪一部分应该是可搜索的?要检索什么信息?

Solr 通常用于索引非规范化数据。如果您的数据是规范化的,您可能希望在建立索引之前将其展平并定义结构。如果完全反规范化是一个挑战,Solr 允许您连接索引。但是 Solr 中的连接不同于数据库连接,它有自己的局限性。Solr 还支持索引层次数据,具有父子关系。第七章介绍了这些高级搜索功能。

所有的搜索引擎都是独一无二的,要么是因为领域、数据的性质,要么是其他因素。可以对全文或元数据进行搜索。此外,Solr 不仅仅局限于作为一个文本搜索引擎,它还被广泛用作 NoSQL 数据库和执行分析;已经看到了许多新的创新用例。例如,一个名为 Lucene Image Retrieval (LIRE)的项目使用 Solr 进行图像搜索。

如果您使用 Solr 作为 NoSQL 数据存储,或者想要快速开始,或者没有完整的数据可见性,或者正在使用动态的数据结构,您可以选择无模式,让 Solr 自动检测fieldType和索引文档。从 5.0 版开始,Solr 默认以无模式模式创建一个内核。在本章的最后你会学到更多关于这个主题的知识。

文档

在 Solr 中,每个搜索结果对应一个文档。这个基本的信息单元类似于数据库表中的记录。开发搜索引擎时,模式设计的第一步是确定搜索引擎的文档内容。相同的数据可以用不同的方式表示和索引,文档的定义也可以不同,这取决于您对引擎及其用途的预期结果。

比如印度电影业,大部分电影都有五到十首歌。如果你正在构建一个电影搜索引擎,每部电影就是一个文档。每个文档包含多首歌曲,索引为一个multiValued字段。相比之下,如果您正在构建一个音乐搜索引擎,每首歌曲的名称将是一个唯一的文档,而电影名称在这些文档中是多余的。

类似地,如果您正在开发一个在线书店,每本书或期刊都将是一个文档,其中包含 ISBN、书名、作者和出版商等信息。但是如果你正在为研究人员开发一个搜索引擎,也许这本书或杂志的每一章、主题或文章都可以是一个文档。

schema.xml 文件

schema.xml文件定义了文档结构及其包含的字段。向 Solr 添加数据或查询结果时会用到这些字段。在下一节中,您将了解 Solr 中的字段。

在 Solr 中,schema.xmlsolrconfig.xml是两个主要的配置文件。通常你会用它们来修改你的搜索策略和增强用户体验。这些文件可以位于$SOLR_HOME内核的conf目录中,也可以位于指定的配置集中。

schema.xml文件有明确的目的,而solrconfig.xml文件用于所有其他配置。以下是schema.xml支持的配置列表:

  • 定义field type s 和文本分析,如果支持的话
  • 定义每个字段及其适用的属性
  • 定义动态字段
  • 指定评分算法
  • 将数据从一个字段复制到另一个字段
  • 指定其他与字段相关的定义,例如唯一字段或默认字段的标识

Note

本章末尾讨论了solrconfig.xml文件。

正如文件扩展名schema.xml所述,配置是以 XML 格式指定的,其中<schema>是根元素。该元素支持两个属性:nameversionname属性为可用于显示的模式指定一个名称。属性声明支持的语法和行为的版本号。Solr 5.3.1 的最新版本是 1.5。以下是一个样本schema.xml文件:

<schema name="example" version="1.5">

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

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

..

<fieldType name="string" class="solr.StrField" sortMissingLast="true"/>

<fieldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/>

..

</schema>

应该重新加载核心以反映该文件中的任何更改。如果在索引时处理过程中进行了任何更改,那么在重新加载核心之后,应该重新索引文档。

菲尔茨

文档中的每一部分信息(例如,ISBN、书名、作者和出版商)都是一个字段,类似于数据库表中的一列。以下是schema.xml中一个字段的示例定义:

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

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

..

Solr 支持一组属性来配置字段的行为。在这些属性中,type定义了fieldType,它类似于 Java 的数据类型,但功能更强大。它将字段链接到索引和查询时要对其数据执行的分析链。

Note

Solr 的早期版本要求在<fields>元素中定义字段。最近的版本已经简化了结构,您可以直接在<schema>元素中指定字段。

字段属性

以下是字段支持的属性的描述。您还可以在field元素中定义fieldType的属性,这将覆盖在fieldType元素中指定的值。

名字

这个强制参数为字段指定一个名称,在对其执行任何操作(如索引、查询和突出显示)时都会引用该名称。

您只能使用字母字符、数字和下划线来命名字段。名称区分大小写。以下划线开头和结尾的名称(如_version__root_)被保留。

命名字段没有其他规范,但是建议遵循一个约定—例如,保持所有单词小写并用下划线分隔,或者遵循 camel 大小写以提高可读性。

类型

该强制参数指定了fieldType的名称。您将在下一节了解更多关于fieldType的内容。

系统默认值

此参数指定为文档编制索引时未提供值的字段的默认值。

当您希望自动索引字段的值而不是在每个文档中指定它时,此参数也很有用。一个经典的例子是索引时间戳,它指示文档被添加到 Solr 的时间。以下是用于索引时间戳的字段定义示例:

<field name="timestamp" type="date" indexed="true" stored="true"``default="NOW"

在这个例子中,type="date"属性将fieldType指定为TrieDateField,并且NOW被提供为default属性的值,该值指定当前时间戳。

该示例还包含indexedstored属性,它们是fieldType的属性,已经在我们的字段定义中被覆盖。有些fieldType参数通常会被字段覆盖,这种情况经常发生,以至于你可能会觉得它们是field而不是fieldType的属性。

保留字段名称

Solr 保留了以下划线开头和结尾的字段名,用于特殊目的。以下是一些保留字段名称:

  • _version_ : Solr 使用该字段进行事务日志记录,以支持 SolrCloud 中文档的乐观锁定和实时 get 操作。
  • _root_:支持嵌套文档(层次数据)需要该字段。它标识嵌套文档块中的根文档。
  • _text_:该字段为预定义的总括字段,支持无模式下的单字段搜索。

螺纹类型

fieldType元素决定了一个字段可以保存的值的类型、应该如何解释该值、可以对其执行的操作以及该字段将经历的文本分析。

字段类型的重要性在 Solr 中至关重要。字段类型和指定的分析链决定了在索引和查询文档时要应用的文本分析管道。匹配策略可以表示为字段定义中的一系列分析器、记号赋予器和记号过滤器。每当您想要调整查询词的搜索以匹配或不匹配索引词时,您通常会更改fieldType定义或其分析链。

下面是一个fieldType元素的例子,其中namefieldType标识符,class是 Solr 的实现类名,sortMissingLast是实现类所需的属性:

<fieldType name="string" class="solr.StrField" sortMissingLast="true" />

Note

Solr 的早期版本要求在<types>元素中定义字段类型。最近的版本已经简化了结构,可以在<schema>元素中直接指定字段类型。

支持的值的类型、字段的行为以及要对字段执行的文本分析取决于三种类型的配置:

  • 实现类
  • 属性
  • 文本分析

接下来将对每种方法进行描述。

实现类

实现类实现字段类型的功能。它决定了字段可以容纳的值的类型以及应该如何解释这些值。为了定制文本处理,实现类通常支持一组属性,其中一些被标记为强制的。

根据数据和要在字段上执行的操作类型,您需要选择一个fieldType。假设你的数据是布尔型的;您将选择实现类solr.BoolField。如果您的数据是整数类型,您可以选择实现类solr.TrieIntField。表 4-1 显示了 Solr 提供的数据类型和支持的实现类。

表 4-1。

Primary Data Types Provided and Their Implementation Classes

| 数据 | 实现类 | | --- | --- | | 线 | `solr.StrField` `solr.TextField` | | 布尔代数学体系的 | `solr.BoolField` | | 数字 | `solr.TrieIntField` `solr.TrieLongField` | | 浮动 | `solr.TrieFloatField` `solr.TrieDoubleField` | | 日期 | `solr.TrieDateField` `solr.DateRangeField` | | 二进制数据 | `solr.BinaryField` | | 空间坐标数据 | `solr.LatLonType``solr.PointType` | | 闭集值 | `solr.EnumField` | | 随机唯一 ID | `solr.UUIDField` | | 外部来源的价值 | `solr.ExternalFileField` |

以下是关于实现类的一些要点:

  • 添加到solr.StrField的文本按原样被索引和查询。没有分析(没有修改)可以应用于它。如果你想用空格分割输入的文本或者把它变成小写,那么solr.StrField并不适合你fieldType。如果您想按原样索引文本,它是合适的—例如,在文档标识符(如产品 SKU)的情况下。
  • solr.TextField允许您对文本执行定制分析,方法是应用一个包含分析器或标记器的链,以及在索引或查询文档时可以应用的标记过滤器列表。在执行分析时,您可以以任何想要的方式转换文本;例如,您可以拆分令牌、引入新令牌、删除令牌或用另一个令牌替换一个令牌。
  • solr.DateRangeField提供了在查询中表示日期范围的附加条款。与solr.TrieDateField的另一个区别是响应格式是字符串而不是日期。

有关 Solr 中可用字段类型的完整列表,请参考位于 https://cwiki.apache.org/confluence/display/solr/Field+Types+Included+with+Solr 的 Solr 官方指南。

Note

solr开头的类名是指一个标准包中的 Java 类(比如org.apache.solr.analysis),不需要提供完整的包名。

字段类型属性

在定义fieldType时,可以将附加属性指定为元素的一部分。这些属性可以是通用的,可以应用于任何fieldType,也可以是特定于实现类的。

下面是一个示例fieldType定义,用于具有不同的precisionStep属性值的数值数据。precisionStep属性支持更快范围的查询,但是增加了索引的大小。如果需要对字段执行范围查询,使用fieldType tint。否则,使用fieldType intprecisionStep="0"禁用不同精度级别的索引数据。

<fieldType name="int" class="solr.TrieIntField"

precisionStep="0" positionIncrementGap="0" sortMissingLast="true"/>

<fieldType name="tint" class="solr.TrieIntField"

precisionStep="8" positionIncrementGap="0" sortMissingLast="true"/>

在这个例子中,sortMissingLast是可以应用于任何fieldType的通用属性,precisionStep是实现类TrieIntField的属性。

以下是 Solr 提供的主要fieldType属性。

编入索引的

要使字段可搜索,必须对其进行索引。如果您要查询匹配文档的字段,必须通过设置属性indexed="true"对其进行索引。只有在启用该属性时,才能对生成的令牌进行索引,并使术语变得可搜索。如果您设置了indexed="false",这个字段上的查询操作将不会获取任何结果,因为没有术语被索引。

当您添加一个永远不会被查询的显示字段时,您可以设置indexed="false"

存信息的

只能显示存储的字段。如果您设置了stored="false",则无法显示该字段中的数据。

当您知道您永远不会将该字段的值返回给用户时,您可以设置stored="false"。此外,如果您将相同的文本索引到多个字段以执行不同的分析,在这种情况下,您可以将所有副本设置为indexed="true"并只存储它的一个副本。无论何时需要显示值,都可以从存储的字段中获取。以下是这种情况的模式定义示例:

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

<field name="title_ws" type="text_ws" indexed="true" stored="false"/>

<field name="title_gen" type="text_general" indexed="true" stored="false"/>

假设您想要查询并显示title_gen字段。在这种情况下,查询title_gen字段并显示title字段。下面是针对这种情况的一个示例查询:

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

如果可能,设置stored="false",因为它将提供更好的性能并减小索引大小(存储信息的大小)。存储的字段越大,检索起来就越费钱。

以下是索引和存储参数的可能组合:

  • indexed="true" & stored="true":当您对查询和显示字段的值都感兴趣时。
  • indexed="true" & stored="false":当你想查询一个字段,但不需要显示它的值时。例如,您可能希望只查询提取的元数据,但显示从中提取元数据的源字段。
  • indexed="false" & stored="true":如果你从来不打算查询一个字段,只显示它的值。
  • indexed="false" & stored="false":忽略字段的输入值。如果被索引的文档或 Lucene 查询包含一个不存在的字段,Solr 可能会报告一个异常。您可以通过创建一个被忽略的字段(禁用这两个属性的字段)来处理此异常。现在,假设您有一个从字段中提取元数据的更新处理器,并且您只对提取的元数据感兴趣,而对非结构化字段的值不感兴趣。这个属性对于忽略这样的字段非常有用。

Note

需要注意的是,存储在字段中的值是它接收到的原始值,而不是经过分析的值。如果要在存储值之前对其进行转换,应该使用转换器或预处理器来完成转换。第五章涵盖了这一主题。

需要

将该属性设置为true会指定一个强制字段。如果被索引的文档不包含任何字段值,则不会被索引。默认情况下,所有字段都设置为required="false"

多值的

此布尔参数指定字段是否可以存储多个值。例如,一首歌可以有多个歌手,一个杂志可以有多个作者,等等。您可能希望将歌手和作者字段设置为multiValued="true"。以下是schema.xml中对singerauthor字段的示例定义:

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

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

因为您通常索引非规范化的数据,所以在schema.xml中很少有multiValued字段是很常见的。如果您将数据从多个源字段复制到一个目标字段,请确保您将目标字段定义为multiValued,因为 Solr 不允许将多个值复制到一个单值字段。如果源字段是multiValued,那么目的字段也应该定义为multiValued。默认情况下,所有字段都是单值的。

文档值

docValues是 Solr 4.0 中引入的一个有趣的特性。如果该布尔参数设置为true,则为该字段创建一个前向索引。倒排索引在排序、分面和突出显示方面效率不高,这种方法有望提高速度,并释放fieldCache。以下是启用了docValues的字段定义示例:

<field name="language" type="string" indexed="true" stored="false" docValues="true"/>

启用docValues需要重新索引所有文档。

排序丢失优先/排序丢失最后

这两个属性都有助于处理排序场景,在这种场景中,被排序的字段不包含某些文档的任何值。在字段上指定sortMissingLast="true"属性会将没有字段值的文档排在最后(在包含字段值的文档之后)。相反,指定sortMissingFirst="true"对结果集顶部没有字段值的文档进行排序。无论排序顺序是升序还是降序,都会保持这个位置。

默认情况下,这两个属性都设置为false,在这种情况下,升序排序会将所有缺少值的文档放在最前面,降序排序会将所有缺少值的文档放在最后。

位置增量间隙

在内部,multiValued字段通过填充值之间的空格来实现(在一个值的最后一个标记和下一个值的第一个标记之间)。可选属性positionIncrementGap指定值之间的虚拟空格数,以避免错误的短语匹配。

假设一个文档的multiValued字段singer有两个值bob marleyelvis presley。如果您指定positionIncrementGap="0",短语 query marley elvis 将匹配该文档,因为没有填充,它被视为同一个令牌流的一部分。如果您指定positionIncrementGap="100",即使是带有中度 slop 的短语也不会匹配,因为 marley 和 elvis 相距一百个空格。

精确步骤

这是一个高级主题,用于对数值和日期字段执行更快的范围查询。范围查询是对具有指定范围内的字段值的文档的搜索请求,例如价格范围为5001,000 的产品。范围查询在第六章中有更详细的介绍。

Solr 为数值字段指定的默认精度步长是 4,这对于执行更快的范围查询是最佳的。如果您不需要对数值字段执行范围查询,请指定precisionStep="0",这将提供更高效的排序。Solr 提供的schema.xml包含一般用途的数值型字段,如 int、float、precisionStep="0"和前缀为 t 的字段,如tintprecisionStep="8"用于需要范围查询的数值型字段。

步长值越小,生成的令牌越多,范围查询的速度越快,但消耗的磁盘空间也越多。通常,该值应保持在 1 到 8 之间。具体实现请参考 http://lucene.apache.org/core/5_3_1/core/org/apache/lucene/search/NumericRangeQuery.html#precisionStepDesc 的 Lucene Javadoc。

omitNorms

字段有与之相关联的规范,它包含额外的信息,如索引时间提升和长度规范化。指定omitNorms="true"会丢弃这些信息,从而节省一些内存。

长度规范化允许 Solr 对较长的字段赋予较低的权重。如果长度规范在您的排序算法中不重要(比如元数据字段),并且您没有提供索引时间提升,那么您可以设置omitNorms="true"。默认情况下,Solr 禁用基本字段的规范。

omitTermFreqAndPositions

索引张贴还存储诸如词频、位置信息和有效载荷(如果有的话)之类的信息。您可以通过设置omitTermFreqAndPositions="true"来禁用这些附加信息。禁用此属性可以节省内存,减小索引大小,并提供更好的性能。如果您需要支持字段上的查询,如短语查询或跨度查询,您不应该禁用omitTermFreqAndPositions,因为这些查询依赖于位置信息。在文档中出现频率较高的查询术语通常被认为与全文域更相关,并且该信息由术语频率维护。因此,您可能不希望在全文字段中禁用它。

省略位置

指定布尔属性omitPosition="true"省略了位置信息,但保留了术语频率。

术语向量

指定布尔属性termVectors="true"保留了完整的术语向量信息。该属性一般与termPositionstermOffsets一起使用。这些由诸如突出显示和“更像这样”之类的功能使用,以提供更好的性能;否则,将使用存储的值重新分析该字段。

期限头寸

指定布尔参数termPositions="true"会保留术语在文档中的位置。

术语偏移量

指定布尔参数termOffsets="true"会保留文档中术语的偏移信息。

术语有效负载

指定布尔参数termPayloads="true"将保留文档中该术语的有效负载信息。有效负载允许您给术语添加数值,您可以在评分中使用该数值。当您想给一个术语(如名词或相关词)一个高权重时,这个特性很有用。

Tip

如果记住类的实现、属性和(尚未涉及的)文本分析似乎太多而难以消化,不要担心!设计自己的模式的最好方法是使用 Solr 发行版中提供的成熟的schema.xml并编辑它。在 Solr 5.x 中,命名配置集sample_techproducts_configs中提供的schema.xml文件可以作为设计模式的参考点。

文本分析(如果适用)

表 4-1 介绍了字符串数据的字段类型solr. TextFieldTextField是一个字段类型的特殊实现,它支持带有单个标记器和多个标记过滤器的可配置链的分析器进行文本分析。分析器可以将输入文本分解成标记,这些标记用于匹配,而不是对整个文本执行精确匹配。反过来,solr.StrField只执行精确匹配,不能应用分析器。

关于TextField的文本分析将在下一节详细介绍。

copyField(复制字段)

您可能希望以多种方式分析同一文本,或者将多个字段中的文本复制到单个字段中。schema.xml中的copyField元素提供将数据从一个字段复制到另一个字段的功能。您在source属性中指定复制源字段名称,在dest属性中指定复制目的字段名称,每当您添加包含源字段的文档时,Solr 将自动将数据复制到目的字段。下面是一个copyField的例子,它将album字段的文本复制到多个字段进行不同的分析:

<copyField source="album" dest="album_tokenized"/>

<copyField source="album" dest="album_gram"/>

<copyField source="album" dest="album_phonetic"/>

如果您将数据从多个源复制到一个目标,或者任何源字段是multiValued,请确保目标字段也被定义为multiValued="true"。否则,Solr 将在索引文档时抛出异常。

复制字段属性

以下是copyField支持的属性。

来源

source属性指定要从中复制数据的字段。源字段必须存在。字段名可以以星号开始和结束,以便从所有匹配该模式的源字段中复制。

建筑环境及 HVAC 系统模拟的软件平台

dest属性指定数据将被复制到的目标字段。目标字段必须存在。字段名可以以星号开始和结束,以便将文本复制到与模式匹配的所有目标字段。只有在源字段中指定了通配符,才能在目标字段中指定通配符。

麦斯查斯

这个可选的 integer 属性允许您检查字段大小,防止索引急剧增长。如果索引到目标字段的字符数超过了maxChars值,这些多余的字符将被跳过。

定义唯一键

schema.xml中的元素<uniqueKey>类似于数据库中的主键。此元素指定唯一标识文档的字段名称。下面是一个示例定义,它将一本书的isbn指定为文档的唯一标识符。

<uniqueKey>isbn</uniqueKey>

声明一个uniqueKey不是强制性的,但是强烈建议定义它。您的应用程序也很有可能有一个。以下是定义uniqueKey字段的规则和建议:

  • 该字段应为字符串或 UUID 类型。
  • 对该字段的任何索引时间分析都可能破坏 SolrCloud 中的文档替换或文档路由,应该避免。
  • 具有多个标记的字段不允许作为uniqueKey
  • 动态字段不能用于声明uniqueKey
  • 不能使用copyField填充该字段的值。

如果您为一个文档编制索引,并且另一个文档已经存在,并且具有相同的唯一键,则现有文档将被覆盖。要更新文档而不是替换它,请使用 Solr 的原子更新特性。第五章提供了更多关于原子更新的细节。

动态字段

Solr 允许您通过指定创建字段的模式来定义动态字段,而不是显式声明所有字段。当索引一个文档时,如果任何指定的字段没有在schema.xml中定义,Solr 会寻找一个匹配的动态字段模式。如果一个模式存在,Solr 创建一个在<dynamicField>元素中定义的fieldType的新字段。

如果您想要对多个字段执行类似的分析,那么您可以为那个fieldType定义一个dynamicField,而不是显式地声明它们。假设您想要为字段电影名称、演员和导演创建 N 元语法。您可以创建如下所示的动态字段,而不是为每个字段显式定义字段:

<dynamicField name="*_gram" type="ngrams" indexed="true" stored="false"/>

同样,如果您正在执行多语言操作,动态字段对于存储特定于语言的信息会很有用:

<dynamicField name="*_ut_en" type="ngrams" indexed="true" stored="true"/>

<dynamicField name="*_ut_fr" type="ngrams" indexed="true" stored="true"/>

<dynamicField name="*_ut_sp" type="ngrams" indexed="true" stored="true"/>

有时,直到文档被索引时,您才知道字段类型—例如,从富文档中提取元数据时。在这种情况下,动态字段很有用。

dynamicField元素支持与字段定义相同的属性,所有行为保持不变。

默认搜索字段

如果搜索请求不包含要查询的字段名,Solr 将搜索默认字段。此元素指定要查询的默认字段。下面是一个不包含字段名的示例查询,Solr 使用默认字段进行查询。

schema.xml:

<defaultSearchField>aggregate_text</defaultSearchField>

Sample query:

select?q=solr rocks&defType=standard // lucene

select?q=solr rocks&defType=edismax  // qf parameter is not specified

在 Solr 3.6 中,该元素已被弃用,不应用作后备。相反,应该使用df请求参数。

索尔克尔·帕塞尔

如果查询中没有指定操作符,这个元素指定查询解析器使用的默认操作符。此处提供了一个示例solrQueryParser定义:

<solrQueryParser defaultOperator="OR"/>

这个元素从 Solr 3.6 开始就被弃用了,应该用q.op请求参数来代替。

类似

Lucene 使用Similarity类实现对文档评分。要使用默认实现之外的实现,可以通过使用该元素并在class属性中指定类名来声明它。下面是schema.xml中默认相似性的规范:

<similarity class="solr.DefaultSimilarityFactory"/>

可以对所有字段或特定字段应用不同的相似性算法。以下是在特定字段上指定不同相似性的示例:

<fieldType name="text_ib">

<analyzer/>

<similarity class="solr.IBSimilarityFactory">

<str name="distribution">SPL</str>

<str name="lambda">DF</str>

<str name="normalization">H2</str>

</similarity>

</fieldType>

<similarity class="solr.SchemaSimilarityFactory"/>

相似性实现在第八章中有更详细的介绍。

文本分析

我们对模式设计的讨论提供了文本分析的概述。在本节中,您将更详细地了解这一过程。

分析是将文本流转换成术语的阶段。术语是 Lucene 用来匹配和计算文档分数的最小信息单位。术语不一定是输入文本中的单词;术语的定义取决于你如何分析文本流。术语可以是整个输入文本、两个或多个单词的序列、单个单词或文本流中任何字符的序列。

图 4-1 描述了一个例子:由于不同的分析,相同的输入文本流发出不同的术语。如您所见,KeywordTokenizerFactory没有对流进行任何更改,发出的术语是整个输入文本;相比之下,WhitespaceTokenizerFactory在空白上拆分,并发出多个术语。在本章的后面,你会学到分析器和记号赋予器。

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

图 4-1。

Text stream and the emitted terms

为了理解匹配行为,表 4-2 提供了几个简单的用户查询,这些用户查询通过该分析在字段上进行搜索(或匹配)。为了简单起见,该表提供了一个单词的查询,您可以假设在查询时没有执行任何分析。

表 4-2。

Query Match Status

| 询问 | Tokenizer | 匹配状态 | 描述 | | --- | --- | --- | --- | | 拿铁咖啡 | `KeywordTokenizerFactory` | 不匹配 | "喝拿铁咖啡或卡布奇诺."是索引术语。 | | 拿铁咖啡 | `WhitespaceTokenizerFactory` | 比赛 | “拿铁”是索引术语。 | | 拿铁咖啡 | `WhitespaceTokenizerFactory` | 不匹配 | 索引术语是“latte ”,由于大小写不同,这些术语不会匹配。 | | 卡普契诺咖啡 | `WhitespaceTokenizerFactory` | 不匹配 | 索引术语“卡布奇诺”包含一个额外的句点,因此术语不同。 |

如果您在标记化之后应用了PatternReplaceCharFilterFactory来删除特殊字符,那么表 4-2 中的 Cappuccino 查询将会匹配。

这些是简单的示例,您可以根据需要应用其他处理(例如小写、转换为最接近的 ASCII 字符或删除特殊字符和符号)。您执行的处理取决于您的匹配要求;这并不是说使用KeywordTokenizerFactory进行的分析是错误的,而是因为它有不同的目的。

Lucene 同时使用布尔模型(BM)和向量空间模型(VSM)来确定文档与查询的相关性。组合工作如下:由 BM 匹配的文档由 VSM 排序。BM 确定一个术语在一个字段中是否匹配。然后,文本分析开始起作用,控制匹配行为,并确定与索引中的术语匹配的查询术语。分析链在文档匹配中起着至关重要的作用。

Note

查询解析器在确定匹配文档时也起着重要的作用。你会在第六章中了解到。

分析器定义了一个进程链,每个进程执行一个特定的操作,比如拆分空白、删除停用词、添加同义词或者转换成小写。每个进程的输出都称为令牌。由分析链中的最后一个过程生成的标记(要么被索引,要么被用于查询)被称为术语,并且只有被索引的术语才是可搜索的。被过滤掉的标记(例如通过停用词移除)在搜索中没有意义,并且被完全丢弃。

图 4-1 过度简化了 Solr 中一个术语的例子。Solr 允许您在索引和查询时对令牌流执行相同或不同的分析。大多数情况下还需要查询时分析。例如,即使您在索引时执行小写,表 4-2 中的后一个查询将导致不匹配,因为查询标记本身以大写字母开始。您通常在索引和搜索时执行相同的分析,但是您可能希望仅在索引时或仅在搜索时应用某些分析,如同义词扩展的情况。

图 4-2 描述了一个对文本流执行一系列分析的典型分析器。链中的第一个进程接收输入流,在空白上分割它,并发出一组标记。下一个进程检查非 ASCII 字符的标记;如果有的话,它们被转换成最接近的等效 ASCII 码。转换后的标记被输入到去除停用词的过程中,该过程过滤掉停用词列表中存在的关键词。下一个过程将所有标记转换成小写,然后词干分析器将标记转换成它们的基本格式。最后,修整标记,最后这个过程发出的术语最终被索引或用于查询。

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

图 4-2。

A typical analysis chain

以下是构建图 4-2 中指定的分析链的示例fieldType定义:

<fieldType name="text_analysis" class="solr.TextField" positionIncrementGap="100">

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.AsciiFoldingFilterFactory"/>

<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />

<filter class="solr.LowerCaseFilterFactory"/>

<filter class="solr.PorterStemFilterFactory"/>

<filter class="solr.TrimFilterFactory"/>

</analyzer>

</fieldType>

图 4-3 描述了该fieldType定义的文本分析示例。假设您正在构建一个为用户发现电影的电影搜索引擎,即使查询部分匹配。系统使用前面的文本分析并将电影名索引为“杀死一只嘲鸟”,如您所见,“嘲鸟”中的字母“I”错误地带有重音。您将看到当一个几乎不记得正确的电影名称搜索的用户杀死知更鸟时,匹配是如何工作的。还要注意,像这位用户一样,大多数用户通常用小写字母提供查询。以下步骤说明了分析链如何导致用户找到《杀死拟声鸟》这部电影:

WhitespaceTokenizerFactory splits the text stream on whitespace. In the English language, whitespace separates words, and this tokenizer fits well for such text analysis. Had it been an unstructured text containing sentences, a tokenizer that also splits on symbols would have been a better fit, such as for the “Cappuccino.” example in Figure 4-1.   AsciiFoldingFilterFactory removes the accent as the user query or content might contain it.   StopFilterFactory removes the common words in the English language that don’t have much significance in the context and adds to the recall.   LowerCaseFilterFactory normalizes the tokens to lowercase, without which the query term mockingbird would not match the term in the movie name.   PorterStemFilterFactory converts the terms to their base form without which the tokens kill and kills would have not matched.   TrimFilterFactory finally trims the tokens.

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

图 4-3。

Text analysis and term matching

代币

令牌存储文本和附加元数据,包括文本的开始和结束偏移量、令牌类型、位置增量信息、应用程序定义的位标志和字节有效负载。

如果您正在编写用于文本分析的自定义实现,那么不应该忽略额外的元数据,因为一些组件需要这些额外的信息才能工作。偏移值由突出显示等功能使用。位置增量值标识字段中每个标记的位置,在短语和跨度查询中起着重要作用。大于 1 的位置增量值表示有间隙(该位置的单词已被删除),例如,停用词的删除会留下间隙。值为 0 会将标记放置在与前一个标记相同的位置;例如,同义词丰富将同义词放在相同的位置。位置增量的默认值为 1。

条款

术语是分析过程的输出,每个术语都被索引并用于匹配。除了令牌类型和标志之外,术语包含与令牌相同的信息。

Note

术语令牌和术语有时可互换使用,以指代发出的令牌。

分析者

分析过程由分析器定义,分析器被指定为schema.xml<fieldType>的子元素。它分析输入文本,并有选择地通过分析链运行它。分析仪可以执行两种类型的分析,如下所述。

简单分析

元素指定了实现分析的类的名称。例如:

<fieldType name="text_simple" class="solr.TextField">

<analyzer class="org.apache.lucene.analysis.WhitespaceAnalyzer"/>

</fieldType>

前面用于简单分析的fieldType定义通过在空白上分割输入令牌流来发出术语。如果输入流是鲍勃·马利,那么类型为text_simple的字段的分析项将是BobMarley

分析链

analyzer元素包含一系列名为tokenizerfilter的子元素,而不是指定实现分析的类。这些元素链接在一起形成了处理和分析输入文本的管道。元素在链中的指定顺序很重要:它们应该按照您希望它们运行的顺序来指定。在高层次上,分析链可用于执行以下操作:

  • 根据空白、符号或大小写变化拆分文本
  • 常化
  • 清除垃圾
  • 执行浓缩

图 4-2 中的fieldType定义提供了一个分析链的例子。您会注意到这个分析器的fieldType定义不包含classname属性,就像简单分析一样。相反,它包含一组子元素,每个子元素对应于链中的一个文本分析过程。

分析阶段

在前面的例子中,我们为索引和查询文档执行了相同的分析。Solr 允许您在两个阶段(索引和查询)执行不同的分析,一个阶段甚至可以没有任何分析。例如,如果您希望 inc 与 incorporated 匹配,您可能需要进行同义词扩展,并且在一个阶段中进行,无论是索引还是查询,都将匹配这两个术语。

以下是关于文本分析的一些要点:

  • 分析器的范围限于它所应用的领域。它不能创建新的字段,并且不能将一个字段发出的术语复制到另一个字段。
  • copyField复制字段的存储值,而不是分析链发出的术语。

两个分析阶段的描述如下。

索引

当一个文档被添加到 Solr 时,索引时间分析适用。每个字段都有一个类型。如果fieldType定义了一个分析器,那么文本流将被分析,发出的术语将与位置和频率等发布信息一起被编入索引。图 4-4 描述了一个指数时间分析过程。需要注意的是,分析过程只影响索引词,存储的值始终是字段接收的原始文本。转换后的文本只能被查询,不能被检索。

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

图 4-4。

Index-time analysis

通过使用type="index"属性指定 analyzer 元素,可以在索引时执行不同的分析,如下所示:

<fieldType name="text_analysis" class="solr.TextField" positionIncrementGap="100">

<analyzer type="index">

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.AsciiFoldingFilterFactory"/>

...

</analyzer>

<analyzer type="query">

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.AsciiFoldingFilterFactory"/>

...

</analyzer>

</fieldType>

Note

索引时间分析器或其分析链中的任何变化都需要对所有文档进行核心重载和重新索引。

询问

查询解析器为用户查询调用对字段的查询时分析。图 4-5 描述了一个查询时分析过程。您可以看到该分析不同于索引时间分析。

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

图 4-5。

Query-time analysis

通过用fieldTypetype="query"属性指定 analyzer 元素,可以在查询时执行不同的分析。

Note

查询时分析器或其分析链中的任何更改只需要重新加载核心,而不需要重新索引。

分析工具

为了检查字段和分析过程,可以使用以下工具。

Solr 管理控制台

为了检查在schema.xml中定义的字段和fieldTypes,Solr 在管理控制台中提供了 Analysis 选项卡。关联页面为索引时和查询时分析提供了单独的字段。您需要输入要分析的文本,选择字段或fieldType,点击分析值。如果在两个文本框中都提供了值,则该过程会突出显示匹配的标记。如果您只对分析器发出的术语感兴趣,可以禁用详细输出选项。

在分析屏幕上,您可以在分析字段名下拉列表旁边找到一个问号符号,它会将您带到模式浏览器屏幕。此屏幕允许您查看字段的属性并检查索引术语。图 4-6 显示了一个字段的模式浏览器屏幕。它显示字段属性和索引到该字段的顶级术语以及术语频率。

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

图 4-6。

Schema Browser

卢克(男子名)

Lucene Index Toolbox (Luke)是一个很棒的开源工具,用于检查和修改索引。你应该使用与 Lucene 库相同的 Luke 版本。您可以在 https://github.com/DmitryKey/luke 找到该工具。

分析仪组件

分析链在<analyzer>元素中指定,由下面描述的三种组件组合而成。图 4-7 描述了一个文本流是如何流经分析器并发出令牌的。

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

图 4-7。

Analyzer components chaining

字符过滤器

该组件在记号赋予器处理字符之前对字符进行清理和预处理。多个CharFilters可以链接在一起,并且应该总是在Tokenizer之前配置。

令牌设备

Tokenizer接受文本流,处理字符,并发出一系列标记。它可以根据空格或符号等字符来中断文本流。相邻的字符序列构成了记号。它还可以添加、删除或替换字符。

Tokenizers应始终在TokenFilters之前指定,并且在分析器中只能指定一次。此外,记号赋予器没有关于它被指定的字段的信息。表 4-3 指定了 Solr 提供的记号赋予器实现。

表 4-3。

Tokenizer Implementations

| 履行 | 描述 | | --- | --- | | `KeywordTokenizerFactory` | 没有符号化。为整个文本创建单个标记。首选用于对元数据等字段进行精确匹配。输入:“鲍勃·马利是一位传奇歌手。”输出:“鲍勃·马利是一个传奇歌手。” | | `StandardTokenizerFactory` | 精密智能的通用型`Tokenizer`。它分割空白和标点符号,识别句子边界和网址。它使用 Unicode 标准单词边界规则。输入:“鲍勃·马利是一位传奇歌手。”输出:"鲍勃" "马利" "是" "一个" "传奇" "歌手" | | `WhitespaceTokenizerFactory` | 它只是在空白上分割。输入:“鲍勃·马利是一位传奇歌手。”输出:"鲍勃" "马利" "是" "一个" "传奇" "歌手。" | | `ClassicTokenizerFactory` | 它支持 Solr 3.1 中的`StandardTokenizer`行为。它可以识别电子邮件 ID,并保持它们的完整性(当前的`StandardTokenizer`因为有了`@`符号而将 ID 分开)。输入:“bob 的 id 是 contact@bob.com”输出:“Bob 的“id”是“contact@bob.com” | | `LetterTokenizerFactory` | 它将连续的字母视为记号,其他的都被丢弃。输入:“bob 的 id 是 contact@bob.com”输出:“Bob 的 id”是“contact”“Bob”“com” |

令牌过滤器

TokenFilter处理Tokenizer产生的令牌。TokenizersTokenFilters的重要区别在于Tokenizer输入是Reader,而TokenFilter输入是另一个TokenStream

TokenFilters应该总是在Tokenizer之后指定,并且可以指定任意次。另外,TokenFilter没有关于它正在处理的字段的信息。

Solr 提供了广泛的令牌过滤器工厂。下一节将介绍初级过滤器工厂。有关 Solr 提供的过滤器的完整列表,请参考位于 https://cwiki.apache.org/confluence/display/solr/Filter+Descriptions 的 Solr 官方参考指南。

常见文本分析技术

Solr 开箱即用提供了各种各样的分析器、令牌化器和令牌过滤器,可以快速轻松地构建一个有效的搜索引擎。在本节中,您将了解文本处理技术,使用这些字段分析器可以实现所需的搜索行为。

同义词匹配

在探索同义词匹配之前,让我们考虑一个例子。定义一词的同义词包括描述、详述、解释、说明和说明。在执行搜索之前,最终用户会考虑使用哪些关键字来表达意图和制定查询。假设用户正在寻找单词同义词的定义;他的查询可以是定义同义词、描述同义词、或定义一词的任何其它同义词。因此,维护术语的同义词非常重要。否则,即使您的语料库包含用户正在寻找的信息,系统也无法检索到它。

Solr 提供了SynonymFilterFactory来执行基于字典的同义词匹配。可以在一个文本文件中配置同义词,该文件包含一系列关键字及其匹配的同义词。以下是schema.xmlSynonymFilterFactory的定义示例:

<fieldType name="text_synonyms" class="solr.TextField">

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt"

ignoreCase="true" expand="false"/>

</analyzer>

</fieldType>

有关文件格式,请参考命名配置集中提供的样本synonyms.txt文件。

因素

SynonymFilterFactory支持以下参数:

  • synonyms:指定包含同义词映射的外部文本文件的名称。
  • ignoreCase:如果设置为true,查找时将忽略关键字的大小写。
  • expand:如果true,令牌将被扩展。如果false,同义词将折叠到关键字。

考虑到拥有一个通用同义词(适用于该语言)和特定同义词(特定于您的领域)的需要,您可以在一个分析链中配置多个SynonymFilters。基本上,如果需要,任何TokenFilter都可以被多次配置。

SynonymFilterFactory可以在索引时和查询时应用,但通常应用于两者之一。索引时同义词扩展会导致更大的索引大小,列表中的任何更改都需要重新编制索引。但它也有好处。扩展的标记将有助于逆文档频率(IDF ),其给予更稀有的单词更高的重要性。

查询时同义词扩展不会增加索引大小,也不会让您在更改同义词列表后重新建立索引,但是它会增加查询响应时间。查询时间扩展的问题在于包含多个单词的同义词,比如电影明星。在被分析器处理之前,这些词已经被QueryParser标记成电影和明星。短语查询的另一个问题是:短语中的标记会被扩展,这导致短语有多个标记,最终可能是不匹配的。例如,电影明星可以扩展到"(movie | cinema) (star | actor | cast)",最终无法匹配包含电影明星的文档。

语音匹配

语音匹配算法用于匹配发音相似的单词。当单词拼写错误时,编辑距离和 N-grams 特别适用,但当单词的写法不同但发音总是相同时,语音学也适用。

名字匹配是一个典型的例子:一个名字可以有多种拼写,但发音相同。为了匹配一个人的名字,语音算法变得更加重要,因为没有标准的名字定义或字典。一个名字可以有多个发音相同的变体。比如 Christy 和 Christie 是同一个名字的音素,两者都是正确的。

语音匹配可能不适用于打字错误,这是无意的,通常包括顺序错误的字母(例如,good typed as godo)。然而,如果错误是故意的,语音匹配可能是有效的,例如在 tweets 或短信中,用户可能会用 gud 替换 good,或者用 u 替换 you。

语音匹配的目的是提高召回率,并确保不会遗漏可能匹配的术语。建议对语音字段给予较低的提升,因为这可能最终匹配完全不相关的单词,要么是因为单词在语音上相似,但代表不同的概念,要么是因为算法的限制。

语音学是高度特定于语言的,并且大多数算法是为英语开发的。这些算法对记号进行编码,并且对于发音相似的单词应该是相同的。表 4-4 提供了 Solr 和样本定义支持的语音算法列表。

表 4-4。

Phonetic Algorithms

| 算法 | 定义 | | --- | --- | | 贝德-莫尔斯语音匹配(BMPM) | 为名字和姓氏提供了更好的代码。配置:`` | | 桑迪克斯 | 发展到匹配发音相似的姓氏。它生成四个字符的代码,以一个字母开头,后跟三个数字。配置:`` | | 变音 | 它通常用于匹配发音相似的单词,并不限于姓氏。配置:`` | | 双重变音 | 变音位的这种扩展解决了英语以外的语言的特殊性。配置:`` `` | | 精制 Soundex | Soundex 的改进版本,它为相同的代码匹配更少的名称。配置:`` | | 戴奇-莫科托夫 Soundex | Soundex 的改进。它为匹配姓名提供了很高的准确性,尤其是斯拉夫语和欧洲语。它生成六位数字的代码。配置:`` | | 穴居人 | 针对新西兰口音进行了优化。配置:`` | | 科隆音素(科隆音素) | 适合德语单词。配置:`` | | 纽约州身份识别和情报系统 | 提供比 Soundex 更好的结果,使用更复杂的代码生成规则。配置:`` |

N-Grams

N-Gram将输入的单词分解成多个子单词,称为 grams。例如,大小为 4 的输入令牌 hellosolr 的克数将是 hell、ello、llos、loso、osol 和 solr。如果用户查询是 solr,您可以看到最后生成的 gram 将匹配。

n-gram 对于匹配子字符串和拼写错误的单词很有用。它对于自动完成、前缀和后缀查询以及通配符搜索等功能也很有用。n 元语法提高了召回率,并匹配了否则会被遗漏的标记。

Solr 为 N 元语法提供了TokenizersTokenFilters。如果要在标记化的文本上生成克,可以使用TokenFilter;否则,可以使用Tokenizer。表 4-5 列出了 Solr 为 N-gram 提供的实现。

表 4-5。

N-Gram Implementations

| 履行 | 定义 | 例子 | | --- | --- | --- | | `NGramTokenizerFactory` `NGramFilterFactory` | 从所有字符位置生成指定范围内所有大小的 N 元字符属性:`minGramSize`:最小字符大小`maxGramSize`:最大字符大小配置:`` `` | 输入:hellosolr 输出:hel,ell,llo,go,oso,sol,olr,hell,that,llos,loso,oso,solr | | `EdgeNGramTokenizerFactory` `EdgeNGramFilterFactory` | 从指定的边生成指定范围内所有大小的 N-gram 属性:`minGramSize`:最小 gram 大小`maxGramSize`:最大 gram 大小`side`:应该从其生成 gram 的边。值可以是`front`或`back`。默认为`front`。配置:`` `` | 输入:hellosolr 输出:hell,hello |

与 N 元文法相比,边 N 元文法生成的令牌更少,性能更好。N-gram 提供了更好的回忆,但应该小心使用,因为它可能导致过匹配。

如果保持minGramSize较小,将会生成大量令牌,索引大小和索引时间将会增加,并会对性能产生影响。你应该只为几个字段生成 N-gram,并尽量保持minGramSize高和maxGramSize低。

叠瓦作用

Shingling 是以单词为基础而不是以字符为基础生成 grams 的。对于输入流 apache solr rocks,生成的瓦片区将是 apache solr 和 solr rocks。

通过允许您匹配子短语,Shingling 提供了一种提高相关性排名和精确度的机制。短语查询匹配整个标记序列,基于标记的匹配匹配一个发出的术语,而瓦片区介于两者之间。与短语相比,Shingling 提供了更好的查询性能,但需要额外的令牌。

瓦片区通常在索引时应用,但是也可以在索引和查询时应用。此外,瓦片区通常会得到很大的提升。

以下是schema.xmlSynonymFilterFactory的示例定义:

<fieldType name="text_shingles" class="solr.TextField">

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.ShingleFilterFactory" minShingleSize="2" maxShingleSize="3" outputUnigrams="false"/>

</analyzer>

</fieldType>

因素

ShingleFilterFactory支持以下参数:

  • minShingleSize:每个瓦片区的最小令牌数。默认值为 2。
  • maxShingleSize:每个瓦片区的最大令牌数。默认值为 2。
  • outputUnigrams:该布尔参数指定是否应该生成单个令牌。默认情况下,这被设置为true。如果这个参数是true,并且你正在给这个领域一个很高的提升,评估这个提升不会对相关性产生负面影响,因为你的总体意图是给由多个令牌组成的瓦片区更高的提升。
  • outputUnigramsIfNoShingles:该布尔参数指定如果没有生成瓦片区,是否应该生成单独的令牌。默认情况下,这被设置为false
  • tokenSeparator:这指定了用于连接形成瓦片区的标记的分隔符。默认值为" "

堵塞物

词干处理是将单词转换成基本形式的过程,以匹配单词的不同时态、语气和其他变化。基词也叫词干。值得注意的是,在信息检索中,词干提取的目的是匹配一个单词的不同词形变化;茎不一定是形态根。

词干分析器通常是特定于语言的。英语有多种词干分析器可供选择。对于其他语言,词干分析器也可用。表 4-6 指定了 Solr 中支持的主要词干分析器。

表 4-6。

Stemming Algorithms

| 史泰克 | 工厂级 | 特征 | 语言 | | --- | --- | --- | --- | | 波特茎 | `solr.PorterStemFilterFactory` | 基于算法。快速算法。 | 英语 | | 克斯特姆 | `solr.KStemFilterFactory` | 与波特斯干相似,但攻击性较弱,速度较快。 | 英语 | | 雪球 | `Solr.SnowballPorterFilterFactory` | 基于算法。比波特斯干慢。更有侵略性。 | 多种语言 | | 洪斯佩尔 | `solr.HunspellStemFilterFactory` | 基于词典和基于规则的词干分析器的组合。使用与 Apache OpenOffice 中相同的词典。 | 支持 99 种语言 | | 英语最小词干 | `solr.EnglishMinimalStemFilterFactory` | 不那么咄咄逼人。适用于轻词干和情况,如复数到单数的转换。 | 多种语言 |

图 4-8 显示了以旅行为基础的单词的词干结果。我们在索引时应用了PorterStemFilter,在查询时应用了EnglishMinimalStemFilter,以说明两种算法在结果和激进程度上的差异。PorterStemFilter更具攻击性,将 travel 的所有词形变化转换为其基本词,而EnglishMinimalStemFilter只对少数词形变化进行转换。

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

图 4-8。

Stemming algorithm comparison Caution

在图 4-8 中,为了进行比较,在索引时和查询时配置了不同的词干分析器。很少会出现您希望这样配置的情况。

在对标记进行词干提取时,可能会出现过度提取和不足提取的问题,因此在选择词干提取算法时应考虑这些误差测量值。在越界中,两个完全不相关的单词源于同一个词根,尽管它们不应该是这样的(误报)。在理解中,两个相关的单词并不是源于同一个词根,尽管它们应该是(一个真正的否定)。比如,EnglishMinimalStemFilter不把 travel 词干化为 travel 就是理解不足的情况,PorterStemFilter把 university 和 universe 词干化为 universe 就是逾越的情况。

词干通常应该在索引和查询时使用。词干不会增加索引的大小。

Solr 分别用KeywordMarkerFilterStemmerOverrideFilter提供解决过度转向和转向不足问题的规定。

关键字标记过滤器

通过在文件中指定受保护的单词,防止单词被词干化。文件名应该在过滤器工厂的protected属性中指定。

KeywordMarkerFilter针对因越界而导致误报的黑名单词做出有效的解决方案。在前面的例子中,你看到大学被超越了。假设你不希望一个词被词干化;您可以将其添加到受保护的单词文件中并配置KeywordMarkerFilter。以下是schema.xml中的fieldType配置示例:

<fieldType name="text_stem" class="solr.TextField">

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt" />

<filter class="solr.PorterStemFilterFactory" />

</analyzer>

</fieldType>

KeywordMarkerFilterFactory应始终在出厂前配置词干分析器。如果在词干分析器之后配置它,它将不起作用。

StemmerOverrideFilter

为了解决理解不足的问题,Solr 提供了StemmerOverrideFilter。它覆盖由配置的词干分析器完成的词干,词干映射到词干覆盖文件中的单词。

词干替代文件名是在过滤器工厂的dictionary属性中配置的,它包含单词到制表符分隔文件中词干的映射。以下是schema.xml中的fieldType配置示例:

<fieldType name="text_stem" class="solr.TextField">

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.StemmerOverrideFilterFactory" dictionary="stemdict.txt" />

<filter class="solr.PorterStemFilterFactory" />

</analyzer>

</fieldType>

StemmerOverrideFilterFactory应始终在出厂前配置词干分析器。如果在词干分析器之后配置它,它将不起作用。

黑名单(停用词)

有些术语对你的搜索引擎来说并不重要。例如,像 a、an 和 the 这样的词会添加到单词包中,最终会增加误报率。您可能希望阻止这些词被索引和查询。Solr 提供StopFilterFactory将停用词文件中指定的词从字段标记中列入黑名单并丢弃。下面是一个配置示例:

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.StopFilterFactory" words="stopwords.txt"/>

</analyzer>

图 4-9 以电影搜索引擎为例说明停用词的去除;单词 movie 被假定为停用词。我们在停用词文件中添加了关键字 movie,并分析了电影《肖申克的救赎》的文本评论。停用字词过滤器从令牌流中移除令牌电影。此外,它在流中创建了一个间隙,这可以由令牌元数据确定,如位置、开始和结束偏移量。

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

图 4-9。

Stop-words removal

sample_techproducts_configs配置集中提供的stopwords.txt文件是空白的,它不包含任何条目。常见的英语单词如 a、an 和 the 通常是stopwords.txt文件的良好候选。要找到特定于您的内容的停用词,Solr 管理控制台中的模式浏览器是一个很好的起点。您可以选择一个字段,并通过单击“加载术语信息”按钮加载出现频率最高的术语。然后,您可以检查这些高频术语,以确定其中是否有一些可以作为停用词。

白名单(保留单词)

白名单与停用词删除或黑名单相反。这仅允许出现在指定列表中的那些令牌通过,所有其他令牌都被丢弃。假设您的应用程序支持一组指定的语言;在这种情况下,您可能希望应用只保留受支持的语言并丢弃所有其他文本的筛选器。

您可以通过使用KeepWordFilterFactory类在 Solr 中实现这一点。该过滤器通常在索引时应用。以下是一个配置示例:

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.KeepWordFilterFactory" words="keepwords.txt"/>

</analyzer>

以下是支持的参数:

  • words:包含允许单词的文本文件的路径。文件定义规则与停用字词文件相同。
  • ignoreCase:该布尔参数,如果设置为true,使过滤器对大小写变化不敏感。默认值为false

其他标准化

在上一节中,您了解了词干化,这是文本规范化的一种形式。类似地,您可能需要通过向分析链添加令牌过滤器来执行其他文本规范化。以下是其他常用的规范化器。

用小写字体书写

用户查询通常不遵循语言的大小写约定,大多以小写形式提供。因此,您可能希望您的搜索不区分大小写。实现这一点的最好方法是遵循一个惯例,即所有的标记都使用相同的大小写。Solr 提供了LowerCaseFilterFactory来将令牌中的所有字母转换成小写,如果不是的话。例如,鲍勃马利必须匹配鲍勃马利。以下是一个配置示例:

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.LowerCaseFilterFactory"/>

</analyzer>

这通常适用于索引和查询。

转换为最接近的 ASCII 字符

如果您希望您的搜索体验对重音不敏感,以便重音和非重音字符都匹配相同的文档,那么您应该将ASCIIFoldingFilterFactory添加到分析链中。此过滤器将 Unicode 字符转换为最接近的 ASCII 等效字符(如果有)。例如,Bełżec 应该与贝尔泽奇相匹配。下面是一个配置示例:

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.ASCIIFoldingFilterFactory"/>

</analyzer>

这通常适用于索引和查询。

移除重复的令牌

在对文本执行一系列操作之后,这个链可能最终会生成重复的标记,比如执行 enrichments 或 synonym expansion,然后进行词干分析。Solr 提供了RemoveDuplicatesTokenFilterFactory实现来删除相同位置的重复标记。以下是一个配置示例:

<analyzer>

<tokenizer class="solr.WhitespaceTokenizerFactory"/>

<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>

</analyzer>

这通常在步进时应用。

多语言支持

您执行的文本分析在很大程度上取决于您需要支持的语言。您可能正在开发一个特定语言的搜索引擎,或者您可能需要支持多种语言。为了支持特定的语言,您可以根据该语言的语言学来定义所有的fieldTypes。如果需要支持多种语言,可以为每种语言定义不同的fieldTypes,并将文本复制到相应的字段中。Solr 提供的命名配置集包含各种语言的fieldType定义,这里提供了一个例子:

<!-- German -->

<fieldType name="text_de" class="solr.TextField" positionIncrementGap="100">

<analyzer>

<tokenizer class="solr.StandardTokenizerFactory"/>

<filter class="solr.LowerCaseFilterFactory"/>

<filter class="solr.StopFilterFactory" ignoreCase="true"

words="lang/stopwords_de.txt" format="snowball" />

<filter class="solr.GermanNormalizationFilterFactory"/>

<filter class="solr.GermanLightStemFilterFactory"/>

<!-- less aggressive: <filter class="solr.GermanMinimalStemFilterFactory"/> -->

<!-- more aggressive: <filter class="solr.SnowballPorterFilterFactory" language="German2"/> -->

</analyzer>

</fieldType>

前面的fieldType定义是针对德语的。可以看到 Solr 提供了特定于它的工厂。在所提供的工厂中,它为词干分析器提供了多种实现,您可以使用最适合您需求的实现。

许多语言的字母表使用发音符号,如法语脚本,来改变它们所添加的字母的发音。规范化的一个简单方法是使用ASCIIFoldingFilterFactory,我们在上一节中讨论过,将带有重音符号的单词转换为最接近的 ASCII 字符。但是这对于像阿拉伯语这样的语言不起作用,因为阿拉伯语的发音符号不能转换成 ASCII 字符。

对于某些语言,Solr 提供了特定的记号赋予器和过滤器工厂,如ElisionFilterFactory来处理适用于所选语言的省略符号。此外,一些过滤器工厂接受包含特定于该语言的内容的不同输入文件。例如,英语中的停用词与保加利亚语中的停用词不同。Solr 在conf中提供了一个lang子目录,其中包含工厂可以使用的特定于语言的文件列表。

在像英语这样的语言中,您可以很容易地根据空格来识别单词,但是有些语言(例如,日语)不使用空格来分隔单词。在这些情况下,很难确定要索引哪些标记。下面是一个句子“你要喝什么,啤酒还是咖啡?”

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

Solr 提供了CJKTokenizerFactory,它将中文、日文和韩文文本分解成记号。生成的令牌是文本流中发现的双精度、重叠的 CJK 字符对。它还为日语提供了JapaneseTokenizerFactory和其他过滤器,这些过滤器在 Solr 附带的schema.xml文件中的fieldType text_ja中定义。图 4-10 中的例子显示了text_ja对日语文本进行的分析。

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

图 4-10。

Text analysis example for Japanese language

您可以在 https://cwiki.apache.org/confluence/display/solr/Language+Analysis 查阅官方指南,了解 Solr 提供的语言特定工厂的完整列表。

无模式化

无模式模式是开始使用 Solr 的最快方式。它允许您从零模式手工制作开始 Solr,并简单地索引文档,而不用担心字段定义。在无模式模式下,你可以先跳过本章前面所读的内容。如果想要更好地控制字段定义或其他模式配置,可以使用托管模式和 REST APIs。

无模式模式本质上是一组 Solr 特性的适当打包,可以轻松设置和动态创建字段。以下是可以一起使用来支持这种动态模式的功能。

  • 自动字段类型识别
  • 自动字段创建
  • 托管模式和 REST APIs
  • 动态字段

如果您不熟悉 Solr,或者不知道文档结构或者结构经常变化,那么无模式模式非常有用。

Solr 为无模式模式捆绑了一个示例配置集。您可以通过运行 Solr 脚本来测试它,如下所示:

$ ./solr start -e schemaless

如果您想要创建一个无模式核心,您可以创建一个没有任何配置信息的核心/集合,或者指定预绑定的配置集data_driven_schema_configs。下面是一个创建无模式核心的示例:

$ ./solr create -c schemaless -d data_driven_schema_configs

$ ./solr create -c schemaless // by default uses data_driven_schema_configs

什么使 Solr Schemaless

如您所知,Solr 通过将一组特性捆绑在一起实现了无模式化。以下是可以用来实现无模式化的特性。

自动字段类型识别

每当 Solr 遇到模式中没有定义的新字段时,它都会对字段内容运行一组解析器,以识别字段类型。目前,Solr 只为原语fieldTypes提供字段类型猜测:Integer、Long、Float、Double、Boolean 和 Date。

请记住,使用动态字段的字段类型猜测是基于字段名模式的,但这里是基于字段内容的。Solr 通过提供以名称Parse*开始的更新处理器工厂来实现这个特性,它在预处理时标识字段类型。你会在第五章中读到更多关于更新处理器工厂的细节。

自动字段添加

对于未知的字段,如果 Solr 成功地猜出了fieldType,它会将该字段添加到模式中。字段添加由配置在用于字段类型猜测的处理器之后的AddSchemaFieldsUpdateProcessorFactory处理。

托管模式和 REST API

自动字段类型识别和字段添加仅限于原始字段类型。如果您想要为任何字段指定一个fieldType或者在其上定义文本分析,您可能想要添加一个字段和/或fieldType。您可以通过使用托管模式和 REST APIs 动态地完成这项工作。我们将在下一节更详细地讨论这一点。

动态字段

从早期版本开始,Solr 就支持动态字段,它支持有限但强大的无模式功能。它允许您将一个复杂的fieldType分配给一个匹配字段命名模式的新字段。

配置

本节指定了将 Solr 配置为无模式的步骤。如果您正在使用data_driven_schema_configs配置集创建一个核心,Solr 已经有了配置,您只需要开始索引文档。如果您想要手动定义或修改现有的无模式核心,请遵循以下步骤:

Define the following updateRequestProcessorChain with the update processor factories specified in sequence in solrconfig.xml. All the defined factories starting with Parse* perform the fieldType identification. The AddSchemaFieldsUpdateProcessorFactory is responsible for creating fields automatically, and the UUIDUpdateProcessorFactory generates unique identifiers for the document. The RemoveBlankFieldUpdateProcessorFactory and FieldNameMutatingUpdateProcessorFactory are for normalization of a field and its value. <updateRequestProcessorChain name="add-unknown-fields-to-the-schema">     <!-- UUIDUpdateProcessorFactory will generate an id if none is present in the incoming document -->     <processor class="solr.UUIDUpdateProcessorFactory" />     <processor class="solr.LogUpdateProcessorFactory"/>     <processor class="solr.DistributedUpdateProcessorFactory"/>     <processor class="solr.RemoveBlankFieldUpdateProcessorFactory"/>     <processor class="solr.FieldNameMutatingUpdateProcessorFactory">       <str name="pattern">[^\w-\.]</str>       <str name="replacement">_</str>     </processor>     <processor class="solr.ParseBooleanFieldUpdateProcessorFactory"/>     <processor class="solr.ParseLongFieldUpdateProcessorFactory"/>     <processor class="solr.ParseDoubleFieldUpdateProcessorFactory"/>     <processor class="solr.ParseDateFieldUpdateProcessorFactory">       <arr name="format">         <str>yyyy-MM-dd’T’HH:mm:ss.SSSZ</str>         <str>yyyy-MM-dd’T’HH:mm:ss,SSSZ</str>         <str>yyyy-MM-dd’T’HH:mm:ss.SSS</str>         <str>yyyy-MM-dd’T’HH:mm:ss,SSS</str>         <str>yyyy-MM-dd’T’HH:mm:ssZ</str>         <str>yyyy-MM-dd’T’HH:mm:ss</str>         <str>yyyy-MM-dd’T’HH:mmZ</str>         <str>yyyy-MM-dd’T’HH:mm</str>         <str>yyyy-MM-dd HH:mm:ss.SSSZ</str>         <str>yyyy-MM-dd HH:mm:ss,SSSZ</str>         <str>yyyy-MM-dd HH:mm:ss.SSS</str>         <str>yyyy-MM-dd HH:mm:ss,SSS</str>         <str>yyyy-MM-dd HH:mm:ssZ</str>         <str>yyyy-MM-dd HH:mm:ss</str>         <str>yyyy-MM-dd HH:mmZ</str>         <str>yyyy-MM-dd HH:mm</str>         <str>yyyy-MM-dd</str>       </arr>     </processor>     <processor class="solr.AddSchemaFieldsUpdateProcessorFactory">       <str name="defaultFieldType">strings</str>       <lst name="typeMapping">         <str name="valueClass">java.lang.Boolean</str>         <str name="fieldType">booleans</str>       </lst>       <lst name="typeMapping">         <str name="valueClass">java.util.Date</str>         <str name="fieldType">tdates</str>       </lst>       <lst name="typeMapping">         <str name="valueClass">java.lang.Long</str>         <str name="valueClass">java.lang.Integer</str>         <str name="fieldType">tlongs</str>       </lst>       <lst name="typeMapping">         <str name="valueClass">java.lang.Number</str>         <str name="fieldType">tdoubles</str>       </lst>     </processor>     <processor class="solr.RunUpdateProcessorFactory"/>   </updateRequestProcessorChain>   For the chain to be usable, UpdateRequestProcessorChain should be registered in the update handler that gets invoked for indexing the documents: <initParams path="/update/**">     <lst name="defaults">       <str name="update.chain">add-unknown-fields-to-the-schema</str>     </lst> </initParams>   Enable support for the managed schema in solrconfig.xml: <schemaFactory class="ManagedIndexSchemaFactory">   <bool name="mutable">true</bool>   <str name="managedSchemaResourceName">managed-schema</str> </schemaFactory>   After making the preceding changes, you are all set to index documents without worrying about whether the field is defined in schema.xml.

限制

在完全无模式模式下运行 Solr 有局限性。以下是需要注意的重要问题:

  • 自动字段添加支持一组有限的原语fieldTypes,并且不允许您应用analyzer或执行一组特定的文本分析。
  • Field Type识别可能导致错误的fieldType猜测,因为 Solr 通过处理器级联来识别fieldType。如果被索引的文档包含一个数字,Solr 将创建一个类型为TrieIntField的字段。现在,如果下一个文档包含浮点数,Solr 将无法索引该文档,因为它已经将该字段的fieldType定义为TrieIntField,并且字段一旦定义就不能自动更改。
  • 在定义一个字段时,您可能希望通过指定额外的属性(如precisionStep)来对其进行微调。一个自动的fieldType定义忽略了这个观点。

Tip

在无模式模式下运行 Solr 时,不应该手动修改模式。

用于管理模式的 REST API

在讨论无模式模式之前,我们使用schema.xml来定义模式。模式中的任何更改都需要您手动编辑schema.xml并重新加载核心。自从 Solr 诞生以来,这就是schema.xml被使用的方式。但是 Solr 的最新版本允许您通过 REST APIs 管理模式。Solr 提供 REST APIs 来读取、添加、删除和更新元素,以管理fieldfieldTypecopyFielddynamicField元素。

Solr 提供了基类IndexSchemaFactory的两个实现来管理模式。您需要在solrconfig.xmlschemaFactory元素中定义实现类:

  • ClassicIndexSchemaFactory:这是默认实现,您可以手动定义模式。schema.xml文件必须存在于conf目录中,并且应该手动修改。这个实现不支持通过 REST APIs 管理模式。
  • ManagedIndexSchemaFactory:这是通过 REST APIs 管理模式的实现。

配置

下面是使用 REST APIs 配置托管模式的步骤。

Define the schemaFactory element in solrconfig.xml and provide the implementation class as ManagedIndexSchemaFactory: <schemaFactory class="ManagedIndexSchemaFactory">   <bool name="mutable">true</bool>   <str name="managedSchemaResourceName">managed-schema</str> </schemaFactory> In Solr 5.x, if you create a core without specifying the configuration directory, it will by default use the data_driven_schema_configs configset, which creates a managed schema to support REST calls. The data_driven_schema_configs configset is provided with the Solr distribution and can be located in the $SOLR_DIST/server/solr/configsets directory. The following is the schemaFactory element in solrconfig.xml for the traditional approach to a schema definition: <schemaFactory class="ClassicIndexSchemaFactory"/>   The default file for a managed schema definition is managed-schema. You can change the filename by modifying the managedSchemaResourceName parameter. On creating a core, if the schema.xml exists but the managed-schema file doesn’t exist, in that case Solr will copy schema.xml to the managed-schema file and rename schema.xml to schema.xml.bak. Solr doesn’t allow you to change the name of managed schema file to schema.xml. The following is the property to be set for specifying the managed schema file name: <str name="managedSchemaResourceName">managed-schema</str>   If your schema is finalized, you may be interested in disallowing all future edits. To limit the REST APIs to support only read access, set the mutable Boolean parameter to false: <bool name="mutable">false</bool> It’s good to disable the mutable setting in production.

休息端点

一旦设置好managed-schema,就可以开始使用 REST API 执行读写操作了。所有写请求都会自动在内部触发核心重载,因此您不应该调用这些请求来反映这些更改。表 4-7 列出了 REST API 支持的操作、要调用的端点以及示例。

表 4-7。

REST Endpoints for Managing Schema

| 操作 | 端点和命令 | 例子 | | --- | --- | --- | | 读取模式 | `GET /core/schema` | `curl http://localhost:8983/solr/core/schema?wt=json` | | 读取字段 | `GET /core/schema/fields` `GET /core/schema/fields/fieldname` | `curl http://localhost:8983/solr/core/schema/fields?wt=json` | | 修改字段 | `POST /core/schema`命令:`add-field` `delete-field` `replace-field` | `curl -X POST -H ’Content-type:application/json’ --data-binary ’{` `"add-field":{` `"name":"title",` `"type":"text_general",` `"stored":true }` | | 读取字段类型 | `GET /core/schema/fieldtypes` `GET /core/schema/fieldtypes/name` | `curl http://localhost:8983/solr/core/schema/fieldtypes?wt=json` | | 修改字段类型 | `POST /core/schema`命令:`add-field-type` `delete-field-type` `replace-field-type` | `curl -X POST -H ’Content-type:application/json’ --data-binary ’{``"add-field-type" : {``"name":"text_ws",``"class":"solr.TextField",``"positionIncrementGap":"100",``"analyzer" : {``"tokenizer":{``"class":"solr.WhitespaceTokenizerFactory" }}}``}’ http://localhost:8983/solr/core/schema` | | 读取动态字段 | `GET /core/schema/dynamicfields` `GET /core/schema/dynamicfields/name` | `curl http://localhost:8983/solr/core/schema/dynamicfields?wt=json` | | 修改动态字段 | `POST /core/schema`命令:`add-dynamic-field` `delete-dynamic-field` `replace-dynamic-field` | `curl -X POST -H ’Content-type:application/json’ --data-binary ’{` `"add-dynamic-field":{` `"name":"*_en",` `"type":"text_general",` `"stored":true }` | | 读取副本字段 | `GET /core/schema/copyfields` | `curl http://localhost:8983/solr/core/schema/copyfields?wt=json` | | 修改副本字段 | `POST /core/schema`命令:`add-copy-field` `delete-copy-field` `replace-copy-field` | `curl -X POST -H ’Content-type:application/json’ --data-binary ’{``"add-copy-field":{``"source":"title",``"dest":[ "title_ut", "title_pn" ]}` |

其他托管资源

Solr 提供了使用 REST APIs 管理其他资源(如停用词和同义词)的条款。

使用步骤

下面是使用 REST APIs 管理停用词和同义词的步骤。

Add the implementing filter factory to the analysis chain <filter class="solr.ManagedStopFilterFactory" managed="product" /> // stopwords <filter class="solr.ManagedSynonymFilterFactory" managed="product" /> // synonyms   Assign a name to the managed resource The managed attribute assigns a name to the resource, as shown in the preceding example. This is useful if you want to support multiple stop words or synonyms, such as for the domain, feature, or language.   Access the resource over REST endpoint /core/schema/analysis/stopwords/<name> // syntax /core/schema/analysis/stopwords/product // example

关于 REST 端点的完整列表和使用信息,请参考 Solr 手册 https://cwiki.apache.org/confluence/display/solr/Managed+Resources

solrconfig.xml 文件

Solr 支持无数的特性,并允许对这些特性进行广泛的定制和控制。您可以使用solrconfig.xml来定义和配置您想要在搜索解决方案中提供的特性,并通过更改 XML 来管理它的行为和属性。在solrconfig. xml中可以配置的重要特性如下:

  • 定义请求处理程序,该处理程序指定 REST 端点和在其上公开的相应 Solr 特性。例如,/select端点的所有请求处理搜索请求,而/update端点处理索引请求。
  • 定义搜索组件、更新请求处理器和查询解析器,并将它们配置到一个处理程序或其他可以链接它们的组件。假设您想在搜索引擎中启用点击突出显示功能。您可以通过定义突出显示组件、配置参数以实现所需的行为,并最终将其链接到搜索处理程序来实现这一点。
  • 配置 Solr 核心使用的路径和文件,例如模式文件的名称。
  • 配置其他核心级属性,如缓存、包含的库、自动提交、索引合并行为和复制。

solrconfig.xml在定义和公开特性方面提供了高度的灵活性。您定义和配置 Solr 提供的组件或您通过扩展 Solr 基类开发的定制组件,然后将它们组装在一起以快速公开一个特性。

Note

没有一章专门介绍solrconfig.xml支持的定义和配置。相反,当我们讨论 Solr 提供的一个特性或搜索功能时,您将了解它们。

常见问题

本节提供了开发人员在定义schema.xml.时经常问的一些问题的答案

我如何处理异常,指示 version field 必须存在于模式中?

SolrCloud 中的乐观并发需要_version_字段,它支持实时 get 操作。Solr 希望它在schema.xml中定义如下:

<field name="_version_" type="long" indexed="true" stored="true" />

建议在schema.xml中有这个字段,但是如果你仍然想禁用它,你可以在solrconfig.xmlupdateHandler部分注释掉updateLog声明。

<!--updateLog>

<str name="dir">${solr.ulog.dir:}</str>

<int name="numVersionBuckets">${solr.ulog.numVersionBuckets:65536}</int>

</updateLog-->

为什么我的模式更改没有反映在 Solr 中?

为了反映schema.xml中的变化,应该重新加载内核。如果某个字段的索引时处理发生了任何变化,所有文档也应该重新编制索引。

我在 Solr 5.0 中创建了一个核心,但是缺少 Schema.xml。我能在哪里找到它?

默认情况下,Solr 5.0 创建的核心是无模式的。它将managed-schema用于所有的模式定义。如果您想创建一个包含用于手工制作的schema.xml的型芯,请按如下步骤操作:

solr create -c corename -d sample_techproducts_configs

摘要

良好的模式设计是构建真正有效的搜索引擎的基本步骤之一。如果您知道数据的结构,模式设计应该总是在索引过程之前。尽管随着搜索引擎的发展,您可能需要回头调整模式,但第一次就做对总是好的。

Solr 提供了一组适用于大多数情况的fieldTypes,但是我们都有自己独特的挑战,这可能需要我们配置自己的分析链。正如您所看到的,将流程配置到链上很简单,因为所有 Solr 配置都是在 XML 文件中定义的,您只需要添加新元素并适当地映射它们。

一些文本分析过程在处理时间和索引大小方面都是很昂贵的,所以应该仔细定义链。此外,无论何时对字段的索引时间处理进行更改,都必须重新编制索引才能反映这些更改。

本章主要关注设计模式和文本分析。您了解了各种分析过程、它们的用途以及可以使用它们的场景。“无模式化”将在单独的章节中介绍。

通过这一章,你可以深入到期待已久的索引和搜索概念中。下一章将介绍索引过程,接下来的章节将介绍搜索和高级功能。

五、索引数据

如您所知,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 | `curlhttp://localhost:8983/solr/hellosolr/queryjson="query":"bobmarley","limit":20|json||| curl``http://localhost:8983/solr/hellosolr/query``?json=’{``"query":"bob marley"` | 查询使用 JSON API 和请求参数的组合。 | | JSON 中的请求参数 | `curlhttp://localhost:8983/solr/hellosolr/querydparams:q:"bobmarley",rows:20|使JSONparamsparamsJSON||| 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 评分。在本章结束时,您将理解一个文档如何在结果集中的特定位置排名,以及为什么一个文档出现在另一个文档之前或之后。

八、Solr 排名

在评估搜索响应时,您可能想知道为什么某个特定文档的排名高于另一个文档,以及这些文档是如何获得分数的。在本章中,您将了解 Solr 排名。

本章从默认的排名实现和控制分数的因素开始,逐步深入到更多的细节,以便您可以覆盖现有的排名因素或编写自定义排名器。即使计分是一个高级的话题,并且这一章涵盖了一些数学公式,你也不需要成为一个数学奇才来理解它。

我本可以在第三章中对排名做一个简单的介绍,但是我想深入了解更多细节,超越基本概念,并在实现层面讨论 Solr 排名,这需要一个专门的章节。

Note

在第三章中,你学习了信息检索概念;如果你跳过了那一章,我建议你在继续这一章之前先读一读,尽管这不是强制性的。

本章涵盖以下主题:

  • Solr 如何对文档进行排序
  • 默认排名模型和因素
  • 支持替代型号
  • 诊断文档分数
  • 自定义排名

Solr 排名简介

相关性排名是任何搜索引擎的核心,Solr 也是如此。为了对用户查询的文档进行排序,Solr 根据模型的算法计算每个匹配文档的分数,并根据它们的相对分数对它们进行排序。它将得分最高的文档排在结果集的最前面。

最初,Lucene 只支持基于 TF-IDF 的向量空间模型来对文档进行排序,但是后来的 4.0 版本引入了额外的模型来支持更灵活的排序。这些模型都扩展了 Lucene 的抽象类Similarity来实现算法。现有的基于 TF-IDF 的模型,在DefaultSimilarity类中实现,仍然是排名的默认模型。在本章中,您将了解这些模型。

大多数 Solr 设置使用DefaultSimilarity,这对于大多数需求来说都很好。我建议您不要担心改变模型,继续使用默认的相似性,除非您真正理解为什么您需要一个替代模型或定制实现。

在 90%的情况下,您将能够通过在索引或查询时应用不同的调优方法来操纵和控制文档的排名,而无需触及Similarity类。这些方法以这样或那样的方式影响着文档的得分,对其进行适当的调整应该会带来预期的结果。综上所述,以下是控制文档排序的方法:

  • 查询语法、运算符和其他组合,如短语、slop 和模糊匹配
  • 查询解析器及其配置
  • 文本分析
  • 索引时提升文档和字段
  • 查询时的术语提升
  • 函数查询

如果这些定制得到了想要的结果,您应该避免改变排名模型,应该只在真正需要的时候操作它。我建议您清楚地了解您正在对排名进行的更改,并彻底测试它们。

即使您没有改变排名模型的计划,了解它们仍然很重要,这样您就可以了解前面的因素如何影响最终得分,并且可以衡量任何调整对您的文档排名的影响。

您可能有一个业务案例,您觉得调整这些参数没有帮助,并且您的排名要求与 Solr 提供的默认实现不同。在这种情况下,您可以执行以下操作:

  • 使用 Lucene 提供的另一个Similarity实现,并根据您的需求进行调整。
  • 扩展默认相似性以操纵其排名因子。
  • 编写您的自定义实现。这将需要你对你的排名需求和你计划使用的算法有一个专家级的理解。你需要小心,因为这可能会让你陷入一个循环迭代,而排名没有任何显著的提高。

在您进一步了解之前,以下是关于 Solr 排名的一些重要事实,将有助于您避免任何假设:

  • 无负分:分数始终是大于 0.0 的浮点数,任何文档都不能有负分。

  • 分数是相对的:对于一个给定的查询,文档的分数是相对的,可以比较。多个文档可以分别具有与所有排名因素相同的分数,或者它们的分数相加可以生成相同的值。如果需要标准化的分数,可以将每个文档的分数除以查询的最高分数。

  • 每场计算:分数是按场计算的。汇总每个字段的单独分数,以计算文档的最终分数。

  • 可插入:Solr 允许您扩展可用的Similarity实现,以及插入您自己的排名算法。

  • Lucene:Solr 中的排名实现是由 Lucene 提供的。org.apache.lucene.search.similarities包包含所有实现各种排名模型的Similarity类。为了连接您的定制实现,您可以将您的类打包在一个单独的 JAR 中,并将其添加到 Solr 类路径中,而不必担心更改 Solr 或 Lucene 包。

  • 按相关性排序:默认情况下,搜索响应按分数降序排序,顶部的文档被标识为最佳匹配。

  • Maximum score: No upper boundary has been specified for the score of documents. The maximum value is based on several scoring factors, including boosts, which combined together can lead to a very high score. The maxScore parameter in a search response returns the maximum score for that query. Figure 8-1 shows an example that marks the maxScore for a query.

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

    图 8-1。

    Maximum score for a user query

Note

术语相关性排名、Solr 排名和相似性可以互换使用。相反,术语排名模型指的是用于实现相似性的算法。

默认排名

Lucene 的默认排名方法是它如此受欢迎的原因。它速度快,经过优化,易于理解和使用,对于大多数用例都很实用。这是一种(几乎)一刀切的方法。

默认的 Lucene 排名是基于布尔模型(BM)和向量空间模型(VSM)的组合。对于给定的查询,并不是对语料库中的所有文档进行排序,而是只对满足布尔条件(使用布尔运算符指定)的文档子集进行排序。这些缩小范围的文档还通过避免 VSM 对不相关的文档进行不必要的排序来减少处理时间。简单来说,BM 批准的文件由 VSM 排名。搜索响应中的numFound参数指定 BM 批准的文档数量。图 8-2 标记了numFound参数,表示匹配文件的数量。

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

图 8-2。

Number of matching documents

履行

Lucene 中的默认排名是由DefaultSimilarity类实现的,它扩展了TFIDFSimilarity类。DefaultSimilarity类派生出一种基于 TF-IDF 排名的高级形式,用于实际的分数计算。在下一节中,您将了解 TF、IDF 和其他违约排名因素。

solrconfig.xml或者schema.xml中不需要额外的定义,Solr 从用户那里抽象出所有的实现细节。如果你想明确指定这种相似性,尽管这没有意义,你可以在schema.xml中这样做,如下所示:

<schema>

..

<similarity class="solr.DefaultSimilarity" />

</schema>

得分因素

本节涵盖影响文档分数的因素。一旦您理解了这些因素,您将了解到将这些因素标准化并将它们组合起来得出汇总分数的公式。对因子的理解足以通过使用诸如 boosting 之类的规定来调整分数,而对公式的理解相对来说不那么重要。

Note

术语权重是在每个字段的基础上计算的。为了计算某个字段的术语分数,排名因子使用该特定字段的统计数据。该术语的最终权重是通过汇总其适用的所有字段的分数来计算的。

以下是决定文档得分的主要因素:

  • 术语频率(TF):这是一个术语在文档字段中出现的次数。文档的得分与它的术语频率成正比:术语在文档中的计数越高,它的得分就越高。该模型假设,如果一个术语在文档中出现的次数越多,它就越与用户相关。
  • 逆文档频率(IDF):此因子根据术语出现的文档数量计算得分。它与计数相反:计数越高,权重越低。这个想法是给予更少的单词更多的重要性。请注意,文档计数是针对该特定字段进行的,不考虑该术语在其他字段中的存在。

在前面的章节中,您使用了停用字词表来过滤掉不重要的术语。stopFilterFactory直接丢弃列表中的术语,对该术语的任何查询都不会得到匹配。但是有些术语不如其他术语重要,而且无论如何也不能被过滤掉。IDF 排名因子通过降低权重来处理如此频繁出现的术语。例如,在医学领域,诸如患者、结果、治疗、蛋白质和疾病等术语是常见的且重要性较低,而诸如肿瘤、帕金森病和囊肿等术语相对较少且重要性较高。IDF 作为一个因素在全文搜索中对适当地加权这些术语是至关重要的。

  • 字段长度(lengthNorm):该因子考虑字段中标记的数量,用于确定文档的重要性。字段越短(索引的标记数量越少),文档越重要。length norm 背后的想法是确保一个术语在短字段(如title)中的出现比在长字段(如abstract)中的出现更重要。
  • 协调因子(coord):该因子基于查询和文档之间的术语重叠来排名。这意味着包含大多数查询词的文档更重要,应该得到奖励。
  • 提升:文档和字段可以在索引时提升,术语可以在查询时提升。通过给予更高的提升,您指定了一个特定的术语或一组术语更重要,应该得到更高的分数。这是您可以直接轻松地调整特定文档分数的因素。

Note

QueryNorm是一个额外的因素,但此处未指定,因为它不影响两个文档之间的得分。相反,它使两个查询之间的分数正常化。你会在讨论 Lucene 排名公式的时候了解到QueryNorm

排名公式

前面讨论的默认相似性因子不使用计数来计算文档分数。原始值被标准化以获得最有用和最平衡的值,该值为计算文档的实际分数提供了最佳权重。根据需要和信息可用性,Solr 在查询时或索引时对这些因素进行规范化,其值基于一个字段、一个文档或整个集合的统计数据。

在 VSM 中,查询和文档被表示为多维空间中的加权向量,其中每个术语是一个维度,TF-IDF 值构成权重。VSM 使用余弦相似度来计算文档的分数。如果你觉得这个数学概念太难消化,你可以暂时跳过这句话。如果你感兴趣或者你想重温一下基本概念,请参考第三章了解 VSM 的详细信息。

如前所述,Lucene 使用 TF-IDF 的高级形式来计算实际分数。这种高级形式提供了增强、长度标准化和协调因子,以及术语频率和逆文档频率。在 Lucene 中使用 TF-IDF 的高级形式计算得分的公式如下:

$$ score\left(q,d\right)= coord\left(q,d\right)\kern0.5em .\kern0.5em queryNorm(q)\kern0.5em .{\displaystyle \sum_{t\ in\ q}}\left(tf\left(t\ in\ d\right)\kern0.5em .\kern0.5em idf(t)\kern0.5em .\kern0.5em t. getBoost\left(\right)\kern0.5em .\kern0.5em norm\left(t,d\right)\ \right) $$

这个得分公式包含六个函数。每个函数负责计算特定得分因子的标准化值。如等式中所述,这些函数的输出被组合,以生成文档的最终得分。

接下来描述每个函数及其与 Lucene 排名因子的关系。为了便于理解,这些函数是在假设索引只有一个可搜索字段的情况下定义的。

tf(t 在 d 中)

如前所述,TF 是频率一词的缩写。这个函数统计每个查询词在文档中出现的次数。一个文档的得分和它的出现频率成正比。术语出现的频率越高,得分越高。Lucene 通过计算频率的平方根来归一化 TF 权重:

$$ tf\left(t\ in\ d\right) = frequenc{y}^{1/2} $$

以色列国防军(t)

如你所知,IDF 是逆文档频率的缩写。该函数通过计算出现该术语的文档数量的倒数来计算分数。Lucene 通过取反数值的对数来规格化该值:

$$ idf(t)=1+ log\left(\frac{numDocs}{docFreq+1}\right) $$

docFreq和对数值加上数字常数 1,以避免分别出现numDocs/0log(0)这样的未定义值。

坐标(q,d)

coord代表配位因子。该函数根据文档中重叠的查询词的数量来计算分数。使用此公式计算协调因子:

$$ coord\left(q,d\right)=\frac{overlap}{maxOverlap} $$

queryNorm(q)

在讨论文档排序因素时,我们跳过了这个函数。queryNorm的目的是标准化查询之间的分数——与标准化文档之间的分数的其他因素相反。这个函数使得两个查询之间的分数具有可比性。相同的queryNorm值应用于一个查询的所有文档,因此它不会直接影响文档的得分。查询词的queryNorm计算如下:

$$ queryNorm(q)=\frac{1}{sumOfSquaredWeight{s}^{1/2}} $$

其中sumOfSquaredWeights使用以下公式计算:

$$ sumOfSquaredWeights=q. getBoost{\left(\right)}²\kern0.5em .{\displaystyle \sum_{t\ in\ q}}{\left(idf(t)\ .\kern0.5em t. getBoost\left(\right)\right)}² $$

t.getBoost()

这个函数获取查询时应用的术语 boost,比如查询adidas³ shoes。值得注意的是,这个函数只考虑查询时的提升,不考虑索引时应用的字段或文档提升。术语 boost 直接影响文档的得分。如果查询词不包含任何提升,则隐式应用默认值 1.0。

范数(t,d)

该函数是两个因素的产物,即lengthNorm和索引时间提升。lengthNorm考虑字段的长度,以确定文档的重要性。字段越短(索引的标记数量越少),文档越重要。索引时提升是在索引文档时应用于文档或字段的提升。计算范数的公式如下:

$$ norm\left(t,d\right)= lengthNorm\kern0.5em .\kern0.5em {\displaystyle \prod }\ f. boost\left(\right) $$

Similarity类在索引文档时计算范数。如果您应用一个替代模型(一个Similarity实现),文档应该被重新索引。

归一化因子(或norm)是在索引文档时计算的。关于norm的一个有趣的事情是,计算出的范数值,也就是float,被编码成一个字节进行存储。在查询时,从索引中读取norm字节值,并解码回float。编码/解码的目的是支持有效的内存利用和减少索引大小,但这是以一些精度损失为代价的。

如果在字段定义中设置omitNorms="true"时省略了某个字段的规范,则该字段的norm值将被忽略。

编制索引时,如果任何文档有多个同名字段,则其所有提升将相乘,以计算该字段的总提升。

Note

默认相似度详见 https://lucene.apache.org/core/5_3_1/core/org/apache/lucene/search/similarities/DefaultSimilarity.html

限制

基于 TF-IDF 的 VSM 是一个简单而强大的模型,适用于大多数情况,尤其是全文搜索,但它有一些局限性。

在高层次上,该模型基于两个主要概念:

  • 识别查询中最感兴趣的术语及其权重
  • 基于加权术语查找最相关的文档

基于 TF-IDF 的模型认为稀有术语更有趣,但情况并非总是如此。例如,在音乐元数据的搜索引擎中,诸如 love 和 heart 之类的术语很常见,因为许多歌曲标题都包含这些词,但它们的重要性丝毫不减。如果您有类似的情况,您可能希望关闭 IDF 作为一个得分因素,也许在该领域。在本章的后面,你会看到一些覆盖默认因子的示例代码。

基础 VSM 的另一个限制是,它假设术语是独立的(术语之间没有关系),但实际上术语是相关的。例如,对于像信用卡或纸袋这样的词,在 VSM 就失去了这种关系。

解释查询

Solr 搜索参数debugQuery=true解释了结果集中每个文档的得分计算。搜索响应中的附加元素explain包含每个文档的一个条目(映射到它的uniqueId),并提供由函数返回的分数的描述及其聚合,以获得每个查询词的分数。不同的字段可以为相同的查询词返回不同的分数,再次汇总这些分数以计算该词的最终分数。这个词得分再次与其他查询词的得分(如果有的话)相加,以返回文档的最终得分。

为了更好地理解查询解释,您将对一小组文档进行索引,并尝试理解一个示例查询的得分计算:

Create a small set of sample documents. The following is a sample in CSV format: id,title,cat,units,popularity 1,Apple iPhone,phone,100,9 2,Apple iMac,desktop,9,8 3,Apple MacBook Pro laptop,laptop,30,10 4,Lenovo laptop,laptop,40,7 5,Asus Laptop,laptop,60,8 6,HP Laptop,laptop,50,7   Index the documents. $ curl http://localhost:8983/solr/hellosolr/update/csv?commit=true --data-binary @explain-sample.csv -H ’Content-Type:text/plain; charset=utf-8’   Query with the additional parameter debugQuery=true. $ curl http://localhost:8983/solr/hellosolr/select? q=apple+laptop&fl=*,score&wt=xml&indent=true&debugQuery=true &defType=edismax&qf=title+cat&lowercaseOperators=true

图 8-3 包含 Solr 返回的查询解释的快照。以下是一些需要注意的要点:

  • 用于每个术语的分数计算的Similarity类在方括号中指定。图 8-3 中用下划线标出了一个例子。
  • 每个函数返回的分数与产生分数的输入值一起指定。
  • 每个函数的单独分数被合计,以获得包含该令牌的所有字段上的术语的最终分数。在图 8-3 中,术语膝上型电脑在两个字段titlecat中匹配,它们的分数被分别计算,标注为 d 和 e
  • 对一个学期来说,它所有的领域分数相加得到一个最终分数。汇总字段分数取决于查询解析器。eDisMax 取最大值,在图 8-3 中标记为 c。
  • 最后将每个标记的汇总分数相加,得出文档的最终分数。在图 8-3 中,注释 a 标注的是最终分数,是学期分数 b 和 c 的总和。
  • 相同的queryNorm适用于所有条款。

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

图 8-3。

Explain query

替代排名模型

除了默认的基于 VSM 的排名,Lucene 4.0 引入了新的排名模型。这些模型为 Solr 提供的排名可能性带来了额外的灵活性。

大多数基于 Solr 和 Lucene 的应用程序仍然依赖默认排名。如果您可以通过调整 TF-IDF 权重或给予适当的提升来获得想要的结果,或者如果数学计算的想法吓到了您,您可以继续使用默认算法。这些替代模型需要一定的数学理解。

org.apache.lucene.search.similarities.*包包含了计算文档相关性等级的所有类。抽象类Similarity是基类,和DefaultSimilarity一样,这些模型的实现也扩展了它。

如果您计划评估这些备选模型中的任何一个,首先要理解底层算法。网上有很多研究论文提供了算法的详细描述。选择最符合您需求的模型,然后对其进行评估,看它是否满足这些需求。您可以使用模型创建单独的索引并比较结果,或者运行 A/B 测试。

默认的相似性不需要任何额外的参数,但是这些替代模型允许您通过使用额外的参数来控制算法的行为。

Note

文档应重新编制索引,以充分利用备选排名模型的潜力,因为在编制索引时会计算标准。

这一节涵盖了 Lucene 支持的两种主要的排名方案。

bm25 相似性

BM25Similarity 是 Lucene 提供的备选实现中使用最广泛的排序算法。这种相似性基于 Okapi BM25 算法,其中 BM 代表最佳匹配,是一种概率检索模型。对于包含小文档的索引,它可以产生比基于 TF-IDF 的排序更好的结果。

该模型类似于 VSM,在某种意义上,它采用词袋方法,并考虑术语频率和逆文档频率来计算和总结得分。但是它有一些显著的不同。这些算法之间最重要的两个区别如下:。

  • Field length (lengthNorm): BM25 considers the fact that longer documents have more terms, and so the possibility of higher term frequency is also more. As a term can have a high frequency due to the long length and might not be more relevant to the user query, this similarity applies an additional parameter, b, that normalizes the term frequency due to this possibility. Table 8-1 provides more details of this parameter.

    表 8-1。

    BM25SimilarityFactory Configuration Parameters

    | 参数 | 类型 | 描述 | | --- | --- | --- | | `k1` | 浮动 | 该可选参数控制术语频率的饱和度。默认值为 1.2。较高的值会延迟饱和,而较低的值会导致过早饱和。 | | `b` | 浮动 | 此可选参数控制文档长度在规范化术语频率方面的影响程度。默认值为 0.75。较高的值会增加规范化的效果,而较低的值会降低其效果。 | | `discountOverlaps` | 布尔代数学体系的 | 此可选参数确定在基于文档长度计算范数时是否应忽略重叠标记(位置增量为 0 的标记)。默认值为`true`,忽略重叠的令牌。 |
  • Saturation: In the VSM, the normalized term frequency of a document grows linearly with the growing term count and has no upper limit. In the BM25-based model, the normalized value grows nonlinearly, and after a point does not grow with the growing term count. This point is the saturation point for term frequency in BM25. Figure 8-4 shows a graph comparing term frequency for DefaultSimilarity (which is VSM based) and BM25Similarity (which is BM25 based).

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

    图 8-4。

    Term frequency in DefaultSimilarity vs. BM25Similarity In BM25Similarity, if the term frequency goes beyond a threshold, the increasing count will have no additional effect on the score of the document, and the score of VSM will be more than that of BM25.

以下是使用 bm25 相似度计算分数的步骤:

Register the BM25SimilarityFactory in schema.xml by using the similarity element. Globally, only one similarity class should be defined. If no similarity definition is available in schema.xml, by default DefaultSimilarity is used. <schema>   ..   <similarity class="solr.BM25SimilarityFactory" /> </schema>   Specify the configuration parameters. Table 8-1 describes the parameters supported by BM25SimilarityFactory. Here is a sample configuration: <similarity class="solr.BM25SimilarityFactory">   <float name="k1">1.2</float>   <float name="b">0.75</float> </similarity> There is no standard rule that specifies the ideal value for the parameters k1 and b. The optimal combination of k1 and b is to be computed by experimenting on the dataset.   Reindex the documents.

相似性

DFRSimilarity 实现了与随机性模型的差异,随机性模型是一种信息检索的概率模型。该模型通过测量实际术语分布和通过随机过程获得的术语分布之间的差异来计算术语权重。

通过该模型的早期形式观察到,信息项的分布不同于非信息项的分布。信息术语在一些文档中出现得更密集,称为精英文档,而非信息词随机分布在所有文档中。

这个框架由三个模型组成,在schema.xml中注册工厂时需要配置。这三个模型将在接下来的小节中介绍。

基本模型

这个模型选择了基本的随机性模型。该框架支持七个基本模型,每个模型使用不同的排名算法。表 8-2 提供了支持型号的详细信息。这个步骤之后可以是两个归一化步骤,这增加了算法的灵活性。

表 8-2。

Basic Models Supported by Divergence from Randomness

| 基本模型 | 价值 | 描述 | | --- | --- | --- | | `BasicModelBE` | `Be` | 实现了玻色-爱因斯坦模型的极限形式。在某些情况下,这可能会导致性能问题。BasicModelG 是一个更好的选择。 | | `BasicModelG` | `G` | 实现了玻色-爱因斯坦模型的几何近似。 | | `BasicModelP` | `P` | 实现二项式模型的泊松近似。 | | `BasicModelD` | `D` | 实现二项式模型的散度近似。 | | `BasicModelIn` | `I(n)` | 考虑计算随机性的逆文档频率。 | | `BasicModelIne` | `I(ne)` | 通过使用泊松和逆文档频率的组合来计算随机性。 | | `BasicModelIF` | `I(F)` | 考虑逆文档频率的近似值来计算随机性。 |

后效模型

这个模型也被称为第一归一化模型。它平滑从基本模型获得的权重。表 8-3 提供了支持的后效模型的详细信息。

表 8-3。

AfterEffect Supported by Divergence from Randomness

| 后果 | 价值 | 描述 | | --- | --- | --- | | `AfterEffectL` | `L` | 运用拉普拉斯定律。 | | `AfterEffectB` | `B` | 基于两个伯努利过程比值的信息增益模型。 | | `NoAfterEffect` | `none` | 此参数禁用第一次规范化。 |

正常化

这个模型也被称为第二归一化模型。它对算法用来标准化术语频率的字段长度进行标准化。表 8-4 提供了支持的标准化模型的详细信息。

表 8-4。

Second Normalization Supported by Divergence from Randomness

| 正常化 | 价值 | 描述 | | --- | --- | --- | | `NormalizationH1` | `H1` | 假设频率项是均匀分布的。 | | `NormalizationH2` | `H2` | 在这个模型中,术语频率与场长度成反比。 | | `NormalizationH3` | `H3` | 实现由 Dirichlet 先验提供的术语频率归一化。 | | `NormalizationZ` | `Z` | 实现由 Pareto-Zipf 规范化提供的术语频率规范化。 | | `NoNormalization` | `none` | 禁用第二次规范化。 |

使用

以下是使用 DFRSimilarity 计算文档得分的步骤:

Register the DFRSimilarityFactory in schema.xml by using the similarity element. The basicModel, afterEffect, and normalization parameters are mandatory, and the value of the desired class should be provided for each of these parameters. Tables 8-2, 8-3, and 8-4 provide options for basicModel, afterEffect, and normalization, respectively. The following is a sample schema.xml configuration: <schema>   ..   <similarity class="solr.DFRSimilarityFactory">     <str name="basicModel">P</str>     <str name="afterEffect">L</str>     <str name="normalization">H2</str>     <float name="c">7</float>     <bool name="discountOverlaps">true</bool>   </similarity> </schema> DFRSimilarityFactory supports the optional parameter c to allow normalization, which controls the behavior of the implementations NormalizationH1 and NormalizationH2.   Reindex the documents.   Note

参见 http://terrier.org/docs/v3.5/dfr_description.html 了解随机性模型偏离的全部细节。

其他相似性度量

除了前面讨论的算法,Lucene 还支持其他一些算法。每个都可以通过在一个similarity元素中注册它的工厂来配置,就像前面的例子一样。以下是 Lucene 支持的其他相似之处:

每场相似度

您已经了解了 Lucene 中几种可用的相似性选择,以及如何在全局范围内实现它们(无论您选择哪种实现,都适用于所有领域)。Lucene 和 Solr 允许你对不同的字段使用不同的Similarity实现。

从行为角度来看,全局相似性将应用于所有字段,除非定义了特定于字段的相似性来覆盖该字段的默认相似性。以下是配置每个字段相似性的步骤:

Define solr.SchemaSimilarityFactory as the global similarity class that delegates similarity if there is a field-specific similarity definition. Here is an example: <schema>   ..   <similarity class="solr.SchemaSimilarityFactory"> </schema> SchemaSimilarityFactory specifies the DefaultSimilarityFactory as an implicit global similarity, and any field-specific definition will apply the overriding similarity on that field.   The schema.xml can have only one global similarity factory. If any other Similarity implementation is defined, that should be commented out. <!--similarity class="solr.BM25SimilarityFactory"-->   To override the global similarity for specific field, specify the applicable similarity factory in the fieldType definition of that field. The following is a sample definition of DFRSimilarity on text_general fieldType. <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">   <analyzer type="index">     <tokenizer class="solr.StandardTokenizerFactory"/>     <filter class="solr.StopFilterFactory" ignoreCase="true"       words="stopwords.txt"/>     <filter class="solr.LowerCaseFilterFactory"/>   </analyzer>   <analyzer type="query">     <tokenizer class="solr.StandardTokenizerFactory"/>     <filter class="solr.StopFilterFactory" ignoreCase="true"       words="stopwords.txt"/>     <filter class="solr.SynonymFilterFactory"       synonyms="synonyms.txt" ignoreCase="true" expand="true"/>     <filter class="solr.LowerCaseFilterFactory"/>   </analyzer>   <similarity class="solr.DFRSimilarityFactory">     <str name="basicModel">I(F)</str>     <str name="afterEffect">B</str>     <str name="normalization">H2</str>   </similarity> </fieldType>   Note

到目前为止,coordqueryNorm还没有作为SchemaSimilarityFactory的一部分实现,所以你会得到 TF-IDF 不同的分数。

自定义相似性

Solr 允许您定制Similarity实现。如果您想要调整现有相似性的行为,您可以扩展Similarity实现并覆盖一个方法来插入您想要的计算。

假设您正在开发一个音乐元数据的搜索引擎,并且您发现诸如 love 和 heart 这样的术语很常见,但是仍然和其他不常见的术语一样有趣。如果您使用默认相似性,IDF 因子会认为稀有术语更重要,并赋予它们更高的权重。但在这种情况下并非如此,您想要禁用 IDF 权重。您可以使用以下步骤自定义此默认行为:

Extend the existing Similarity implementation provided by Lucene. import org.apache.lucene.search.similarities.DefaultSimilarity; public class NoIDFSimilarity extends DefaultSimilarity {   @Override   public float idf(long docFreq, long numDocs) {     return 1.0f;   } } In this class, you have overridden the idf() method to always return a value of 1.0, which disables the role of IDF in computing the score of a document. If you want to provide your custom computation formula, you can put your code in the method and return the computed value. In DefaultSimilarity, the method computes the IDF score as follows:   public float idf(long docFreq, long numDocs) {     return (float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0);   }   Add the Java executable JAR of the project containing the class to the classpath. <lib dir="../../../custom-lib" regex="solr-practical-approach-\d.*\.jar" />   Register the custom similarity in schema.xml either globally or on the desired fieldType. The following example configures the custom similarity on a specific field. <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">   <analyzer     <tokenizer class="solr.StandardTokenizerFactory"/>   </analyzer>   <similarity class="com.apress.solr.pa.chapter08.similarity.NoIDFSimilarity"/> </fieldType> <similarity class="solr.SchemaSimilarityFactory"> Generally, you want to apply the custom implementation at the field level, as the default similarity works well for most cases and your implementation would address the requirement of a specific field.   For the preceding example, it’s not necessary to rebuild the index. But it’s advisable that you reindex the documents after changing the Similarity implementation.   Query for the result. Figure 8-5 contains a snapshot of the ranking explanation. You can see that NoIDFSimilarity (underlined) is used as the Similarity implementation for computing the score. You also can see that all IDF computation returns a constant score of 1.0. This constant value disables the default IDF implementation applied while computing the term weight.

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

图 8-5。

Explanation for custom similarity

前面的例子有一个限制:您可以调整现有的因子来计算相似性,但是不能引入一个新的因子。如果你想插入一个全新的相似性算法,或者是基于一篇研究论文,或者是你自己开发的东西,你需要做更多的工作,而不是扩展现有的Similarity实现。首先,您需要扩展SimilarityFactory并实现getSimilarity()方法来返回您的自定义相似性,这扩展了抽象的Similarity类。

摘要

在这一章中,你看到了 Lucene 是如何增强 Solr 的相关性排名的,Lucene 支持的各种相关性模型,以及主要模型的细节。您看到了 Lucene 的默认排名简单而强大,这也是 Lucene 更受欢迎的原因。您了解了主要模型中影响文档得分的因素。您还了解了文档分数的解释,这样您就可以理解为什么一个文档会排在另一个文档之上,或者出现在结果集的顶部。您还看到了覆盖默认相似性的示例代码,以自定义排名因素的行为。

九、附加功能

开发搜索引擎的目的是帮助用户以最方便的方式找到最相关的信息。在第六章和 7 中,您了解了检索文档的各种方法以及 Solr 提供的实现。

在本章中,您将了解 Solr 的其他重要特性,这些特性将带来便利并增加用户体验。您还将看到可以用来控制文档排名和向用户推荐其他感兴趣的文档的其他功能。

本章涵盖以下主题:

  • 赞助搜索
  • 拼写检查
  • 自我暗示
  • 文档相似度

赞助搜索

对于给定的查询,您可能希望将一组精选的文档提升到搜索结果的顶部。你通常需要这样一个特性,要么允许赞助搜索,要么支持编辑提升。Google AdWords 是赞助搜索的一个典型例子。此外,有时一个相关的文档可能在响应中排名靠后,或者一个不相关的文档可能排名靠后,您将需要一个快速的解决方案来修复这些问题,尤其是当它们在生产中被发现时。

Solr 提供了QueryElevationComponent作为快速简单的解决方案来满足这些需求。对于指定的查询,它支持以下行为,而不考虑文档的分数:

  • 将所需的一组文档置于搜索结果的顶部
  • 从搜索结果中排除一组文档

Note

QueryElevationComponent要求在schema.xml中定义uniqueKey字段。它也适用于分布式环境。

使用

以下是使用查询提升组件的步骤:

Define the elevation file. This file contains the rules for elevating and excluding documents. The filename must map to the name provided in the config-file parameter in QueryElevationComponent. The following is a sample query elevation file. elevate.xml <elevate> <query text="dslr camera">   <doc id="101" />   <doc id="103" /> </query> <query text="laptop">    <doc id="106" />    <doc id="108" exclude="true" /> </query> </elevate> The text attribute of the query element specifies the query to be editorially boosted. The doc element represents the documents to be manipulated. Its id attribute marks the document that should be elevated, and if the additional attribute exclude="true" is specified, the document is marked for exclusion. The provided id should map to the uniqueKey of a document. The elevation file must exist either in the $SOLR_HOME/<core>/conf/ or $SOLR_HOME/<core>/data directory. If it exists in conf, modifications to the file will reflect on core reload; and if it exists in data, modifications will reflect for each IndexReader. Note The component first looks for elevate.xml in the conf directory (or ZooKeeper, if applicable) and then in data. If you have the file in both directories, conf will get the priority.   Configure the QueryElevationComponent in solrconfig.xml. Table 9-1 describes the arguments supported to control the behavior of the component. The following is an example configuration.

表 9-1。

QueryElevationComponent Parameters

| 参数 | 描述 | | --- | --- | | `config-file` | 指定包含查询提升规则的文件的名称。 | | `queryFieldType` | 指定应该用于分析用户查询的`fieldType`。分析的术语与提升文件中定义的查询相匹配。指定的`fieldType`必须存在于`schema.xml`中。如果不想进行分析,将`string`指定为`queryFieldType`。 | | `forceElevation` | 查询提升根据配置从结果集中引入或删除文档,但考虑排序。如果应用除`score desc`之外的任何排序,提升的文档将根据排序条件改变它们的顺序。将该参数设置为`true`会覆盖该行为。 | | `editorialMarkerFieldName` | 该参数有助于区分提升的文档和有机排序的文档。提升的文档获得一个具有此名称的附加字段。默认名称是`editorial`。当分配的名称被添加到`fl`请求参数时,标记被启用。进一步提供了使用这种标记的例子。 | | `markExcludes` | 默认情况下,排除的文档会从搜索结果中删除。将该参数设置为`true`会将此类文档标记为已排除,而不是将其全部删除。 | | `excludeMarkerFieldName` | 此参数为排除的文档分配一个字段名。它仅适用于`markExcludes`参数。分配的默认名称是`excluded`。 |

<searchComponent name="elevator" class="solr.QueryElevationComponent" > <str name="config-file">elevate.xml</str> <str name="queryFieldType">string</str> </searchComponent>   Register the component to the last-``components list of the desired handler. <requestHandler name="/select" class="solr.SearchHandler"> <lst name="defaults"> <str name="echoParams">explicit</str> </lst> <arr name="last-components"> <str>elevator</str> </arr> </requestHandler>   Provide additional request parameters for the component. Table 9-2 lists the additional parameters supported.

表 9-2。

QueryElevationComponent Request Parameters

| 参数 | 描述 | | --- | --- | | `enableElevation` | 该参数启用`QueryElevationComponent`。 | | `exclusive` | 如果启用此布尔参数,则仅返回提升的结果。有机结果将被忽略。 | | `elevateIds` | 此参数指定要提升的文档 id 的逗号分隔列表。 | | `excludeIds` | 此参数指定要排除的文档 id 的逗号分隔列表。 |

Query for results. The following are some sample queries. Search request with QueryElevationComponent enabled $ curl " http://localhost:8983/solr/hellosolr/select?q=laptop&enableElevation=true " Search request that marks the elevated and excluded field $ curl " http://localhost:8983/solr/hellosolr/select?q=laptop &markExcludes=true&fl=*,[elevated],[excluded]" Search request to mark the elevated field, if editorialMarkerFieldName is specified as paid $ curl " http://localhost:8983/solr/hellosolr/select?q=laptop &enableElevation=true&fl=*,[paid],score" Search request that specifies the elevation and exclusion IDs $ curl " http://localhost:8983/solr/hellosolr/select?q=cable &df=product&excludeIds=IW-02&elevateIds=3007WFP,9885A004"

拼写检查

用户查询容易出错,所以几乎所有流行的搜索引擎都支持拼写检查功能。对于拼写错误的查询,此功能会建议文本的正确形式。您通常会在搜索框的正下方看到建议。例如,去 Amazon.com 并搜索 laptap(laptop 的拼写错误形式),响应将包含额外的信息,如您的意思是:laptop。

搜索引擎采用两种方法进行拼写检查。他们要么执行原始查询并提供拼写建议,要么执行拼写纠正的查询,并选择运行原始查询。网络搜索引擎,如谷歌、必应和雅虎!采取乐观的拼写纠正方法。它们检索正确形式的查询的结果,并为用户提供执行原始查询的选项。

图 9-1 描述了一个典型的拼写检查示例(来自 Google ),其中拼写错误的查询 wikipedia 被纠正为 Wikipedia,并为其提供了一个结果。

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

图 9-1。

Example of spell-checking in Google

Solr 为拼写检查提供了现成的支持。它允许您根据 Solr 字段、外部 Lucene 索引或外部文本文件中维护的术语提供建议。Solr 提供了SpellCheckComponent,它扩展了SearchComponent来实现这个特性,并且可以配置到任何SearchHandler来让它工作。

如果你只是想给用户提供一个拼写建议,比如问“你的意思是?”,将SpellCheckComponent配置到查询处理程序,例如/select,并在适当的位置显示该组件的响应,以供用户操作。如果您的目的是向用户提供一个自动修正的结果,那么在一个单独的处理程序中配置SpellCheckComponent,比如/spell,并在启用排序的情况下进行查询。在本节的后面,您将了解到排序规则。客户端应该使用这个处理程序返回的拼写正确的响应向查询处理程序发出新的请求,比如/select。这个自动纠正过程需要两个 Solr 请求。

SpellCheckComponent提供了一组拼写检查的可选选项,可在classname参数中指定。可以将多个拼写检查器配置为同时执行。

您应该对用于拼写检查的字段执行最少的分析。应该避免将标记转换为完全不同的形式的分析,例如词干分析或同义词扩展。对于基于 Solr 字段的拼写检查器,最好有一个单独的字段用于拼写检查,所有需要拼写建议的字段的内容都可以复制到这个字段中。

SpellCheckComponent支持分布式环境。

通用参数

表 9-3 指的是通用参数,可以在任何拼写检查器实现中使用。

表 9-3。

SpellCheckComponent Generic Parameters

| 参数 | 描述 | | --- | --- | | `name` | 为拼写检查定义指定一个名称。在为组件配置多个拼写检查器时,此参数非常有用。 | | `classname` | 指定要使用的拼写检查器实现。值可以指定为`solr.`,比如`solr.FileBasedSpellCheck`。默认实现是`IndexBasedSpellCheck`。 |

履行

本节解释 Solr 中可用的拼写检查器实现。每个都提供了一组特定参数,可与表 9-3 中的通用参数一起使用。

IndexBasedSpellChecker

使用单独的索引来维护拼写检查词典。该索引是一个附加索引,也称为副索引,它是与主索引分开创建和维护的。在配置拼写检查器时,您可以指定用于创建 sidecar 索引的源字段。

这是在SpellCheckComponent中注册的默认拼写检查器实现。

表 9-4 规定了配置IndexBasedSpellChecker的附加参数。

表 9-4。

IndexBasedSpellChecker Parameters

| 参数 | 描述 | | --- | --- | | `field` | 指定 Solr 字段,在`schema.xml`中定义,用于构建字典。 | | `sourceLocation` | 这个拼写检查器不从 Solr 字段加载术语,而是允许您从任意 Lucene 索引中加载术语。该参数指定 Lucene 索引的目录。 | | `spellcheckIndexDir` | 指定将创建边车索引的目录。 | | `buildOnCommit` | 将这个布尔参数设置为`true`会在每次提交时构建字典。默认情况下,该参数设置为`false`。 |

DirectSolrSpellChecker

这个实现使用主 Solr 索引,而不是专门为拼写检查构建额外的索引。因为使用了主索引,拼写检查器总是有最新的可用术语,并且还可以省去您定期重建索引的麻烦。

表 9-5 规定了配置DirectSolrSpellChecker的附加参数。

表 9-5。

DirectSolrSpellChecker Parameters

| 参数 | 描述 | | --- | --- | | `field` | 指定 Solr 字段,在`schema.xml`中定义,用于拼写检查。 | | `accuracy` | 指定建议的准确性级别。默认值为 0.5。 | | `maxEdits` | 指定术语中允许的最大修改次数。 | | `minPrefix` | 指定允许编辑的最小初始字符数。值越高,性能越好。此外,您会注意到前几个字符通常不会拼写错误。默认值为 1。 | | `maxInspections` | 指定在返回建议之前要检查的最大匹配数。默认值为 5。 | | `minQueryLength` | 指定查询中用于生成建议的最小字符数。如果查询短于此长度,将不会生成任何建议。 | | `maxQueryFrequency` | 指定查询词应该出现在文档中的最大数量。如果计数大于指定的阈值,该项将被忽略。该值可以是绝对值(例如 5)或百分比(例如 0.01 或 1%)。默认值为 0.01。 | | `thresholdTokenFrequency` | 指定查询词应该出现在文档中的最小数量。如果计数小于指定的阈值,该项将被忽略。与`maxQueryFrequency`类似,可以指定绝对值或百分比。默认值为 0.0。 |

FileBasedSpellChecker

使用一个外部文本文件作为拼写的来源,并用它构建一个 Lucene 索引。当您不希望拼写检查器基于索引文档中的术语,而是从另一个来源(如频繁查询的日志分析或外部主题词表)提取时,这种实现很有帮助。

源文件应该是一个简单的文本文件,每行定义一个单词。这是一个样本文件。

medical-spellings.txt

advanced

aided

assigned

assessed

assisted

..

表 9-6 规定了配置FileBasedSpellChecker的附加参数。

表 9-6。

FileBasedSpellChecker Parameters

| 参数 | 描述 | | --- | --- | | `sourceLocation` | 指定包含术语的文本文件的路径。 | | `characterEncoding` | 指定文件中术语的编码。 | | `spellcheckIndexDir` | 指定将创建索引的目录。 |

WordBreakSolrSpellChecker

该实现侧重于执行拼写检查,以检测由于缺少或不需要的空白而导致的拼写错误。用户很可能会将一个单词拆分成两个单词,或者将两个单词合并成一个单词。一个典型的例子是拼写检查和拼写检查。WordBreakSolrSpellChecker就是为了解决这个特殊问题而开发的。它组合和分解术语来提供建议。

表 9-7 规定了配置WordBreakSolrSpellChecker的附加参数。

表 9-7。

WordBreakSolrSpellChecker Parameters

| 参数 | 描述 | | --- | --- | | `field` | 指定 Solr 字段,在`schema.xml`中定义,用于构建字典。 | | `combineWords` | 指定是否应该合并相邻的术语。默认值为`true`。 | | `breakWords` | 指定拼写检查器是否应该尝试将术语拆分为多个术语。默认值为`true`。 | | `maxChanges` | 指定拼写检查器应该进行的最大排序尝试次数。 |

它是如何工作的

Solr 中的SpellCheckComponent类实现了SearchComponent,它利用了 Lucene 提供的SpellChecker实现。SpellCheckComponent处理请求的步骤如下:

The client makes a request to a SearchHandler that has SpellCheckComponent registered to it.   The SpellCheckComponent, like any other SearchComponent, executes in two phases, namely prepare and process.   If the received request is to build or reload the dictionary, it is done in the prepare phase. DirectSolrSpellChecker and WordBreakSolrSpellChecker don’t require a build or reload.   In the process phase, Solr tokenizes the query with the applicable query analyzer and calls the appropriate SpellChecker with the provided request parameters.   The spell-checker implementation generates suggestions from the loaded dictionary or field as applicable.   If collation is required, Solr calls the SpellCheckCollator to collate the spellings.   The generated responses are returned to the client.   Note

如果您对内部处理的细节感兴趣或者正在计划定制,可以参考这些步骤;否则 Solr 不要求你了解他们。

使用

下面是集成和使用拼写检查器的步骤:

Define the SpellCheckComponent in solrconfig.xml, specify the implementation classname, and add the parameters from the table of corresponding classnames. Register a single spell-checker <searchComponent name="spellcheck" class="solr.SpellCheckComponent"> <lst name="spellchecker"> <str name="classname">solr.IndexBasedSpellChecker</str> <str name="spellcheckIndexDir">./spellchecker</str> <str name="field">spellings</str> <str name="buildOnCommit">true</str> </lst> </searchComponent> Register multiple spell-checkers <searchComponent name="spellcheck" class="solr.SpellCheckComponent"> <lst name="spellchecker"> <str name="name">primary</str> <str name="classname">solr.IndexBasedSpellChecker</str>     .. </lst> <lst name="spellchecker"> <str name="name">secondary</str> <str name="classname">solr.FileBasedSpellChecker</str>     .. </lst> </searchComponent>   Register the component to the desired SearchHandler. You can register it either to your primary handler that serves the search request or a separate handler dedicated for spell-checking. The following is an example configuration that registers multiple spell-checkers to the /select handler. <requestHandler name="/select" class="solr.SearchHandler"> <lst name="defaults"> <str name="spellcheck.dictionary">primary</str> <str name="spellcheck.dictionary">secondary</str> </lst> <arr name="last-components"> <str>spellcheck</str> </arr> </requestHandler> Table 9-8 lists the request parameters supported by SpellCheckComponent.

表 9-8。

SpellCheckerComponent Request Parameters

| 参数 | 描述 | | --- | --- | | `spellcheck` | 默认情况下,拼写检查是禁用的。设置`spellcheck="true"`打开该功能。 | | `spellcheck.q` | 指定拼写检查的查询。如果未指定该参数,组件将从`q`参数中获取值。 | | `spellcheck.count` | 指定要返回的建议的最大数量。默认值为 1。 | | `spellcheck.dictionary` | 指定用于请求的词典。 | | `spellcheck.build` | 此参数清除现有字典,并使用源中的最新内容创建一个新副本。此操作的成本可能很高,应该偶尔触发。 | | `spellcheck.reload` | 将该参数设置为`true`会重新加载拼写检查器和底层词典。 | | `spellcheck.accuracy` | 以浮点值的形式指定所需的精度级别。分数低于此值的建议将不会提供。 | | `spellcheck.alternativeTermCount` | 指定为每个查询词返回的建议数。这有助于构建上下文相关的拼写纠正。 | | `spellcheck.collate` | 如果有多个建议,或者您想向用户提供拼写正确的查询结果,那么将该参数设置为`true`是一个不错的选择。该特性为查询中的每个标记提取最佳建议,并将它们组合成一个新的查询,您可以执行该查询以获得建议的结果。 | | `spellcheck.maxCollations` | 默认情况下,Solr 返回一个排序规则。您可以设置该参数来获取更多的排序规则。 | | `spellcheck.maxCollationTries` | 排序规则的功能确保排序查询在索引中找到匹配项。该特性指定了对索引进行测试的次数。在测试时,原始查询被排序规则替代,并尝试进行匹配。默认值 0 可以跳过测试,从而导致不匹配。较高的值可以确保更好的排序,但代价会很高。 | | `spellcheck.maxCollationEvaluations` | 指定要评估和排序的最大组合数,以形成对索引进行测试的排序规则。默认值已被优化设置为 10,000 个组合。 | | `spellcheck.collateExtendedResult` | 将该参数设置为`true`将扩展响应,以包含排序规则的详细信息。 | | `spellcheck.collateMaxCollectDocs` | 测试排序规则时,如果不需要命中次数,此参数有助于提高性能。对于值 0,通过对所有文档运行测试来提取准确的命中次数。对于大于 0 的值,基于那些文档提供估计。 | | `spellcheck.collateParam.*` | 您可以使用此前缀来覆盖默认的查询参数,或者为归类测试查询提供一个附加的查询参数。通过在`spellcheck.collateParam.fq`中指定覆盖值,可以覆盖所有的`fq`参数。 | | `spellcheck.extendedResults` | 此布尔参数指定是否使用包含更多详细信息的扩展响应格式。扩展响应格式不同于标准建议;对于每个建议,它提供了文档频率,对于每个术语,它提供了一个包含附加信息(如术语频率)的建议块,默认值为`false`。 | | `spellcheck.onlyMorePopular` | 如果该参数设置为`true`,则仅当建议的标记比原始术语具有更高的文档频率时,才返回建议。默认情况下,该参数设置为`false`。 | | `spellcheck.maxResultsForSuggest` | 默认情况下,`correctlySpelled`响应参数被设置为`false`,只有当查询术语在字典和索引中都缺失时,才会返回建议。如果索引的结果达到指定的数量,则将该参数设置为大于 0 的值将返回一个建议。例如,如果响应最多有 9 个结果,值 10 将返回建议,如果有 10 个或更多结果,则不返回建议。 |

Query for search results with the spell-checking feature enabled.   Builds the spell-checker and then generates a spelling suggestion

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

&spellcheck=true&spellcheck.build=true"

任何拼写建议的请求只有在词典建立后才会得到结果。因此,在请求拼写建议之前,spellcheck.build=true应该至少被触发一次。构建操作可能成本很高,应该不经常触发。

Request suggestions for the user query

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

&spellcheck=true&spellcheck.count=5&spellcheck.accuracy=0.6"

自动完成

自动完成是用户搜索体验的一个重要特性。这是一种预输入搜索,只要用户输入几个字符,它就会完成单词或预测其余的单词。对于用户键入的每个额外字符,建议被细化。大多数现代搜索引擎都支持这个特性。

以下是实现自动完成的一些主要原因:

  • 实现自动完成最简单的原因是为了避免拼写错误。如果所提供的建议是正确的,并且用户选择了它,则查询将没有拼写错误。
  • 如果预测是正确的,用户就不需要键入完整的查询。用户可以选择其中一个建议,然后按回车键。
  • 有时用户知道他们想要什么,但不知道如何用语言表达。当一个搜索关键字有许多常见的同义词,或者您正在寻找某个问题的答案时,您可能会遇到这种情况。Google autocomplete 在这方面做得很好,如果弹出一个建议,您可以相当自信地认为您正在制定一个正确的查询。
  • 自动完成有助于查询分类和应用过滤器。在电子商务中,建议根据产品的主要方面对产品进行分类。例如,查询 jeans 可能会给出诸如 jeans(男士)或 jeans(女士)的建议,而查询 apple 可能会给出诸如 apple(手机)或 apple(笔记本电脑)的建议。
  • 该功能还用于根据查询生成建议。它再次广泛应用于电子商务,根据查询向用户推荐最畅销的产品。例如,只要你输入尼康,自动完成功能就会显示像尼康 3200 这样的畅销产品及其价格,甚至可能还有一张图片。

自我暗示可以有多种类型,也可以基于多种来源。提供给用户的建议可以是完整的短语或特定的单词。短语可以从 Solr 索引或用户日志中提取。如果搜索引擎被广泛使用并拥有大量用户,从日志中提取的短语可以产生更实际的建议。来自日志的查询应该经过一系列的处理,比如过滤掉索引中没有匹配的查询;有拼写错误的查询应该被过滤掉或进行拼写纠正;网络搜索引擎会先过滤掉对敏感话题和不良内容的查询,然后再生成短语。在建议之前应该折叠短语,以便不会多次建议相同或相似的短语。

短语建议可以与令牌建议等其他方法结合使用,令牌建议可以作为一种后备机制。当没有短语建议可用于用户键入的查询时,系统可以完成用户正在键入的单词。

Solr 提供了多种供应和实现来实现自动补全。每一种都有其优点和局限性。您选择哪一种或哪几种组合取决于您的需求。由于为用户按下的每个键都提供了一个建议,所以无论您采用哪种方法都不应该忽略性能,并且应该快速生成建议。Solr 提供的实现可以大致分为两类:

  • 传统方法:这种方法利用 Solr 的现有分析器和组件来获得特性。
  • SuggestionComponent:专门为支持自动完成功能而开发的组件。

传统方法

传统的自动建议方法指的是 Solr 版之前引入的条款。在这种方法中,自动完成是通过利用 Solr 中已经可用的特性来实现的。您所需要做的就是对它进行适当的配置,让它正常工作。

在这种方法中,应该根据您想要生成的建议类型,在现场配置适当的分析器。对于暗示短语,KeywordTokenizerFactory是合适的;而对于暗示的话,StandardTokenizerFactory或者WhitespaceTokenizerFactory是合适的。您可以两者都用,给短语字段更高的权重。

用于生成建议的字段应该分析为小写,以执行不区分大小写的搜索。一些组件(接下来将详细讨论)不支持查询时分析,在这种情况下,客户端程序可能需要将查询转换成小写形式。任何其他处理都可以在索引时分析中进行,以便适当地匹配标记。

以下是实现自我暗示的传统方法。

术语组件

TermsComponent提供对字段中索引的术语的直接访问,并返回包含该术语的文档数。该组件直接访问 Lucene 术语词典,因此检索速度很快。

利益

以下是使用TermsComponent进行自动暗示的好处:

  • 直接查找术语词典,因此是所有传统自我暗示方法中最快的。
  • 它允许您执行前缀和中缀查询。
限制

以下是这种方法的局限性:

  • TermsComponent直接查找术语词典,因此不能应用过滤器将结果限制在文档的子集内。例如,您不能只检索库存产品。
  • 直接查找的另一个限制是,它还会考虑标记为删除的术语。
  • TermsComponent不分析查询。
使用

以下是为自动建议配置TermsComponent的步骤:

Define the TermsComponent in solrconfig.xml. <searchComponent name="terms" class="solr.TermsComponent"/>   Register the component to the desired request handler. <requestHandler name="/terms" class="solr.SearchHandler" startup="lazy"> <lst name="defaults"> <bool name="distrib">true</bool> </lst> <arr name="components"> <str>terms</str> </arr> </requestHandler> Table 9-9 defines the important parameters of TermsComponent for supporting autocomplete.

表 9-9。

TermsComponent Important Request Parameters

| 参数 | 描述 | | --- | --- | | `terms` | 指定`terms="true"`启用`TermsComponent`。默认情况下,该组件处于禁用状态。 | | `terms.fl` | 指定应该从中检索术语的字段名称。 | | `terms.limit` | 指定要检索的最大术语数。 | | `terms.prefix` | 在此参数中指定用户键入的查询,以检索以该查询开头的所有标记。 | | `terms.regex` | 指定用于检索术语的正则表达式模式。这对中缀自动补全很有用。该操作的执行成本可能比`terms.prefix`高,但允许中缀操作。在请求处理程序之前,用户查询需要预处理来形成正则表达式。 | | `terms.regex.flag` | 指定`terms.regex.flag=case_insensitive`执行不区分大小写的正则表达式。 | | `terms.mincount` | 响应中将不会返回文档频率小于指定值的术语。此参数可用于避免拼写错误的内容,假设它们出现的频率很低。 | | `terms.maxcount` | 文档频率超过指定值的术语将不会在响应中返回。此参数有助于消除可能成为停用词的术语。 | | `terms.sort` | 指定`terms.sort=count`首先对出现频率最高的术语进行排序,指定`terms.sort=index`按索引顺序进行排序。 |

No specific text analysis is required on the fieldType. You may just want to convert all tokens to lowercase for case-insensitive matching. A typical fieldType is defined as follows: <fieldType name="autocomplete" class="solr.TextField" positionIncrementGap="100" >   <analyzer type="index"> <tokenizer class="solr.KeywordTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.KeywordTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>   Define the field for autosuggestion. The field need not be stored. <field name="brand" type="autocomplete" indexed="true" stored="false" />   Send a request for each character typed by the user.   Suggest all terms beginning with specified characters

$ curl " http://localhost:8983/solr/hellosolr/terms?terms=true&terms.fl=brand"

Suggest all terms containing the specified characters

$ curl " http://localhost:8983/solr/hellosolr/terms?terms=true&terms.fl=brand

&terms.regex=.*eebo.*&terms.regex.flag=case_insensitive&terms.limit=5"

面状

分面可以用来实现自我暗示。这有利于在category等字段上生成建议。方面也将计数与术语一起返回。第七章提供了刻面的细节。

利益

以下是使用FacetComponent进行自动建议的好处:

  • 它允许您过滤要在文档子集上运行的建议。例如,您可以只检索有库存的产品。
限制

以下是这种方法的局限性:

  • 分面是一个占用大量内存的过程,尤其是在唯一令牌的数量很大的情况下。与TermsComponent相比,它的响应时间也很长。
  • 它不支持检索中缀项。
  • 它不分析查询。
使用

默认情况下,FacetComponent对处理程序可用,不需要注册。以下是使用分面配置自动建议的步骤:

Define the fieldType for text analysis of the faceted field. <fieldType name="autocomplete" class="solr.TextField" positionIncrementGap="100" > <analyzer type="index"> <tokenizer class="solr.KeywordTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.KeywordTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>   Define the field for autosuggestion. The field need not be stored. <field name="autocomplete" type="autocomplete" indexed="true" stored="false" />   Leverage the facet.prefix request parameter provided by faceting for generating a suggestion. The value supplied to this parameter is not analyzed, so as a convention always lowercase the value before supplying it to the field.   Generate a suggestion using the facet.prefix feature of FacetComponent

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

&rows=0&facet=true&facet.field=brand&facet.mincount=5

&facet.limit=5&facet.prefix=ree"

表 9-10 描述了FacetComponent支持自动建议的重要参数。

表 9-10。

FacetComponent Parameters for Autosuggestion

| 参数 | 描述 | | --- | --- | | `facet` | 指定`facet=true`启用`FacetComponent`。 | | `rows` | 设置`rows=0`,因为你不需要从 Solr 获取搜索结果。 | | `facet.prefix` | 指定用户查询以检索以它开头的所有建议。 | | `facet.field` | 指定应在其上执行刻面的字段。 | | `facet.mincount` | 此参数可用于避免拼写错误的内容,假设对计数小于阈值的术语不感兴趣。 | | `facete.limit` | 此参数限制生成的建议数量。 |

EdgeNGram

我们在第四章中讨论模式设计时讨论了NGramEdgeNGram从一条边开始,将记号分解成不同大小的子记号。对于 autocomplete,只对从前端创建的标记感兴趣。

利益

以下是使用EdgeNGram进行自动暗示的好处:

  • 当您需要搜索整个文档而不仅仅是拼写建议时,EdgeNGram对于生成建议非常有用。电子商务中的一个典型例子是向用户推荐受欢迎的产品,在这种情况下,您还需要返回产品的图片和价格。
  • 它允许您将结果限制在文档的子集内。例如,您可以只检索库存产品。
  • 可以提升文档,例如,如果您希望热门的匹配文档在建议列表中排名更高。
  • 可以执行查询时分析。
限制

以下是这种方法的局限性:

  • 这种方法会生成许多令牌,从而增加索引的大小。
  • 可能会有性能问题,缓慢的提前键入违背了自动完成的目的。
  • 搜索查询在EdgeNGram字段上执行,响应可以包含重复的标记。因此,多个文档的建议字段不应具有相同的值。如果您不能删除多余的文档,您需要使用一种混合的方法,即EdgeNGram和刻面。EdgeNGram可以在用户查询日志中创建一个单独的索引来生成建议时发挥作用。
使用

以下是使用EdgeNGram配置自动建议的步骤:

Define a fieldType to use EdgeNGram. <fieldType name="edgengram" class="solr.TextField" positionIncrementGap="100" omitNorms="true" omitTermFreqAndPositions="true"> <analyzer type="index"> <tokenizer class="solr.KeywordTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.EdgeNGramFilterFactory" minGramSize="3" maxGramSize="15" />   </analyzer> <analyzer type="query"> <tokenizer class="solr.KeywordTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType> Set the attribute minGramSize to specify the minimum characters from which suggestions should start coming up. Specify the attribute maxGramSize to specify the characters beyond which you don’t want to provide any refinement. The EdgeNGramFilterFactory should be applied only at index time. In the preceding fieldType text analysis, you have defined EdgeNGramFilterFactory to generate grams from a minimum size of 3 to a maximum size of 15.   Define the field for generating suggestions. <field name="autocomplete" type="edgengram" indexed="true" stored="true" />   Execute the query to generate suggestions. $ curl " http://localhost:8983/solr/hellosolr/select?q=autocomplete:lapt "

建议组件

Solr 3.1 引入了一个专门的组件来解决自动建议的问题。这种方法使用 Lucene 的 suggester 模块,比前面讨论的方法更加强大和灵活。

SuggestComponent的两个重要方面是字典和查找算法。在配置建议器时,您需要选择两者的实现。接下来将描述这两个方面。

词典

字典指定了维护术语的来源。Solr 提供的实现使用了Dictionary接口。概括地说,词典可以有以下两种类型:

  • 基于索引:使用 Lucene 索引作为生成建议的来源
  • 基于文件:使用指定位置的文本文件来生成建议

以下是 Solr 提供的实现。

文档字典工厂

DocumentDictionaryFactory是一个基于索引的字典,使用术语、权重和可选的有效负载来生成建议。以下是此实现支持的附加参数。

  • weightField:如果要给术语分配权重,在此参数中指定包含权重的字段名。分配的默认权重为 0。该字段应该被存储。
  • payloadField:如果你想给术语分配一个有效载荷,在此参数中指定包含有效载荷的字段名。该字段应该被存储。
DocumentExpressionDictionaryFactory

这个工厂提供了一个DocumentValueSourceDictionary的实现,它扩展了DocumentDictionary来支持一个权重表达式,而不是一个数字权重。

以下是此实现支持的附加参数:

  • weightExpression:您可以指定一个表达式来计算术语的权重。它使用 Lucene 表达式模块来计算表达式。表达式定义类似于函数查询表达式。例如,"((div(rating,10) + 1) + sqrt(popularity))"就是一个有效的表达式。表达式中指定的字段应为数字。该参数是必需的。
  • payloadField:如果你想给术语分配一个有效载荷,在此参数中指定包含有效载荷的字段名。必须存储该字段。
高频词典

这是基于索引的字典的默认实现,它允许您只考虑构建字典的常用术语。频率小于阈值的项被丢弃。

以下是此实现支持的附加参数:

  • threshold:该参数指定添加到字典中的术语的阈值。该值可以在 0 和 1 之间,表示该术语应该出现在文档中的比例。该参数是可选的。如果未指定,则考虑所有术语。
FileDictionaryFactory

这是唯一支持的基于文件的字典实现,它允许您从外部文件读取建议。类似于基于索引的字典,权重和有效负载是允许的,可以在由分隔符分隔的术语后指定。

以下是此实现支持的附加参数:

  • fieldDelimiter:指定分隔条款、重量和有效载荷的分隔符。默认值为 tab。

这里是一个维护建议的样本文件。

suggestions.txt

# File based suggestions

mobile\t1.0

mobile phone\t3.0

mobile cases\t2.0

算法

在选择了合适的字典实现之后,您需要选择最适合您的用例的lookupImpl。Lucene 支持一组算法(或数据结构),并为它们提供不同的实现。所有的实现类都扩展了Lookup抽象类。

以下是支持的数据结构的广泛定义:

  • 有限状态转换器(FST):建议者用所有的术语建立一个自动机,用来满足所有的自动建议请求。
  • 自动机提供快速搜索,内存占用低,但在所有支持的数据结构中,构建自动机的过程是最慢的。此外,术语不能附加到自动机。对于添加术语,应该从头开始构建。自动机可以作为二进制 blob 保存到磁盘上,以便在 Solr 重启或内核重载时快速重载。
  • 三元搜索树(TST):三元搜索树类似于二叉查找树,但是最多可以有三个子树。它为术语查找提供了一种快速灵活的方法。该树可以动态更新。
  • JaSpell 算法:该算法由 Bruno Martins 编写,使用三叉树来提供高度复杂的建议。它可以快速构建数据结构。它支持基于 Levenshtein 距离的模糊查找,比 FST 更复杂。参考 Jaspell 网站 http://jaspell.sourceforge.net/
  • 这些建议可以按字母顺序或诸如评级、受欢迎程度等参数排序。
  • 基于索引:这种方法使用 Lucene 索引进行查找。

以下是 Solr 为查找提供的实现。每一个都定义了自己的参数集来支持所提供的功能。

TSTLookupFactory

该实现提供了基于三元搜索树的查找。它的建议者不支持有效负载,也不需要指定任何额外的参数。

FSTLookupFactory

这种实现提供了基于自动机的查找,速度非常快。这是一个很好的建议,除非你需要一个更复杂的。表 9-11 指定了FSTLookupFactory支持的参数。

表 9-11。

FSTLookupFactory Parameters

| 参数 | 描述 | | --- | --- | | `weightBuckets` | 指定为权重创建的桶数。计数可以在 1 到 255 之间。默认桶数是 10。 | | `exactMatchFirst` | 如果设置为`true`,则首先返回准确的建议,而不考虑自动机中可用的其他字符串的前缀。 |
WFSTLookupFactory

这种实现基于加权 FST 算法。它使用最短路径方法来查找最佳建议。它支持exactMatchFirst参数。表 9-11 提供了关于该参数的信息。

JaspellLookupFactory

该实现基于 JaSpell 算法。它的执行速度很快,对许多问题都很有用。

分析工厂

这种实现使用加权 FST 进行查找。它被称为AnalyzingLookupFactory,因为它在构建 FST 之前和查找期间分析文本。查找之前的分析提供了强大而灵活的自我暗示。分析链可以配置为使用停用词和同义词扩展等功能。例如,如果将 cell phone 和 mobile phone 定义为同义词,则用户文本单元格可以提供手机壳等建议。

分析器应该仔细配置,因为像停用词这样的特性可能导致没有建议。此外,返回的建议是原始文本,而不是文本的分析形式。表 9-12 指定了AnalyzingLookupFactory支持的参数。

表 9-12。

AnalyzingLookupFactory Parameters

| 财产 | 描述 | | --- | --- | | `suggestAnalyzerFieldType` | 指定用于分析的`fieldType`。 | | `exactMatchFirst` | 如果设置为`true`,精确匹配将作为首选建议返回,而不考虑其他具有更高权重的建议。默认设置为`true`。 | | `preserveSep` | 如果设置为`true`,则保留标记分隔符(即`cellphone`和`cell phone`不同)。默认设置为`true`。 | | `preservePositionIncrements` | 如果设置为`true`,位置增量保持不变。如果设置为`false`,假设 of 是一个停用词,那么像`best 20`这样的用户查询将生成类似 2015 年最佳的建议。默认设置为`false’`。 |
模糊 LookupFactory

FuzzyLookupFactory扩展了AnalyzingLookup的功能,允许基于 Levenshtein 距离对分析文本进行模糊匹配。它支持AnalyzingLookupFactory的所有参数,以及表 9-13 中提到的附加参数。

表 9-13。

FuzzyLookupFactory Parameters

| 财产 | 描述 | | --- | --- | | `maxEdits` | 指定允许的最大编辑次数。默认值为 1。该值的硬限制被指定为 2。 | | `transpositions` | 如果`true`,将使用图元编辑操作计算变调。如果`false`,将使用 Levenshtein 算法。 | | `nonFuzzyPrefix` | 指定查找键的长度,超过该长度的字符将执行模糊匹配。应该只建议包含键的初始前缀字符的匹配。默认值为 1。 | | `minFuzzyLength` | 指定长度,在该长度以下将不对查找关键字执行编辑。默认值为 3。 | | `unicodeAware` | 默认情况下,该参数设置为`false`,前面四个参数以字节为单位。如果该参数设置为`true`,它们将以实际字符进行测量。 |
分析 gInfixLookupFactory

使用 Lucene 索引作为字典,并对索引的术语提供灵活的基于前缀的建议。与AnalyzingLookupFactory类似,它也在构建字典和查找时分析输入文本。表 9-14 指定了AnalyzingInfixLookupFactory支持的参数。

表 9-14。

AnalyzingInfixLookupFactory Parameters

| 参数 | 定义 | | --- | --- | | `indexPath` | 指定存储和加载索引的目录。默认情况下,索引是在`data`目录中创建的。 | | `allTermsRequired` | 如果`true`,在多项键上应用布尔运算符`AND`;否则,应用操作员`OR`。默认值为`true`。 | | `minPrefixChars` | 指定从开始使用`prefixQuery`的最小字符数。n 元语法是为短于这个长度的前缀生成的,这提供了更快的查找速度,但增加了索引大小。默认值为 4。 | | `highlight` | 如果`true`是默认值,则建议被突出显示。 |
BlendedInfixLookupFactory

这个实现是对AnalyzingInfixLookupFactory的扩展,它允许您对前缀匹配应用权重。它允许您根据第一个匹配单词的位置分配更多的权重。它支持AnalyzingInfixLookupFactory的所有参数,以及表 9-15 中提到的附加参数。

表 9-15。

BlendedInfixLookupFactory Parameters

| 参数 | 描述 | | --- | --- | | `blenderType` | 指定用于计算权重系数的搅拌机类型。以下是支持的搅拌机类型:`linear`:使用公式`weight×(1 - 0.10×position)`计算重量。它在开始时给予匹配更高的权重。这是默认的`blenderType`。`reciprocal`:使用公式`weight/(1+position)`计算重量。它最终会给匹配项更高的权重。 | | `numFactors` | 指定结果数的倍增因子。默认值为 10。 |
FreeTextLookupFactory

当其他建议者找不到匹配时,这个建议者已经实现了回退建议的需求。它在构建词典时构建文本的 N 元语法,并在查找时考虑来自用户查询的最后 N 个单词。它适合处理以前从未见过的查询。表 9-16 指定了FreeTextLookupFactory支持的参数。

表 9-16。

FreeTextLookupFactory Parameters

| 参数 | 描述 | | --- | --- | | `suggestFreeTextAnalyzerFieldType` | 指定用于分析的`fieldType`。此字段为必填字段。 | | `ngrams` | 指定构建字典时要考虑的最后标记的数量。默认值为 2。 |

它是如何工作的

Solr 中的自动建议特性由SuggestComponent提供,它与SolrSuggester交互以生成建议。以下是组件遵循的步骤:

The client makes a request to a SearchHandler, which has SuggestComponent registered to it.   The SuggestComponent, like any other SearchComponent, executes in two phases, namely prepare and process.   If the received request is to build or reload the dictionary, in the prepare phase the component calls the SolrSuggester to perform the task. SolrSuggester is responsible for loading the lookup and dictionary implementations specified in the configuration.   In the process phase, the component calls all the registered SolrSuggesters for getting the suggested results.   SolrSuggestor calls the appropriate implementation, which looks up a key and returns the possible completion for this key. Depending on the implementation, this may be a prefix, misspelling, or even infix.   The component converts the suggested results to an appropriate form and adds to the response.

使用

以下是在 Solr 中配置和使用SuggestComponent的步骤:

Define the SuggestComponent in solrconfig.xml. Table 9-17 specifies the parameters supported by the component.

表 9-17。

SuggestComponent Parameters

| 参数 | 描述 | | --- | --- | | `name` | 指定用于生成建议的建议者的名称。 | | `dictionaryImpl` | 指定要使用的字典实现。默认情况下,`HighFrequencyDictionaryFactory`用于基于索引的数据结构,`FileDictionaryFactory`用于基于文件的数据结构。如果`sourceLocation`参数存在,组件假定使用基于文件的字典。 | | `lookupImpl` | 指定要使用的查找实现。如果未提供该参数,默认情况下将使用`JaspellLookupFactory`。 | | `field` | 对于基于索引的字典,此参数指定查找实现要使用的字段。 | | `sourceLocation` | 如果使用`FileDictionaryFactory`,该参数指定字典文件路径。 | | `storeDir` | 字典将被保存到的目录。 | | `buildOnStartup` | 将这个布尔参数设置为`true`会在 Solr 启动和内核重载时构建数据结构。 | | `buildOnCommit` | 将此布尔参数设置为`true`会在提交时构建数据结构。 | | `buildOnOptimize` | 将此布尔参数设置为`true`会在优化时构建数据结构。 |

<searchComponent name="suggest" class="solr.SuggestComponent"> <lst name="suggester">   <str name="name">analyzedSuggestion</str> <str name="lookupImpl">AnalyzingLookupFactory</str>   <str name="dictionaryImpl">DocumentDictionaryFactory</str>   <str name="field">brand</str>   <str name="weightField">popularity</str> <str name="suggestAnalyzerFieldType">string</str>   <str name="buildOnStartup">false</str> </lst> </searchComponent>   Register the component to the desired SearchHandler. Generally, if you like to have a dedicated endpoint for autosuggestion, add it to components instead of last-components. Table 9-18 specifies the parameters that can be configured in the handler or provided in the search request.

表 9-18。

SuggestComponent Parameters for the Request Handler

| 参数 | 描述 | | --- | --- | | `suggest` | 将该布尔参数设置为`true`会启用`SuggestComponent`。 | | `suggest.q` | 应该检索其建议的用户查询。如果未指定该参数,组件将在`q`参数中寻找一个值。 | | `suggest.dictionary` | 该参数是必需的。它指定用于生成建议的 suggester 组件的名称。 | | `suggest.count` | 指定要检索的建议的最大数量。 | | `suggest.build` | 将该参数设置为`true`构建建议者数据结构。构建操作的成本可能很高,因此可以根据需要触发该过程。表 9-17 提供了构建建议器的其他选项。 | | `suggest.buildAll` | 将该参数设置为`true`为组件中注册的所有建议者建立数据结构。 | | `suggest.reload` | 将该参数设置为`true`重新加载建议者数据结构。 | | `suggest.reloadAll` | 将该参数设置为`true`重新加载组件中注册的所有建议者的数据结构。 |

<requestHandler name="/suggest" class="solr.SearchHandler" startup="lazy">   <lst name="defaults">     <str name="suggest">true</str>     <str name="suggest.count">10</str> </lst>   <arr name="components">     <str>suggest</str>   </arr> </requestHandler>   Define the fieldType for text analysis. <fieldType class="solr.TextField" name="textSuggest" positionIncrementGap="100">   <analyzer>     <tokenizer class="solr.StandardTokenizerFactory"/>     <filter class="solr.StandardFilterFactory"/>     <filter class="solr.LowerCaseFilterFactory"/>   </analyzer> </fieldType>   Define the field. The field must be set as stored. You might want to copy all the fields on which the suggestion should be generated to this field.   Query for results. If you are using a dedicated handler for suggestions, you can set suggest=true and other infrequently changing parameters in it, so that you don’t need to provide those parameters with each request. The following are the sample queries for generating suggestions.   Request to build specific suggester data structures

$ curl "``http://localhost:8983/solr/hellosolr/suggest?suggest=true&suggest.buildAll=true

Request to build all the suggester data structures

$ curl " http://localhost:8983/solr/hellosolr/suggest?suggest=true

&suggest.build=true&suggest.dictionary=analyzedSuggestion"

Request for suggestions in the analyzedSuggestion dictionary

$ curl " http://localhost:8983/solr/hellosolr/suggest?suggest=true

&suggest.dictionary=analyzedSuggestion&wt=json&suggest.q=lapt"

文档相似度

开发搜索引擎的主要目的是检索与用户查询最相关的文档。一旦用户预览了检索到的文档,他很可能会对其他类似的文档感兴趣。如果应用程序是为搜索新闻、期刊和博客而开发的,那么建议相似的文档就变得更加重要。您可能对查找相似文档感兴趣的另一个领域是重复检测、剽窃和指纹识别。

Solr 提供了MoreLikeThis特性来解决查找相似文档的问题。这个特性也可以用于构建基于内容的推荐系统,这基本上是一个机器学习问题。基于内容的推荐系统使用项目特征或关键字来描述项目,这在 Solr 字段中已经可用。现在,如果您维护一个用户档案或从用户的购买历史和“喜欢”中提取产品的共同属性,您可以使用这些共同的关键字或属性查询MoreLikeThis来为用户生成推荐。

MoreLikeThis采用词袋方法进行相似性检测。它接受一个文档或任意文本作为输入,并返回匹配的文档。它允许您通过配置定义和请求参数来控制匹配功能。

MoreLikeThis中的相似文档检测是一个两阶段过程。以下是各阶段的处理细节:

  • 检测感兴趣的关键字:该算法以统计方式确定文档中的重要和感兴趣的术语,以便检索相似的文档。它选择性地忽略非常常见、非常罕见、非常短或非常长的术语。如果 boost 使能,它们会添加到基于 TF-IDF 系数的项中。
  • 匹配文档查询:第一阶段符合条件的术语作为搜索请求提交,检索最相关的文档。

先决条件

以下是使用MoreLikeThis组件的先决条件:

  • 应当存储uniqueKey字段。
  • 该组件中使用的字段应存储术语向量(termVectors="true")。以下是示例字段定义:<field name="product" type="text_general" indexed="true" stored="true" termVectors="true" />
  • 如果术语向量被禁用,则组件从存储的字段生成术语。因此,至少应该存储该字段(stored="true")。

Caution

MoreLikeThis支持分布式搜索但是有一个公开的 bug https://issues.apache.org/jira/browse/SOLR-4414 。服务于请求的碎片应该包含文档;否则,MoreLikeThis不会找到任何匹配项。

履行

MoreLikeThis在 Solr 中有三种实现来满足不同的用户需求。实现如下所示:

  • MoreLikeThisComponent:可以注册到任何SearchHandler的组件列表中。如果您希望检索与主查询检索的每个文档相似的文档,这将非常有用。
  • MoreLikeThisHandler:这个处理程序允许您提供一个文档或内容流,并检索类似的文档。
  • MLTQParserPlugin:这个查询解析器通过使用文档 ID 和提供的其他统计信息形成一个MoreLikeThisQuery

通用参数

表 9-19 规定了适用于所有实现的通用参数。

表 9-19。

MoreLikeThis Generic Parameters

| 参数 | 描述 | | --- | --- | | `mlt.fl` | 指定应在其中识别感兴趣的术语以确定相似性的字段。该参数不适用于`MLTQParserPlugin`。 | | `mlt.qf` | 指定用于确定相似文档的查询字段。这些字段也必须在`mlt.fl`参数中指定。字段名后面可以跟一个字符运算符(`^`)和相应的 boost。 | | `boost` | 如果该参数设置为`true`,查询中感兴趣的术语将被提升。此参数不适用于`MLTQParserPlugin`。 | | `mlt.mintf` | 指定最小术语频率。计数小于指定值的术语将从感兴趣术语列表中忽略。默认值为 2。 | | `mlt.mindf` | 指定最小文档频率。出现在较少文档中的术语将被忽略。默认值为 5。 | | `mlt.maxdf` | 指定最大文档频率。文档中出现超过指定值的术语将被忽略。 | | `mlt.minwl` | 指定最小单词长度。长度较小的单词将被忽略。默认值为 0,编程为无效。 | | `mlt.maxwl` | 指定最大单词长度。超过这个长度的单词将被忽略。默认值为 0,编程为无效。 | | `mlt.maxqt` | 指定在形成查询的第二阶段要使用的最大术语数。默认值为 25。 | | `mlt.maxntp` | 如果禁用了术语 vector,此参数将指定为每个存储字段解析的最大令牌数。默认值为 5000。 |

Note

在 Solr 5.3 之前,MLTQParserPlugin不支持这些通用参数。在MLTQParserPlugin中,参数不应以mlt.为前缀。

MoreLikeThisComponent

如果将MoreLikeThisComponent配置为任何处理程序,它将为主查询返回的每个文档返回相似的文档。该操作的执行成本很高,而且用户不太可能希望主查询的所有结果都是相似的文档。它适用于指纹识别和重复检测等场景,用于处理一批文档。表 9-20 表示通用参数。

表 9-20。

MoreLikeThisComponent Specific Parameters

| 参数 | 描述 | | --- | --- | | `mlt` | 如果设置为`true`,该布尔参数启用`MoreLikeThis`组件。默认情况下,它被设置为`false`。 | | `mlt.count` | 为主查询的每个结果指定要返回的相似文档的数量。默认值为 5。 |
它是如何工作的

下面是MoreLikeThisComponent处理请求所遵循的步骤:

The client requests a SearchHandler that has MoreLikeThisComponent registered to it. The handler invokes the MoreLikeThisComponent.   The MoreLikeThisComponent does no processing in the prepare phase.   In the process phase, all the matching documents retrieved by the main query are provided to MoreLikeThisHelper for retrieving similar documents.   This helper class creates a new Query object by extracting the interesting terms from the documents and executes it on index to retrieving similar documents. This process is followed for each document retrieved by the main query and so is costly to execute.   The retrieved documents are added to response and returned to the client.   Note

如果你对内部处理的细节感兴趣或者正在计划定制,可以参考这些步骤;否则 Solr 不要求你了解他们。

用法

MoreLikeThisComponent是所有SearchHandlers的默认组件之一,因此不需要注册。以下是一个示例查询:

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

&mlt=true&mlt.fl=product&mlt.mindf=5&mlt.mintf=3&fl=product’

莫雷利克·蒂申德勒

MoreLikeThisHandler是 Solr 为查找相似文档提供的专用处理程序。与MoreLikeThisComponent不同,处理程序将返回与请求中指定的文档相似的文档。这是使用MoreLikeThis的首选方法,可以在用户预览文档时调用。您可以通过提供查询或ContentStream来找到类似的文档。该处理程序支持常用参数,如fqdefTypefacets。除了通用的MoreLikeThis参数,表 9-21 提到了MoreLikeThisHandler支持的附加参数。

表 9-21。

MoreLikeThisHandlerSpecific Parameters

| 参数 | 描述 | | --- | --- | | `mlt.match.include` | 此布尔参数指定响应中是否应该包含匹配的文档。默认值为`true`。它仅适用于使用查询查找相似文档的情况。 | | `mlt.match.offset` | 指定为响应主查询而返回的文档的偏移量,应该为该偏移量检索相似的文档。它仅适用于使用查询查找相似文档的情况。 | | `mlt.interestingTerms` | 指定如何在响应中返回感兴趣的术语。Solr 支持三种风格的有趣术语:`none`:禁用该特性。这是默认值。`list`:以列表形式显示术语`details`:显示术语及其提升。 |
它是如何工作的

下面是MoreLikeThisHandler处理请求所遵循的步骤:

MoreLikeThisHandler accepts either a content stream or a query.   If a query is provided, the handler parses it by using a defined query parser and forms a Solr Query object. If a content stream is provided, it creates a Reader object.   Solr runs the query, if applicable, on the index and retrieves matching document IDs but considers only the first retrieved document for MoreLikeThis matching.   The document ID or the reader is provided to MoreLikeThisHelper for retrieving similar documents.   This helper class creates a new Query object by extracting the interesting terms from the document or content stream and executes it on index to retrieve the matching documents.   The retrieved documents are added to the response, as well as the interesting terms and facets if specified.   The generated response is returned to the client.   Note

如果你对内部处理的细节感兴趣或者正在计划定制,可以参考这些步骤;否则 Solr 不要求你了解他们。

使用

使用MoreLikeThis处理程序很容易。以下是步骤:

Define MoreLikeThisHandler in solrconfig.xml. <requestHandler name="/mlt" class="solr.MoreLikeThisHandler" />   Query for MoreLikeThis documents. Find documents similar to document with id APL_1001 $ curl " http://localhost:8983/solr/mlt?q=id:APL_1001 &mlt.fl=product&mlt.mintf=2&mlt.mindf=5"

MLTQParserPlugin

MLTQParserPlugin是查询解析器的工厂,它允许您检索与查询中指定的文档相似的文档。该条款允许您启用高亮显示和分页,可以在qfqbq中提及。解析器期望文档的uniqueKey作为一个值,不支持 Lucene 内部文档 ID 或任何任意查询。

从 Solr 5.3 开始,这个解析器支持除了flboost之外的所有通用参数。参数名不应以mlt为前缀。

它是如何工作的

下面是 Solr 使用MLTQParserPlugin解析查询的步骤:

The parser creates a query by using the document ID provided and retrieves the matching document. If document with the specified ID doesn’t exist, Solr will report an exception.   For the retrieved document, Solr extracts the vector and term frequency of all the terms in the field list.   Using the terms and frequency, it filters out unnecessary terms and creates a PriorityQueue.   Finally, it creates MoreLikeThisQuery from terms in PriorityQueue.   Note

如果你对内部处理的细节感兴趣或者正在计划定制,可以参考这些步骤;否则 Solr 不要求你了解他们。

使用

MLTQParserPlugin不需要solrconfig.xml中的任何定义。UniqueKey字段的值应该作为文档标识符提供。解析器的用法如下。必须指定至少一个qf字段。以下是一个查询示例:

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

q={!mlt qf=product mintf=2 mindf=5 maxdf=100}APL_1001"

摘要

本章详细介绍了以下 Solr 特性:赞助搜索、拼写检查、自动完成和文档相似性。您了解了获得这些特性的方法,以及如何调整配置来定制和控制结果的行为。

在下一章中,您将学习各种扩展 Solr 的方法,包括 SolrCloud 模式。您将看到是什么使 Solr 成为高度可伸缩的分布式搜索引擎,以及如何在您的环境中设置它。

十、传统扩展和 SolrCloud

第二章介绍了如何设置 Solr 的独立实例、核心管理、内存管理和其他管理任务。接下来的章节讲述了索引、搜索和 Solr 的其他高级特性。Solr 的这个单一实例非常适合于概念验证、开发、特性测试和简单的产品部署。但是当涉及到任务关键型部署时,您需要考虑性能、可伸缩性、可用性和容错等方面。

在本章中,您将了解 Solr 支持的各种模型,以使您的系统为生产做好准备。你可以选择一个最适合你需要的。您将首先探索传统模型,然后关注 SolrCloud,这是 Solr 提供的共同运行服务器集群的终极模型。

本章涵盖以下主题:

  • 独立实例
  • 分片
  • 主从架构
  • 分片和主从的混合体
  • 索尔鲁德
  • 常见问题

独立模式

独立模式是最简单的 Solr 设置:一个 Solr 服务器(也称为实例)运行在一个特定的端口上,以满足您所有的搜索需求。在这种模式下,文档被索引到同一个实例并在其上进行查询。独立实例是开发活动的首选模式,在这种模式下,您需要频繁地进行更改、重新加载和重新启动。如果高可用性并不重要,并且数据量有限,那么您也可以在生产环境中以这种模式运行。

Solr 引入了多核的概念,这样每个核都可以有自己的索引和配置,而不是有一个单独的 Solr 设置,每个内核运行在不同的端口上,用于不同类型的索引。Solr 中的多核类似于拥有多个表的数据库。

拥有多个内核可以让您轻松管理多个索引。通过 Solr 管理界面,可以在运行时轻松加载、卸载、重命名、交换和优化内核。对于索引和搜索,URL 应该包含核心名称;这就是所有需要的改变。

前面的章节已经探讨了内核、它们的用法和core.properties文件,所以我假设现在你已经理解了拥有多个内核而不是多个实例的好处。多核作为一项功能,并不局限于独立模式,可以在任何架构中设置。

图 10-1 展示了一个具有 N 个内核的独立 Solr 实例,该实例被请求用于索引和搜索。

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

图 10-1。

Stand-alone Solr instance with multiple cores

碎片

一旦开始索引越来越多的文档,索引大小就会增加,搜索性能也会受到影响。如果您的应用程序即将达到可接受的响应时间阈值,那么是时候进行分片了。

分片是将一个大的索引分成较小的分区的过程,这些分区在物理上分布在服务器上,但在逻辑上构成一个单元。索引被垂直划分并分布在 Solr 服务器网络上;每个单独的单元称为一个碎片。

假设您正在为一家制造商的文档编制索引,其中包含多个国家的销售点信息。如果索引很小,那么所有文档都可以放在一个 Solr 实例中。但是随着业务的增长,数据量也将增长,您可能希望将索引划分为碎片。分片策略将取决于您的需求和数据量。可以通过使用散列函数简单地对文档进行分片,或者分片可以基于定制的逻辑。图 10-2 展示了两种分片策略:一种是基于国家划分文档,如美国或英国;另一种是以地区为基础,如北美、欧洲、中东、非洲(EMEA)和亚太地区(APAC)。

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

图 10-2。

Concept of sharding

搜索应用程序应该知道 shard URLs,并可以根据需要查询全部或部分内容。这个请求应该发送给任何一个带有附加的shards参数的实例,该参数指定了要查询的其他碎片的列表。接收请求的实例将查询分发给在shards参数中提到的所有碎片,获取它们的结果,合并它们,并响应客户端。这种方法提供了更好的查询响应时间。

以下是使用多个碎片查询 Solr 的示例:

$ curl ’``http://localhost:8983/solr/core1/select?q=*:*&shards=localhost:8984/solr/core1,localhost:8985/solr/core1

图 10-3 描述了一个多共享架构,其中所有的请求都发送给在端口 8983 上运行的 Solr 实例,该实例将查询分配给其他 shards。值得注意的是,文档被分别索引到每个实例,而查询可以被发送到任何一个碎片进行分发。

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

图 10-3。

Sharding in Solr

以下是关于分片设计需要注意的一些要点:

  • 模式应该包含一个uniqueKey
  • 这种方法只提供分布式查询,不支持分布式索引。索引应用程序应该将用于索引的文档分别发送到每个碎片。
  • 逆文档频率是该碎片中的本地术语计数,而不是所有碎片的聚合值。如果术语分布是偏斜的,分数可能会受到影响。
  • Solr 将请求分布在各个分片中,因此应该考虑增加线程池的大小。

主从架构

在主-从架构中,索引和搜索的过程在不同的机器之间是分离的。所有文档都被索引到主服务器,主服务器将索引复制到从实例,客户端将搜索请求发送到任何从实例。这种体系结构有明确的任务分离:索引在主服务器上完成,搜索请求发送给从服务器。每个从服务器都包含主服务器索引的冗余副本,因此客户端可以向任何一个从服务器发送请求。或者,您可以引入一个负载平衡器,它将向客户端提供一个 URL,并在循环的基础上将搜索请求重定向到一个从属服务器。负载平衡器通常能够检测实例的故障,并向实时服务器发送请求。这种方法有助于扩展 Solr 来处理大量请求,还支持高可用性和容错。

在这种体系结构中,索引应用程序很简单,其过程与独立实例上的索引相同。对于搜索请求,通常在客户端应用程序之前有一个负载平衡器,在从属实例之间路由请求。

图 10-4 描述了一个简单的主从架构,其中所有的索引请求都发送给主设备,而搜索请求则发送给从设备。此外,您可以看到一个负载平衡器位于应用程序的前面,用于路由请求。

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

图 10-4。

Master-slave architecture Note

主实例能够处理搜索请求,但是使用专用的从实例进行搜索是一种规范。

为了建立一个主从架构,Solr 要求您进行一些配置更改,以支持从主服务器到从服务器的索引复制。除了复制更改之外,您可能希望优化主实例以进行索引,优化从实例以进行搜索。

复制过程由ReplicationHandler实现。为了让主从架构工作,这个处理程序应该在solrconfig.xml中注册。在处理程序中,您需要指定服务器类型(主服务器或从服务器)并使用适当的设置来获得所需的行为。

需要注意的是,主服务器不知道从服务器。了解主人的是奴隶。从属实例配置有主实例的 URL,它们以指定的时间间隔轮询主实例的更新。

掌握

主服务器的复制处理程序应该包含一个名为master的元素,从服务器应该包含一个名为slave的元素。表 10-1 显示了主机支持的复制参数的详细信息。以下是主实例的配置示例。

表 10-1。

Master-Slave Architecture: Master Configuration

| 名字 | 描述 | | --- | --- | | `replicateAfter` | 此属性指定应在何时触发复制的事件。从节点轮询主节点,如果发生了任何指定的事件,将会触发复制。支持的事件有`startup`、`commit`、`optimize`,可以配置多个事件。 | | `confFiles` | 除了索引之外,还可以复制配置文件。此元素指定要复制的文件的逗号分隔列表。只能复制`conf`及其子目录中的文件。仅当主服务器有新的索引要复制时,才复制修改的文件(如果更新了配置文件,但没有修改任何文档,则不会触发复制)。 | | `backupAfter` | 此参数与复制无关,但与备份有关。它指定应在哪个事件之后备份索引。该主题在第二章中有详细介绍。 | | `maxNumberOfBackups` | 此属性指定要维护的备份数量。 | | `commitReserveDuration` | 此参数指定从主机向从机传输 5MB 数据的最大允许时间。 |

<requestHandler name="/replication" class="solr.ReplicationHandler" >

<lst name="master">

<str name="replicateAfter">startup</str>

<str name="replicateAfter">commit</str>

<str name="backupAfter">optimize</str>

<str name="confFiles">schema.xml,stopwords.txt,elevate.xml</str>

</lst>

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

</requestHandler>

复制配置文件的规定使您免于在所有实例中应用更改的冗余工作。您应该在主实例中执行配置,复制会负责将其部署到从实例中。但是有一个挑战:使用这种架构,主实例和从实例的solrconfig.xml文件应该是不同的,并且您不能将主实例的副本复制到从实例。为此,您可以在文件名后添加一个冒号(:)分隔符,并指定复制到从属服务器时应该使用的名称。下面是一个将主实例中的solrconfig_slave.xml复制到从实例中作为solrconfig.xml的例子:

<str name="confFiles">solrconfig_slave.xml:solrconfig.xml,schema.xml</str>

要从conf中的子目录复制文件,可以如下指定相对路径。conf之外的文件不会被复制。

<str name="confFiles">schema.xml,velocity/template.html</str>

这样,您就可以设置主服务器了。

奴隶

从属实例应该知道它的主实例以及它应该轮询它的频率。表 10-2 显示了从站支持的复制参数的详细信息。以下是在从属实例中启用复制的配置:

表 10-2。

Master-Slave Architecture: Slave Configuration

| 名字 | 描述 | | --- | --- | | `masterUrl` | 指定主复制处理程序的 URL。 | | `pollInterval` | 指定从设备轮询其主设备以检查更新的时间间隔(以 HH:mm:ss 表示)。更高的间隔将延迟从设备中反映的变化。复制也可以通过复制 API 触发,在这种情况下,您可能希望禁用轮询。可以通过删除此参数来禁用轮询。 | | `httpConnTimeout` | 指定 HTTP 连接超时。 | | `httpReadTimeout` | 指定 HTTP 读取超时。 | | `httpBasicAuthUser` | 如果主服务器需要认证,可以在该参数中指定用户名。 | | `httpBasicAuthPassword` | 指定与用户名对应的密码。 |

<requestHandler name="/replication" class="solr.ReplicationHandler">

<lst name="slave">

<str name="masterUrl">``http://localhost:8983/solr/core1/replication</str

<str name="pollInterval">00:00:30</str>

</lst>

</requestHandler>

主从碎片

分片允许 Solr 处理分布式请求,而主从架构有助于实现复制。如果您的应用程序需要这两者,您可以构建一个混合系统,其中每个碎片可以有一个主碎片用于索引,一组从碎片用于搜索。

图 10-5 显示了一个同时使用分片和主从模型的设计。该模型包含两个碎片,每个碎片都有自己的主实例和从实例。

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

图 10-5。

Hybrid of shards with master-slave architecture

这种方法是所有传统方法中最复杂的,但是它有一些限制。这种 Solr 扩展模型非常复杂,难以管理、监控和维护。

为了解决传统架构的局限性,引入了 SolrCloud。如果您计划实现混合方法,我建议您考虑 SolrCloud,这将在下一节中介绍。

索尔鲁德

在前面的小节中,您看到了扩展 Solr 并将其组合在一起的传统方法,这种方法有以下局限性:

  • 索引过程不是分布式的,所有文档都应该被索引到主实例。主服务器可能是单点故障;如果主机停机,步进必须停止。
  • 客户端应用程序需要包含一些逻辑,比如文档的路由和负载平衡。
  • 通过复制过程很难实现实时更新,因为从实例会频繁地轮询主实例,并且索引会复制到从实例。
  • 不支持反向文档频率。

SolrCloud 是在 4.0 版中引入的,比目前所有的模型都要先进一步。它解决了以前的限制,并提供了真正的分布式体系结构。

SolrCloud 提供了高度可用和可伸缩的解决方案,并支持负载平衡、自动路由、乐观并发、故障转移和健壮性。

SolrCloud 建立了一个服务器集群,其中的数据被分割成碎片并分布在多台机器上。它还维护多个碎片副本,以实现容错和高可用性,并且可以在集群中的任何碎片上索引和搜索文档。集群自动将文档分发到适当的碎片上进行索引,并同步副本。类似地,集群中的任何碎片都可以接受搜索请求进行处理。集群负责路由、合并和可用性。如果任何故障节点变为可用,群集会自动将其同步。客户是从所有的实现细节和分配和协调过程中抽象出来的。

您甚至可以在一个有数百台机器的农场上建立这种架构,并从 Solr 管理界面集中管理它。该模型甚至在监控和维护方面提供了很多便利,甚至可以在商用机器上运行。

理解术语

SolrCloud 引入了一些新的术语,以及一些大家可能很熟悉但需要从全新角度理解的术语。第二章简要介绍了一些术语,本节将进一步阐述。事不宜迟,我们开始吧。

结节

节点,通常称为服务器,是在机器上的特定端口上运行 Solr 的 Java 虚拟机实例。图 10-6 描绘了一个 SolrCloud 节点。

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

图 10-6。

A SolrCloud node

一组两个或更多的节点就是一个集群。集群将外部世界从实现细节中抽象出来,并使节点看起来像一个单元。可以在集群中添加或删除节点,以动态扩展或收缩集群,客户端不需要知道任何关于这种变化的信息。客户端只需向集群发出请求,如果得到确认,客户端就不需要担心其他任何事情。确保请求的原子性和进一步处理是集群的工作。

图 10-7 表示一个 SolrCloud 集群,它对索引和搜索应用程序来说是一个单独的单元。

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

图 10-7。

SolrCloud cluster

集群依靠 ZooKeeper 来协调和管理配置。动物园管理员将在本章后面讨论。

核心

在本书中,我们一直将核心称为单一索引。如果你想创建两种类型的索引,你需要创建两个核心。但是在 SolrCloud 中,情况有所不同。一个索引在物理上可以驻留在多个节点上,但在逻辑上可以分布在几台机器上。节点中的每个物理索引可以被视为一个核心,每个逻辑索引可以由多个核心组成。

Note

核心有多重含义。在独立的 Solr 实例中,一个核心对应一个逻辑/物理索引。在 SolrCloud 中,集合定义逻辑索引,而核心对应于驻留在节点中的物理索引,它是逻辑索引的一部分。

募捐

集合是簇中完整的逻辑索引。一个集群可以有多个集合(多种类型的索引),一个集合可以有多个分片,一个分片可以有多个副本,这些副本可以分布在集群中的多个节点上。

陶瓷或玻璃碎片

分片的概念对 SolrCloud 来说并不陌生。你也可以从传统的分布式体系结构方面了解到这一点。在独立方法中,所有文档都在同一台机器上编制索引。在这种方法中,索引分布在多个碎片中,这些碎片维护一部分逻辑索引的物理副本。用户查询被分发给所有的碎片进行处理,并最终在响应用户之前被其中一个碎片合并。

传统的分片方法要求客户端应用程序包含分片逻辑,但 SolrCloud 会自动处理。

图 10-8 显示了 Solr admin UI 中的云标签,它有一个包含两个碎片的hellocloud集合。该设置包含一个在端口 8983 和 7574 上运行的双节点集群。这个示例在同一台机器上的不同端口上运行两个实例。在生产环境中,您可能希望在不同的机器上运行节点。

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

图 10-8。

Sharding in SolrCloud

复制品

副本是作为 Solr 核心在节点中运行的碎片的物理副本。假设一个碎片只有一个物理副本,如果它关闭了,Solr 将只能处理部分结果。为了实现高可用性,需要维护 shard 的冗余副本,每个副本称为一个副本。每个副本对应于 Solr 管理控制台中的一个核心。

在管理界面中,副本由连接碎片的圆圈表示。如果一个圆圈被标为绿色,它是活动的,可以接受请求。图 10-8 显示了一个例子,其中每个碎片有两个副本。

领导者

每个碎片的一个副本被选为领导者,负责协调索引和查询请求。领导者确保所有副本都得到更新并且同步。他们以先到先得的方式当选。如果主服务器关闭,其中一个可用副本将自动升级为主服务器。

图 10-9 显示了一个运行四个节点的典型 SolrCloud 集群。该集群代表两个碎片的集合,一个以名字 S1 开始,另一个以名字 S2 开始。每个以 S 开头的方框代表一个复制品。以 L 结尾的复制品是各自碎片的领导者,所有 S1(或 S2)盒子放在一起构成碎片。

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

图 10-9。

A typical SolrCloud cluster

与主从架构不同,在主从架构中,主机是在设置实例时配置的,领导者是动态选举的。群集中任何节点上的任何副本都可以被指定为领导者。

在 Solr 管理控制台中,领导者由实心空白圆圈表示。图 10-8 显示在端口 7574 上运行的节点上的副本是shard1shard2的领导者。

启动 SolrCloud

在第二章中,你学会了在单机模式下设置 Solr,这与设置 SolrCloud 是不同的。在这里,您将首先了解如何启动 SolrCloud 实例,这显然是执行任何其他操作的先决条件。一旦启动了 SolrCloud 实例,就需要执行其他设置步骤。启动和设置 SolrCloud 最简单的方式是在交互模式下。如果您以前没有使用 SolrCloud 的经验,我建议您以这种模式启动 Solr,尝试一下它的特性和行为,一旦您对它感到满意,就可以使用标准方法启动 SolrCloud。

以下是设置 SolrCloud cluster 以索引和搜索文档时需要执行的一般步骤。

Start Solr in cloud mode   Design the schema and create the required configurations files. Alternatively, you can use a named configset or choose to go schemaless.   SolrCloud doesn’t reads the schema.xml and other configurations from conf directory but relies on ZooKeeper for it. Solr bundles a ZooKeeper and by default starts it for you. Alternatively, you can start a separate ZooKeeper ensemble. Upload the configurations to the preferred ZooKeeper.   Create a collection by specifying the ZooKeeper path and other required information.

对话方式

bin/solr脚本允许您在本地工作站上快速启动一个小型节点集群,而不需要您执行前面列表中提到的步骤。交互过程自动在本地机器的不同端口上实例化多个节点,并设置碎片及其副本和配置集以创建集群。

如果你选择尝试无模式,设置 Solr 就像下载和启动一样简单,你就可以尽情摇滚了!

对于交互模式,按如下方式运行脚本:

$ bin/solr start -e cloud

这个脚本将为您提供一个以交互方式运行的 SolrCloud 实例。设置过程简单明了。即使您对所有选项都按 Enter 键,您也将建立一个名为gettingstarted的双节点集群。

如果您想绕过交互步骤,从默认设置开始,运行带有-noprompt选项的脚本:

$ bin/solr start -e cloud -noprompt

成功启动后,终端将打印以下消息:

"SolrCloud example is running, please visit``http://localhost:8983/solr

标准模式

在这种模式下,你需要自己完成所有的艰苦工作。首先,通过向solr脚本传递一个附加参数来启动 SolrCloud 实例,如下所示:

$ bin/solr start -c -z <zkHost>

  • -c:该参数指示 Solr 以云模式启动实例。或者,-cloud也可以作为选项提供。
  • -z:默认情况下,SolrCloud 启动一个嵌入式 ZooKeeper 实例,其端口号比当前实例端口号多 1000。例如,如果您在端口 8983 上启动 Solr 节点,那么嵌入式 ZooKeeper 将在端口 9983 上启动。要指定一个外部 ZooKeeper 系综,可以使用-z参数。

运行此命令后,SolrCloud 应该会成功启动。但是您仍然需要创建集合;按照“创建收藏”的步骤。完成后,您就可以开始添加文档并在集合中搜索结果了。

如果您已经设置了配置和$SOLR_HOME,那么可以为solr脚本提供-s参数,该参数指向您的$SOLR_HOME目录。下面是一个使用$SOLR_HOME路径启动实例的例子:

$ bin/solr start -c -s "/home/dev/solr-5.3.1/server/solr"

假设您正在启动另一个节点,或者想要引用现有的 ZooKeeper 集合。这里有一个例子:

$ bin/solr start -c -s "/home/dev/solr-5.3.1/server/solr" -z localhost:9983 -p 8984

通过提供-help参数,参考完整手册了解bin/solr脚本中可用的启动选项:

$ bin/solr start -help

重新启动节点

重新启动节点的选项类似于启动节点的选项。此示例重新启动在端口 8983 上运行的节点:

$ bin/solr restart -c -p 8983 -s "/home/dev/solr-5.3.1/server/solr"

创建收藏

要创建集合,有几个先决条件。首先,应该定义模式和其他配置。您甚至可以使用命名的配置集。第二,集合中碎片的数量应该在创建集合时确定。虽然你可以把一个碎片一分为二,但是你不可能动态的扩展或者收缩一个碎片。我知道,很难预测语料库的大小,所以确定准确的碎片数量可能是不可能的。在这种情况下,您可以创建大约数量的碎片,然后在需要时进行分割。

SolrCloud 节点从 ZooKeeper 读取配置,因此集合使用的配置必须上传到 ZooKeeper。Solr 提供了一个将收藏目录上传到 ZooKeeper 的脚本。这将在下一节讨论。

以下是创建集合的示例命令:

$ bin/solr create -c hellocloud -d sample_techproducts_configs

-n hellocloud -shards 2 -replicationFactor 2

  • -c指定收藏名称。
  • -d指定应该上传到 ZooKeeper 并使用的配置目录的路径或现有配置集的名称。
  • -n指定 ZooKeeper 中收藏的名称。如果未指定该参数,将考虑参数-c的值。
  • -shards指定要创建的碎片数量。
  • -replicationFactor指定为碎片创建的副本数量。如果因子指定为 2,将创建一个引线和一个复本。

您也可以选择使用create_collection选项。要查看可用于创建集合的所有选项,请按如下方式运行脚本:

$ bin/solr create_collection -help

SolrCloud 提供了用于管理集合的集合 API。如果您想使用 API 创建一个集合,可以按如下方式完成:

$ curl ’``http://localhost:8983/solr/admin/collections

action=CREATE&name=hellocloud&collection=hellocloud&numShards=2&replicationFactor=2’

使用集合 API,您不能指定配置集或配置目录。相反,它应该被预先加载到 ZooKeeper,并且它的名字应该在collection.configName参数中提供。

Note

参考 Solr 官方参考指南,了解 Solr 在 https://cwiki.apache.org/confluence/display/solr/Collections+API 提供的一整套集合 API。

上传到动物园管理员

在前面的例子中,您看到 Solr 自动将配置上传到 ZooKeeper。如果想要手动将配置上传到 ZooKeeper,Solr 提供了 ZooKeeper 命令行接口(也称为 zkCLI)。zkCLI 脚本可以位于$SOLR_DIST/server/scripts/cloud-scripts目录中。zkcli.sh应该用在*nix 机器上,zkcli.bat应该用在 Windows 机器上。

这个例子使用zkcli.sh将配置上传到 ZooKeeper:

$ ./zkcli.sh -cmd upconfig -zkhost  localhost:9983 -confname hellocloud

-solrhome /home/dev/solr-5.3.1/server/solr

-confdir /home/dev/solr-5.3.1/server/solr/configsets/hello_configs/conf

Note

关于 https://cwiki.apache.org/confluence/display/solr/Command+Line+Utilities 的完整命令集实用程序,请参考 Solr wiki。

删除收藏

可以使用如下的bin/solr脚本从集群中删除集合:

$ bin/solr delete -c hellocloud -deleteConfig false -p 8983

  • -c指定要删除的收藏的名称。
  • 指定 ZooKeeper 应该保留配置还是删除它。
  • -p指定删除集合时要引用的本地实例上的端口。

除了使用bin/solr脚本删除集合,您还可以使用集合 API 删除集合,如下所示:

$ curl ’``http://localhost:8983/solr/admin/collections?action=DELETE&name=hellocloud

为文件编制索引

SolrCloud 简化了客户端应用程序的索引过程。在传统的分布式体系结构中,客户端程序将所有文档索引到主实例,主实例被复制到从实例进行搜索。此外,客户端应用程序负责将文档路由到适当的碎片。SolrCloud 自动决定将文档发送到哪个碎片进行索引,而无需客户端担心主实例或路由到合适的碎片。

客户端程序将文档发送到集群中的任何碎片进行索引。如果接收请求的碎片是一个副本,它将文档发送给它的领导者进行处理。领导者识别文档应该被索引到的碎片,并将请求发送到该碎片的领导者。该领导者将文档附加到事务日志,对其进行索引,然后将其路由到其所有副本进行索引。如果更新在主服务器上成功,但在副本服务器上失败,客户端仍会得到成功状态的响应。当失败的副本恢复时,它们可以通过与主服务器同步来进行协调。

在传统的主从架构中,所有文档都在主服务器上建立索引,主服务器将索引段复制到从服务器上。从属实例中没有索引进程。相比之下,在 SolrCloud 中,领导者索引文档,然后将其发送给副本进行索引。领导者通常传输文档而不是段,并且索引在领导者和复制品中完成。

在 SolrCloud 中,通常避免从客户端应用程序触发提交,而应该依靠自动提交。如果有多个应用程序索引文档,并且您发现很难确保它们都不会触发提交,那么您可以将IgnoreCommitOptimizeUpdateProcessorFactory注册到流程链,以禁止来自客户端应用程序的提交。以下是禁用提交的配置示例:

<updateRequestProcessorChain name="preprocessor">

<processor class="solr.IgnoreCommitOptimizeUpdateProcessorFactory">

<int name="statusCode">403</int>

<str name="responseMessage">External commit is disabled on this collection!</str>

</processor>

<processor class="solr.LogUpdateProcessorFactory" />

<processor class="solr.DistributedUpdateProcessorFactory" />

<processor class="solr.RunUpdateProcessorFactory" />

</updateRequestProcessorChain>

负载平衡

SolrCloud 会自动对请求进行负载平衡,您不需要像传统架构那样在前端安装负载平衡器。但是仍然建议使用负载平衡器或智能客户端向集群发出请求。假设您向同一个节点发出所有请求,该节点会将请求转发给其领导者,而该领导者又会确定应该处理请求的分片,然后将其转发给该分片的领导者。如果您的客户端应用程序本身知道请求应该发送到哪个碎片,就可以避免初始路由,从而避免网络不堪重负。像 SolrJ 这样的智能客户端提供了一个负载平衡器,它在循环的基础上分发请求。

如果你把所有的请求发送到同一个碎片,而那个碎片出现故障,所有的请求都会失败。为了解决这种单点故障,SolrJ 使您能够提供 ZooKeeper 的引用,而不是 Solr 节点。因为 ZooKeeper 总是知道集群的状态,所以它永远不会将请求定向到不可用的节点。为了避免这种单点故障,对 SolrJ 的请求应该配置如下:

import org.apache.solr.client.solrj.impl.CloudSolrServer;

import org.apache.solr.common.SolrInputDocument;

// Provide ZooKeeper address as argument

CloudSolrServer server = new CloudSolrServer("localhost:9983");

server.setDefaultCollection("hellocloud");

// Create document

SolrInputDocument document = new SolrInputDocument();

document.addField( "productId", "Mac152016");

document.addField( "name", "Apple MacBook Pro");

server.add(document);

// Let Solr autocommit

// server.commit();

文档路由

默认情况下,SolrCloud 使用哈希函数在碎片之间分发文档。如果您愿意,可以在创建集合时通过指定参数router.name=compositeId来微调行为。该参数还要求指定参数numShards,该参数定义了应该在其中分发文档的碎片数量。

如果使用了compositeId路由器,那么文档的uniqueKey应该以一个字符串为前缀,Solr 将使用这个字符串来生成一个散列,以标识文档应该被索引到的碎片。前缀后面应该跟一个感叹号(!)以区别于uniqueKey。假设原来的uniqueKeyMac152016,并且您想要根据制造商来路由文档。在这种情况下,您可以在文档前面加上制造商名称,例如Apple!Mac152016

Solr 最多支持两级路由,中间用感叹号隔开。例如,如果您想要基于制造商和产品类别进行路由,那么文档 ID 应该修改为类似于Apple!Laptop!Mac152016的内容。

如果使用compositeId传送文件,那么在搜索时,应该在附加参数_router_中提供前缀信息。这些信息将帮助 Solr 将查询路由到正确的分片,而不是发送到所有的分片。以下是查询路由的一个示例:

使用_router_参数,一个示例查询将是q=mac&_router_=Apple

$ curl ’``http://localhost:8983/solr/hellocloud/select?q=*:*&_router_=Apple

使用事务日志

Solr 使用事务日志来降低数据丢失的风险。在 Solr 4.0 中引入,它是一个只追加的预写日志,记录每个片上的所有索引操作。在 SolrCloud 中,所有碎片,无论是主碎片还是副本碎片,都有自己的日志文件。

事务日志也称为tlog,创建在data目录内的tlog目录中(与index目录相邻)。图 10-10 显示了tlog的目录结构。

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

图 10-10。

Solr transaction log directory

Solr 将所有文档附加到事务日志的末尾,并在硬提交时翻转。当请求是原子更新时,tlog记录旧值和新值,这允许您回滚文档。如果在很长时间后执行硬提交,或者以很高的速度对文档进行索引,文件可能会变得非常大,恢复成本也会很高。

执行碎片运行状况检查

索引客户端可以发送表示最小复制因子的附加参数min_rf,以了解碎片是否处于降级状态以及文档的复制因子是否低于预期阈值。如果您想采取任何预防措施,此信息可能会有所帮助。

查询结果

向 SolrCloud 发送查询几乎和往常一样简单,因为集群对客户端应用程序隐藏了所有实现细节。SolrCloud 处理搜索请求的步骤如下:

The client application sends a request to any node in the cluster.   The receiving node determines the eligible shards and sends the query to any available replica of each of them.   Each replica returns the matching documents to the receiver shard.   The receiver shard merges the results and triggers another request to the replicas, for getting the stored values for the documents to be returned.   The receiver responds to the client with the matching documents.   Note

查询请求不会被路由到碎片领导者,而是可以由任何碎片复制品来处理。

建议使用智能客户端(如 SolrJ)或负载平衡器来查询集群。Solr 内置了对负载平衡器的支持,但是如果所有的查询都被发送到同一个节点,它将不得不完成所有的路由,并且可能会被请求淹没。反过来,负载平衡器会将查询分布到各个节点上,从而提供更好的性能。像 SolrJ 这样的智能客户端从 ZooKeeper 获取集群状态,并将请求重新发送到一个活动节点。

在完整索引上搜索结果的过程和以前一样,您可能甚至不知道您正在查询一个集群。如果您想要查询索引的一部分(一组指定的碎片),您所需要做的就是传递一个额外的参数shards,就像传统的碎片架构一样。下面是它们各自的一个例子:

  • 查询整个集合/完整索引。$ curl ’``http://localhost:8983/solr/hellocloud/select?q=*:*
  • 通过用逗号分隔的 URL 列表指定shards参数来查询一组特定的碎片。$ curl ’ http://localhost:8983/solr/hellocloud/select?q=*:*&shards=localhost:8983/solr ,localhost:7574/solr’
  • 通过使用管道(|)操作符分隔碎片 URL,查询一组特定的碎片并在其副本之间进行负载平衡。$ curl ’ http://localhost:8983/solr/hellosolr/select?q =*:* &shards=localhost:8983/solr|localhost:8483/solr’

Note

像 curl 这样的简单客户端可以用于评估和开发,但是建议在生产中使用像 SolrJ 这样的智能客户端。

如果您想获得关于处理查询的碎片的信息,请将附加参数shards.info=true传递给请求。

执行恢复

失败是常见的,尤其是当您运行大型集群时,在商用机器上也是如此。当故障节点恢复时,SolrCloud 通过自动与其他碎片同步来从这种故障中恢复。

当停机的非主节点恢复时,它与分片主节点同步,并且只有在它更新后才开始接受请求。如果要重播的文档数量很大,恢复过程可能会很长。

当一个领导者倒下时,Solr 指定另一个复制品作为领导者。当此故障节点变为可用时,它会与当前的主节点同步,以获取它可能会错过的更新。

值得注意的是,当副本从故障中恢复时,它从主服务器的tlog中获取文档并重放它。但是,如果它发现自己落后太多,就像传统方法一样复制数据段。

碎片分裂

Solr Collections API 允许您将一个分片分成两个分区。现有碎片的文档被分成两部分,每一部分都被复制到一个新的碎片中。现有的碎片可以在以后方便的时候删除。

下面是一个将hellocloud集合中的shard1分割成两部分的例子:

$ curl ’ http://localhost:8983/solr/admin/collections?action=SPLITSHARD

&collection=hellocloud&shard=shard1’

添加副本

SolrCloud 提供了扩展和收缩集群的灵活性。假设您想要扩展系统以处理更多的查询,或者您有了新的硬件;在这种情况下,您可能希望向群集中添加一个节点,并为其分配副本。

您可以使用 Solr Collections API 向集合中添加副本。本例向集合hellocloud中的碎片shard1添加一个节点:

$ curl ’ http://localhost:8983/solr/admin/collections?action=ADDREPLICA

&collection=hellocloud&shard=shard1&node=192.10.8.120:7573_solr’

动物园管理员

Apache ZooKeeper 是一个成熟快速的开源服务器,广泛应用于分布式系统中,用于协调、同步和维护共享信息。它能够每秒处理数十万个事务,在包括 Hadoop、Spark 和 HBase 在内的分布式系统中非常流行。

ZooKeeper 在内存中维护信息,并构建一组服务器来维护这些配置的冗余副本,以实现快速性能和高可用性。主服务器被称为领导者,并有一组冗余的追随者。图 10-11 描绘了一个 ZooKeeper 系综以及它如何适应 SolrCloud 架构。

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

图 10-11。

ZooKeeper ensemble Note

更多详情请参考 Apache ZooKeeper 官方网站: https://zookeeper.apache.org/

在 SolrCloud 中,集群使用 ZooKeeper 来协调碎片并维护配置。概括地说,SolrCloud 将 ZooKeeper 用于以下用途:

  • 配置管理:你在core/conf目录下的所有配置文件,包括solrconfig.xmlschema.xml,都在 ZooKeeper 中集中维护。所有配置的节点都引用 ZooKeeper。配置的任何变化都应该上传到 ZooKeeper。
  • 集群协调:ZooKeeper 维护关于集群、其集合、活动节点和副本状态的信息,这些信息由节点监视以进行协调。
  • 首领选举:每个碎片应该有一个首领,并且可以有多个副本。如果领导者倒下,复制品中的一个必须被选为领导者。动物园管理员在这个领袖选举过程中扮演着重要的角色。

Solr 与 ZooKeeper 捆绑在一起,这为评估、开发和初始测试提供了一个很好的起点。但是因为 ZooKeeper ensemble 需要超过一半的服务器,所以建议在生产环境中运行外部 ensemble。法定人数是一个组织中开展业务的最少成员的集合。— www。词汇。com

Note

关于在 https://cwiki.apache.org/confluence/display/solr/Setting+Up+an+External+ZooKeeper+Ensemble 建立外部动物园管理员合奏,请参考 Solr 的官方指南。

常见问题

本节涵盖了一些与 SolrCloud 相关的常见问题。

为什么我的数据/tlog 目录的大小急剧增长?我该怎么处理?

该目录包含原子更新和 SolrCloud 使用的事务日志。所有索引更新都记录在这些文件中,并在硬提交时被截断。如果您的系统高速索引,但硬提交很少执行,文件会变得很大。

您可以通过更频繁地执行自动提交来控制tlog的大小。下面是您如何在solrconfig.xml中配置它:

<autoCommit>

<maxTime>${solr.autoCommit.maxTime:30000}</maxTime>

<maxTime>${solr.autoCommit.maxDocs:100000}</maxTime>

<openSearcher>false</openSearcher>

</autoCommit>

我可以完全禁用事务日志吗?会有什么影响?

您可以通过注释掉solrconfig.xml中的以下部分来禁用tlog:

<updateLog>

<str name="dir">${solr.ulog.dir:}</str>

<int name="numVersionBuckets">${solr.ulog.numVersionBuckets:65536}</int>

</updateLog>

强烈建议不要禁用事务日志。SolrCloud 的高可用性,以及原子更新和近实时搜索等功能都依赖于tlog,禁用它将影响对这些功能的支持。

我最近从传统架构迁移到了 SolrCloud。在 SolrCloud 中,有什么我应该小心和不要做的事情吗?

有了 SolrCloud,Solr 中的许多东西都发生了变化,通读本章会让您对这些变化有一个大致的了解。以下是你应该注意的一些关键事项:

  • 您应该避免从客户端程序触发提交,而是使用 Solr 的autocommitsoftCommit特性。
  • 总是优雅地停止节点。如果你一直在做kill -9,停止它。
  • 避免搜索所有碎片。

我正在迁移到 SolrCloud,但是它无法将配置上传到 ZooKeeper。原因可能是什么?

ZooKeeper 将配置的大小限制在 1MB 以内。如果您的配置集太大,ZooKeeper 将无法上传。由于巨大的同义词文件或集成分类法的定制插件,conf目录的大小通常很大。您需要一个更好的数据源来解决这个限制。

摘要

在本章中,您了解了 Solr 为可伸缩性提供的各种传统方法。根据您的需求,您可以选择这些方法之一或它们的组合。您还了解了这些方法的局限性以及引入 SolrCloud 的原因。

本章还介绍了 SolrCloud 的突出特性及其基本概念,以及这些特性如何革新 Solr 中的分布式伸缩。您还了解了动物园管理员的概况。

十一、语义搜索

您已经读到了本书的最后一章,在这一过程中,您已经了解了 Solr 的重要特性以及使用它的基本原理。在前面的章节中,您还了解了信息检索概念和相关性排名,这对于理解 Solr 的内部原理以及评分的方式和原因是必不可少的。这些知识对于您大部分时间要做的事情是不可或缺的:调整文档相关性。有了所有这些信息,您应该能够开发一个有效的搜索引擎来检索与查询相关的文档,对它们进行适当的排序,并提供增加用户体验的其他功能。

到目前为止,一切顺利,但用户期望更多。如果你看看市场上的一些搜索应用程序,它们正在实现许多创新功能,并插入不同的框架和组件,以将搜索体验提升到一个新的水平。由于期望值很高,所以需要超越关键词的匹配来理解底层语义和用户意图。例如,谷歌也像一个问答系统。它运用巨大的智能来理解查询的语义,并相应地采取行动。如果您分析您的查询日志,您会发现相当多的查询包含用户的意图,而不仅仅是关键字,尽管这取决于领域。

此时,您应该能够开发一个不错的基于关键字的搜索引擎,但是有一些限制。当你把所有这些放在一起时,系统不会理解语义。例如,如果您的应用程序面向医疗领域,那么用户查询心脏病发作将无法检索到心脏骤停的结果,而这可能是医疗从业者更感兴趣的。

语义搜索通过理解用户意图和术语的上下文含义来解决基于关键字的搜索的局限性。可以以多种方式利用所获得的知识来提高搜索准确度和相关性排名。

语义搜索是一个高级而广泛的话题,介绍它的文本分析技术需要一本专门的书。在本章中,您将了解一些最简单形式的技术,以及可用于进一步探索的参考资料。本章涵盖以下主题:

  • 基于关键字的系统的局限性
  • 语义搜索简介
  • 构建语义功能的常用工具
  • 将语义能力集成到 Solr 的技术
  • 识别标记的词性
  • 从非结构化数据中提取命名实体,如人员、组织和位置
  • 语义丰富

关键词系统的局限性

基于关键字的文档排序基本上依赖于关于查询术语的统计信息。尽管它们对于许多用例是有益的,但是如果用户未能制定适当的查询或提供意图查询,它们通常不会为用户提供有价值的结果。如果用户在搜索时多次改写同一个查询,这意味着需要扩展搜索引擎以支持高级文本处理和语义功能。计算机科学家汉斯·彼得·鲁恩描述了关键字系统的局限性:

This fairly simple argument about "importance" avoids linguistic meanings such as grammar and syntax ... and pays no attention to the logical and semantic relations established by the author.

基于关键字的系统的主要限制可以分类如下:

  • 上下文和意图:基于关键字的系统不知道上下文,也不考虑文档的认知特征。对白色正式衬衫的查询表明用户意图,但是关键字系统可能检索到与用户期望无关的文档。类似地,在音乐搜索引擎中,对今年热门歌曲的查询是纯粹的意图查询,基于关键字的系统可能最终检索到包含这些标记的专辑或标题,这与用户正在寻找的内容无关。
  • 重要术语:基于关键字的搜索引擎在统计信息的基础上确定术语的重要性和意义,而忽略底层语义。
  • 同义词:基于关键字的引擎检索包含匹配标记的文档,但忽略语言学上指代同一事物的术语。这些被系统视为不相关的被忽略的标记实际上可能更相关,如在前面的心脏病发作的例子中。
  • 你可能会争论synonyms.txt解决了同义词的问题,但是这有两个限制。首先,该文件应该手动创建,并且仅限于手工制作的同义词。其次,它忽略了语义和一个词的同义词可以根据上下文而不同的事实。
  • 多义性:在英语中,单词是多义的(一个单词可以有不同的意思)。synonyms.txt中定义的同义词对于这样的单词可能会出错。例如,如果在synonyms.txt文件中将心脏映射到情绪,那么查询心脏病发作将扩展到情绪发作,而实际上它应该扩展到心脏停搏。
  • 非结构化数据:非结构化数据基本上是供人们使用的,其中隐藏着大量的信息。基于关键字的系统未能充分利用这些非结构化数据中的可用知识。

语义搜索

语义搜索是指一套解释意图、上下文、概念、意义和术语之间关系的技术。这个想法是开发一个遵循认知过程的系统,以类似于我们人类的方式理解术语。Technopedia.com 给出了语义搜索的定义:

Semantic search is a data search technology. In this technology, the purpose of search query is not only to find keywords, but also to determine the intention and contextual meaning of words used by a person to search.

在搜索应用程序中利用语义功能的潜力是无限的。您利用它们的方式在很大程度上取决于您的领域、数据和搜索需求。例如,谷歌使用语义功能来传递答案,而不仅仅是链接。图 11-1 展示了 Google 语义能力的一个例子,它精确理解用户意图并回答查询。

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

图 11-1。

An example of the question-answering capability of Google

从历史上看,搜索引擎是为满足基于关键字的查询而开发的,但由于其局限性,这些引擎也提供了高级搜索功能。这在某些垂直领域是可以接受的(例如,法律搜索),但是很少有用户觉得这有吸引力。用户更喜欢单个盒子,因为它易于使用和简单。由于搜索框是开放式的(你可以键入任何你想要的),用户用自然语言提供查询,使用日常生活中的语言学。早期谷歌的主页上有高级搜索选项,但在 2011 年被隐藏了。谷歌的高级搜索现在可以在它的主页设置中找到,需要额外点击,或者你需要去 www.google.com/advanced_search 。在高级搜索中,您可以提供有关查询术语及其适用字段的详细信息。它通常被图书管理员、律师和医疗从业者所偏爱。

图 11-2 显示了一个智能搜索的例子,由 Amazon.com 为用户查询 white formal shirt for men 执行。搜索引擎通过使用内置的语义功能找到您想要的东西。

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

图 11-2。

An example of query intent mining in Amazon.com

语义搜索技术通过使用人工智能、自然语言处理和机器学习等技术来对文本进行深度分析。在这一章中,你将学习其中的一些,以及如何将它们集成到 Solr 中。

语义功能可以在索引和搜索时集成到 Solr 中。如果您正在处理新闻、文章、博客、日志或电子邮件,这些数据将是非结构化或半结构化的。在这种情况下,您应该从文本流中提取元数据和可操作的信息。由于非结构化数据是为人类消费而创建的,因此机器可能很难解释,但通过使用自然语言处理等文本处理功能,可以提取有用的信息。

语义处理主要取决于以下几点:

  • 知识库:语义知识源包含关于与术语和概念相关的实体或事实的信息。知识可以作为本体、分类法、辞典、其他受控词汇、训练模型甚至一组规则来获得。这些知识可以由第三方供应商提供、由社区众包或内部开发。
  • 文本处理:这是指应用于知识的处理、推理规则和推理,以检测实体、建立逻辑连接或确定上下文。

工具

本节介绍了一些工具和技术,您可能希望在处理文本以丰富语义时对它们进行评估。您可以扩展 Solr 组件来插入适合您的文本处理需求的工具。在继续本节内容之前,请参考第三章的“文本处理”部分复习这些概念。

OpenNLP

Apache OpenNLP 项目提供了一套处理自然语言文本的工具,用于执行常见的 NLP 任务,如句子检测、标记化、词性标记和命名实体提取等。OpenNLP 为这些任务中的每一项提供了单独的组件。这些组件可以单独使用,也可以组合起来形成一个文本分析管道。该图书馆使用最大熵和感知器等机器学习技术来训练模型,并建立高级文本处理能力。OpenNLP 发布了一组通用模型,这些模型在一般用例中表现良好。如果您想要为您的特定需求构建一个定制模型,它的组件提供了一个用于训练和评估模型的 API。

该项目获得了 Apache 软件许可证的许可,可以在 https://opennlp.apache.org/ 下载。其他 NLP 库也是可用的,比如斯坦福 NLP,但是它们要么不是开源的,要么需要类似 GPL 的许可,这可能不符合许多公司的许可要求。

OpenNLP 的免费模型可以从 http://opennlp.sourceforge.net/models-1.5/ 下载。

OpenNLP 集成尚未在 Solr 中提交,也不能作为开箱即用的特性。更多详情请参考 https://wiki.apache.org/solr/OpenNLP 的 Solr wiki。在本章的后面,你会看到将 OpenNLP 与 Solr 集成的例子。

Note

有关整合的更多详情,请参考 JIRA https://issues.apache.org/jira/browse/LUCENE-2899

Apache 的游泳

如您所知,UIMA 代表非结构化信息管理架构,这是一个 Apache 项目,允许您开发可互操作的复杂组件,并将它们组合在一起运行。这个框架允许您开发一个分析引擎,可以用来从非结构化的文本中提取元数据和信息。

分析引擎允许您开发一个管道,您可以用它来链接注释器。每个注释器代表一个独立的组件或特性。注释器可以消费和产生一个注释,一个注释的输出可以输入到链中的下一个。该链可以通过使用 XML 配置来形成。

UIMA 的可插拔架构、可重复使用的组件和可配置的管道允许您扔掉整体结构,设计一个多阶段的过程,其中不同的模块需要建立在彼此的基础上,以获得一个强大的分析链。这也允许您向外扩展并异步运行组件。你可能会发现这个框架有点复杂;它有一个学习曲线。

来自不同供应商的注释可供使用,可以添加到管道中。Open Calias 和 AlchemyAPI 等供应商为文本处理提供了各种注释,但需要许可证。

UIMA 的 Solr 集成作为 contrib 模块提供,Solr 的丰富可以通过少量的配置更改来完成。关于 UIMA 集成,请参考位于 https://cwiki.apache.org/confluence/display/solr/UIMA+Integration 的 Solr 官方文档。

阿帕哈奇·斯坦布尔

Apache Stanbol 是一个基于 OSGi 的框架,它提供了一组用于推理和内容增强的可重用组件。它提供的额外好处是内置的 CMS 功能和供应,以持久化语义信息,如实体和事实,并定义知识模型。

没有可用的 Solr 插件,这个框架也不需要任何插件,因为 Stanbol 内部使用 Solr 作为文档存储库。它还使用 Apache OpenNLP 进行自然语言处理,并使用 Apache Clerezza 和 Apache Jena 作为 RDF 和存储框架。Stanbol 提供了一个管理链的 GUI,并提供了额外的功能,如网络服务器和安全功能。

如果您正在从头开始开发一个具有语义功能的系统,您可能想要评估这个框架,因为它为您提供了完整的套件。

应用的技术

语义搜索已经成为一个活跃的研究领域很长一段时间了,仍然是一个没有解决的问题,但在这个领域已经取得了很多进展。典型的例子是 IBM 的沃森;这个智能系统能够用自然语言回答问题,赢得了 2011 年的危险挑战。构建语义能力可能是一项相当复杂的任务,这取决于您想要实现什么。但是您可以使用简单的技术来提高结果质量,有时一点点语义就能帮您走很长的路。

图 11-3 概述了语义技术如何与不同的知识库相结合来处理输入文本和构建智能。从这些知识库中获得的信息可用于扩展术语、指示概念之间的关系、介绍事实以及从输入文本中提取元数据。第三章概述了这些知识库。在本节中,您将看到如何利用这些知识来执行智能搜索。

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

图 11-3。

Semantic techniques

在本书的前面,您已经了解到 Solr 通过使用向量空间模型等模型来对文档进行排序。该模型将文档视为一个单词包。对于用户查询,它基于诸如术语频率和逆文档频率之类的因素来检索文档,但是它不理解术语之间的关系。诸如此类的语义技术可以在 Solr 中应用,以检索更相关的结果:

  • 查询解析:语义技术可以应用于查询以挖掘用户意图。基于领域,可以通过开发文本分类系统或其他技术来挖掘用户意图。这个特性可以通过编写一个定制的查询解析器插入 Solr。基于对意图的理解,解析器可以重构查询,或者用同义词和其他相关概念扩展查询术语。重新制定可能是一项艰巨的任务,应该小心进行。
  • 文本分析:在执行文本分析时,可以用语义相关的单词来丰富标记,这与使用同义词过滤器工厂的方式类似。您可以通过编写一个定制的令牌过滤器来插入丰富内容,该过滤器可以在索引文档的字段时应用,也可以在查询结果时应用。本章稍后将介绍自动扩展同义词的示例实现。
  • 查询重新排序:意图挖掘甚至可以通过编写定制的重新排序查询来应用,您在第七章的中了解到了这一点,用于改变检索到的文档的顺序。与定制查询解析器的情况不同,查询重排序不会引入全新的文档。
  • 索引文档:结构化内容比非结构化内容更有价值。如果您正在索引非结构化数据,如书籍或期刊的文本,您应该对其进行结构化。您可以应用多种技术从这些内容中提取实体和隐藏的事实,并自动生成元数据。包含这些提取的实体的字段可以基于其重要性被提升,可以用于生成方面和控制可以表示结果的方式。在本章的后面,您将学习编写一个定制的更新请求处理器,用于从非结构化内容中自动提取元数据。
  • 排名模型:用于对文档评分的排名模型可以进行调整,以考虑术语的语义关系,但这不是一项简单的任务。我建议您考虑其他有助于文档排名的方法,通过使用现有的模型,例如通过应用提升或有效载荷。

图 11-4 描述了语义能力如何应用于 Solr 以提高相关性。

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

图 11-4。

Application of semantic techniques in Solr

接下来,您将看到各种自然语言处理和语义技术,它们可以集成到 Solr 中以提高结果的精确度。

词性标注

句子中的每个单词都可以被归类到一个词汇类别中,也称为词类。常见的词类包括名词、动词和形容词。这些可以进一步分类,例如,名词可以分为普通名词或专有名词。这种分类和次分类信息可以用来发现术语在上下文中的意义,甚至可以用来提取大量关于单词的有趣信息。我建议你对词类有一个公平的理解,因为这可能有助于你理解单词的重要性和用途。例如,名词用于标识人、地点和事物(例如,衬衫或乔),形容词定义一个名词的属性(例如红色或智能)。类似地,子类如普通名词描述一类实体(如国家或动物),专有名词描述实例(如 America 或 Joe)。图 11-5 提供了示例文本及其词性。在图 11-5 中,标签 NNP、VBD、VBN 和 In 分别指专有名词(单数)、动词(过去式)、动词(过去分词)和连词(介词或从属关系)。

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

图 11-5。

Part-of-speech tagging

有了对词类的理解,你就能清楚地认识到并非所有的词都同等重要。如果您的系统可以标记词类,这种知识可以用于根据上下文中标记的重要性来控制文档排名。目前,在索引文档时,您要么提升一个文档,要么提升一个字段,但是忽略了这样一个事实,即每个术语可能也需要不同的提升。在一个句子中,通常名词和动词更重要;您可以提取这些术语,并将其索引到不同的字段。这为您提供了一个包含更小且更集中的术语集的字段,在查询时可以为其分配更高的提升。Solr 特性如MoreLikeThis在这个领域使用更重要的标记会更好。您甚至可以在索引时对它们应用有效负载。

词性标注可能是许多类型的高级分析的必要功能和先决条件。语义丰富的例子,你将在本章后面看到,需要词性标记的单词,因为一个单词的含义和定义可能因其词性而异。词性标注器使用宾夕法尼亚树库项目中的标签来标注句子中单词的词性。

用于 POS 标记的 Solr 插件

在本节中,您将学习从被索引的文档中提取重要的词类,并将提取的术语填充到一个单独的 Solr 字段中。这个过程需要两个主要步骤:

Extract the part of speech from the text. In the provided sample, you’ll use OpenNLP and the trained models freely available on its web site. The model can be downloaded from http://opennlp.sourceforge.net/models-1.5/ .   Write a custom update request processor, which will create a new field and add the extracted terms to it.

接下来详细提供将期望的词性添加到单独的 Solr 字段所遵循的步骤。

Write a Java class to tag the part of speech by using OpenNLP. The steps for part of-speech tagging in OpenNLP are provided next. These steps doesn’t relate to Solr but is called by the plugin for getting the POS. Read the trained model using FileInputStream and instantiate the POSModel with it. The path of model is passed to the InputStream during instantiation. OpenNLP prebundles two POS models for English and a few other languages. You can use en-pos-maxent.bin, a model which is based on maximum entropy framework. You can read more details about maximum entropy at ​maxent.​sourceforge.​net/​about.​html. InputStream modelIn = new FileInputStream(fileName); POSModel model =  new POSModel(modelIn);   Instantiate the POSTaggerME class by providing the POSModel instance to its constructor. POSTaggerME tagger = new POSTaggerME(model);   POSTaggerME as a prerequisite requires the sentence to be tokenized, which can be done using OpenNLP tokenization. If this code had been part of an analysis chain, the sentence could be tokenized using Solr provided tokenizer. The simplest form of tokenization can even be built using Java String’s split() method, also used in this example, though it’s prone to creating invalid tokens. String [] tokens = query.split(" ");   Pass the tokenized sentence to the POSTaggerME instance, which returns a string array containing all the tagged parts of speech. String [] tags = tagger.tag(tokens);   Iterate over the array to map the tags to the corresponding tokens. You have populated the extracted tags to the PartOfSpeech bean that holds the token and its corresponding part of speech.   int i = 0; List<PartOfSpeech> posList = new ArrayList<>(); for(String token : tokens) {   PartOfSpeech pos = new PartOfSpeech();   pos.setToken(token);   pos.setPos(tags[i]);   posList.add(pos);   i++; }   Write a custom implementation of UpdateRequestProcessor and its factory method. Follow the next steps to add the terms with specified parts of speech to a separate field. (Refer to Chapter 5 if you want to refresh your memory about writing a custom update request processor.) Read the parameters from NamedList in the init() method of POSUpdateProcessorFactory, the custom factory, to populate the instance variables for controlling the tagging behavior. Also, set up the POS tagger that can be used for extraction, as mentioned in step 1. private String modelFile; private String src; private String dest; private float boost; private List<String> allowedPOS; private PartOfSpeechTagger tagger; public void init(NamedList args) {         super.init(args);         SolrParams param = SolrParams.toSolrParams(args);         modelFile = param.get("modelFile");         src = param.get("src");         dest = param.get("dest");         boost = param.getFloat("boost", 1.0f);         String posStr = param.get("pos","nnp,nn,nns");         if (null != posStr) {                 allowedPOS = Arrays.asList(posStr.split(","));         }         tagger = new PartOfSpeechTagger();         tagger.setup(modelFile); };   In the processAdd() method of POSUpdateProcessor, a custom update request processor, read the field value of the document being indexed and provide it to the tagger object for tagging the parts of speech. Create a new field and add the important tag to it.   @Override public void processAdd(AddUpdateCommand cmd) throws IOException {   SolrInputDocument doc = cmd.getSolrInputDocument();   Object obj = doc.getFieldValue(src);   StringBuilder tokens = new StringBuilder();   if (null != obj && obj instanceof String) {     List<PartOfSpeech> posList = tagger.tag((String) obj);     for(PartOfSpeech pos : posList) {       if (allowedPOS.contains(pos.getPos().toLowerCase())) {         tokens.append(pos.getToken()).append(" ");       }     }     doc.addField(dest, tokens.toString(), boost);   }   // pass it up the chain   super.processAdd(cmd); }   Add the dependencies to the Solr core library. <lib dir="dir-containing-the-jar" regex=" solr-practical-approach-\d.*\.jar" />   Define the preceding custom processor in solrconfig.xml. In the parameters, you need to specify the path of the model, the source field, the destination field, the parts of speech to be extracted, and the boost to be provided to the destination field. <updateRequestProcessorChain name="nlp">   <processor class="com.apress.solr.pa.chapter``11``.opennlp.POSUpdateProcessorFactory">     <str name="modelFile">path-to-en-pos-maxent.bin</str>     <str name="src">description</str>     <str name="dest">keywords</str>     <str name="pos">nnp,nn,nns</str>     <float name="boost">1.4</float>   </processor>   <processor class="solr.LogUpdateProcessorFactory" />   <processor class="solr.RunUpdateProcessorFactory" /> </updateRequestProcessorChain>   Register the defined update chain to the /update handler. <str name="update.chain">nlp</str>   Restart the instance and index documents. The terms with the specified part of speech will be automatically added to the keywords field, which is defined as a destination field in solrconfig.xml. The following is an example of the resultant document. {   "id": "1201",   "description": "Bob Marley was born in Jamaica",   "keywords": "Bob Marley Jamaica " }

命名实体提取

如果您的搜索引擎需要索引非结构化内容,如书籍、期刊或博客,一项至关重要的任务是提取隐藏在文本流中的重要信息。在本节中,您将了解提取这些信息的不同方法,并利用它们来提高精确度和整体搜索体验。

非结构化内容主要是供人们消费的,提取隐藏在其中的重要实体和元数据需要复杂的处理。这些实体可以是通用信息(例如,人员、位置、组织、资金或时间信息)或特定于某个领域的信息(例如,医疗保健中的疾病或解剖)。识别诸如个人、组织和位置等实体的任务被称为命名实体识别(NER)。例如,在文本“鲍勃·马利出生在牙买加”中,NER 应该能够检测出鲍勃·马利是人而牙买加是地点。图 11-6 显示了本例中提取的实体。

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

图 11-6。

Named entities extracted from content

提取的命名实体可以在 Solr 中以多种方式使用,实际的用法取决于您的需求。Solr 中的一些常见用法如下:

  • 使用实体支持分面导航
  • 对实体的结果进行排序
  • 为自动建议词典查找实体,如人名
  • 为实体分配不同的提升,以改变它们在域中的重要性
  • 基于检测到的实体类型搜索有限的字段集的查询重构。

NER 的方法可以分为三类,在下面的小节中详细介绍。该方法及其实现取决于您的需求、用例以及您试图提取的实体。前两种方法可以通过使用 Solr 或任何高级 NLP 库的现成特性来实现。对于第三种方法,您将定制 Solr 来集成 OpenNLP 并提取命名实体。定制或提取可以根据您的需要而有所不同。

使用规则和正则表达式

对于 NER 来说,规则和正则表达式是最简单的方法。您可以定义一组规则和一个正则表达式模式,它与实体提取的传入文本相匹配。这种方法适用于提取遵循预定义模式的实体,例如电子邮件 id、URL、电话号码、邮政编码和信用卡号。

在分析链中使用PatternReplaceCharFilterFactoryPatternReplaceTokenFilterFactory可以集成一个简单的正则表达式。用于确定电话号码的正则表达式可以像这样简单:

^[0-9+\(\)#\.\s\/ext-]+$

为了提取电子邮件 id,可以使用 Lucene 提供的UAX29URLEmailTokenizerFactory。参见 http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters#solr.UAX29URLEmailTokenizerFactory 了解记号赋予器的详细信息。您可能会觉得有趣的是,有一个针对电子邮件的官方标准正则表达式,称为 RFC 5322 。详见 http://tools.ietf.org/html/rfc5322#section-3.4 。它描述了有效的电子邮件地址必须遵守的语法,但是实现起来太复杂了。

如果您正在寻找复杂的正则表达式规则,您可以评估 Apache UIMA 提供的正则表达式注释器,在这里您可以定义规则集。关于注释器的详细信息,请参考 https://uima.apache.org/downloads/sandbox/RegexAnnotatorUserGuide/RegexAnnotatorUserGuide.html 。如果您正在使用 Drools 之类的规则引擎,您可以通过编写自定义更新处理器或过滤器工厂将其集成到 Solr 中。

如果您想将 OpenNLP 用于基于正则表达式的 NER,您可以使用RegexNameFinder并指定模式,而不是使用NameFinderME。参考“使用训练好的模型”一节中的例子,您可以使用RegexNameFinder进行替换。

这种 NER 方法的局限性在于,遵循指定模式或满足规则的任何不相关的事物都将被检测为有效实体;例如,格式错误的五位数薪水可能会被检测为邮政编码。此外,这种方法仅限于遵循已知模式的实体类型。它不能用于检测名称或组织等实体。

使用字典或地名词典

实体提取的基于词典的方法,也称为基于地名词典的方法,维护适用类别的术语列表。输入文本在地名词典上进行匹配以提取实体。这种方法适用于适用于特定领域且术语有限的实体。典型的例子是你组织中的职位、国籍、宗教、一周中的几天或一年中的几个月。

您可以通过从本地数据源或外部来源(如维基百科)提取信息来构建列表。维护字典的数据结构可以是满足您需求的任何数据结构。它可以像从文本文件填充的 Java 集合一样简单。基于文件的方法在单独的字段中填充实体的一个更简单的实现是使用 Solr 的KeepWordFilterFactory。以下是对它的一个样本文本分析:

<analyzer>

<tokenizer class="solr.StandardTokenizerFactory"/>

<filter class="solr.LowerCaseFilterFactory"/>

<filter class="solr.KeepWordFilterFactory" words="keepwords.txt"/>

</analyzer>

该列表可以在数据库表中维护,但是大的列表可能会降低性能。一种更快、更高效的方法是构建一个自动机。可以参考 Solr 建议模块中的 FST 数据结构或者大卫·斯迈利在 https://github.com/OpenSextant/SolrTextTagger 提供的数据结构。

如果您想查找短语和单个术语,可以使用 OpenNLP Chunker 组件或 Solr 提供的ShingleFilterFactory来处理输入的文本。下面是用于生成不同大小的瓦片区的过滤器定义。已经提供了参数outputUnigrams="true"来匹配单个令牌。

<filter class="solr.ShingleFilterFactory" maxShingleSize="3" outputUnigrams="true"/>

这种方法的好处是不需要培训,但是不太受欢迎,因为很难维护。它不能用于常见的实体,如名称或组织,因为这些术语可能有歧义,并且不限于一组定义的值。这种方法也忽略了上下文。例如,这种方法不能区分文本汤米·席尔菲格是指一个人还是一个组织。

OpenNLP 提供了一种更好的基于字典的提取方法,它扫描字典中的名称,让您不必担心匹配短语。

以下是 NER 在 OpenNLP 中使用字典的步骤:

Create an XML file containing the dictionary terms. <dictionary case_sensitive="false">   <entry ref="director">     <token>Director</token>   </entry>   <entry ref="producer">     <token>Producer</token>   </entry>   <entry ref="music director">     <token>Music</token><token>Director</token>   </entry>   <entry ref="singer">     <token>Singer</token>   </entry> </dictionary   Create a FileInputStream and instantiate the dictionary with it. InputStream modelIn = new FileInputStream(file); Dictionary dictionary = new Dictionary(modelIn); Alternatively, the Dictionary object can be created using a no-arg constructor and tokens added to it as shown here: Dictionary dictionary = new Dictionary(); dictionary.put(new StringList("Director")); dictionary.put(new StringList("Producer")); dictionary.put(new StringList("Music",  "Director")); dictionary.put(new StringList("Singer"));   Create the DictionaryNameFinder instance by using the Dictionary and assigning a name to it. DictionaryNameFinder dnf = new DictionaryNameFinder(dictionary, "JobTitles");

请参考下面“使用训练模型”一节中的实现步骤,因为其余步骤保持不变。

如果您觉得可以提高精确度,可以使用基于规则和基于地名词典的混合方法。

使用经过训练的模型

对 NER 使用训练模型的方法属于机器学习的监督学习类别:需要人工干预来训练模型,但在模型被训练后,它会返回几乎准确的结果。

这种方法使用统计模型来提取实体。这是提取不限于一组值的实体的首选方法,例如在名称或组织的情况下。这种方法可以找到模型中没有定义或标记的实体。该模型考虑了文本的语义和上下文,很容易解决实体之间的歧义,例如人名和组织名。使用早期的方法无法解决这些问题;训练有素的模特是唯一的出路。它不需要创建难以维护的大型字典。

用于实体提取的 Solr 插件

在本节中,您将学习从被索引的文档中提取命名实体,并将提取的术语填充到一个单独的 Solr 字段中。这个过程需要两个主要步骤:

Extract the named entities from the text. In the provided sample, you’ll use OpenNLP and the trained models freely available on its web site. The model can be downloaded from http://opennlp.sourceforge.net/models-1.5/ .   Write a custom update request processor, that will create a new field and add the extracted entities to it.

以下是将提取的命名实体添加到单独的字段时要遵循的详细步骤:

Write a Java class to extract the named entities by using OpenNLP. Here are the steps for extraction: Read the trained model by using FileInputStream and instantiate the TokenNameFinderModel with it. OpenNLP requires a separate model for each entity type. To support multiple entities, a separate model should be loaded for each entity. InputStream modelIn = new FileInputStream(fileName); TokenNameFinderModel model = new TokenNameFinderModel(modelIn);   Instantiate the NameFinderME class by providing the model to its constructor. A separate instance of NameFinderME should be created for each entity type. NameFinderME  nameFinder = new NameFinderME(model);   NameFinderME as a prerequisite requires the sentence to be tokenized, which can be done using the OpenNLP tokenization component. If this code is part of the analysis chain, the sentence can be tokenized by using the Solr-provided tokenizer. The simplest form of tokenization can even be using Java String’s split() method, also used in this example, though it’s prone to creating invalid tokens. String [] sentence = query.split(" ");   Pass the tokenized sentence to the NameFinderMe instance, which returns the extracted named entity. Span[] spans = nameFinder.find(sentence);   Iterate the Span array to extract the named entities. We have populated the extracted entities to the NamedEntity bean that holds the entity and its corresponding entity type. List<NamedEntity> neList = new ArrayList<>(); for (Span span : spans) {   NamedEntity entity = new NamedEntity();   StringBuilder match = new StringBuilder();   for (int i = span.getStart(); i < span.getEnd(); i++) {     match.append(sentence[i]).append(" ");   }   entity.setToken(match.toString().trim.());   entity.setEntity(entityName);   neList.add(entity); }   After processing the sentences of a document, call clearAdaptiveData() to clear the cache, which is maintained by OpenNLP to track previous entity extraction of a word. nameFinder.clearAdaptiveData();     Write a custom implementation of UpdateRequestProcessor and its factory method. The following are the steps to be followed for adding the extracted entities to a separate field. (Refer to Chapter 5, if you want to refresh your memory about writing a custom update request processor.) Read the parameters from NamedList in the init() method of NERUpdateProcessorFactory, the custom factory, to populate the instance variables for controlling the extraction behavior. Also, set up the NamedEntityTagger that can be used for extraction as mentioned in step 1. private String modelFile; private String src; private String dest; private String entity; private float boost; private NamedEntityTagger tagger; public void init(NamedList args) {   super.init(args);   SolrParams param = SolrParams.toSolrParams(args);   modelFile = param.get("modelFile");   src = param.get("src");   dest = param.get("dest");   entity = param.get("entity","person");   boost = param.getFloat("boost", 1.0f);   tagger = new NamedEntityTagger();   tagger.setup(modelFile, entity); };   In the processAdd() method of NERUpdateProcessor, a custom update request processor, read the field value of the document being indexed and provide it to the NER object for extracting the entities. Create a new field and add the extracted entities to it. @Override public void processAdd(AddUpdateCommand cmd) throws IOException {   SolrInputDocument doc = cmd.getSolrInputDocument();   Object obj = doc.getFieldValue(src);   if (null != obj && obj instanceof String) {     List<NamedEntity> neList = tagger.tag((String) obj);     for(NamedEntity ne : neList) {       doc.addField(dest, ne.getToken(), boost);     }   }   super.processAdd(cmd); }     Add the dependencies to the Solr core library. <lib dir="dir-containing-the-jar" regex=" solr-practical-approach-\d.*\.jar" />   Define this custom processor in solrconfig.xml. In the parameters, you need to specify the path of the model, the source field, the destination field, the entity to be extracted, and the boost to be provided to the destination field. The destination field should be multivalued, as multiple entities can be extracted from the text. <updateRequestProcessorChain name="nlp">   <processor class="com.apress.solr.pa.chapter``11``.opennlp.NERUpdateProcessorFactory">     <str name="modelFile">path-to-en-ner-person.bin</str>     <str name="src">description</str>     <str name="dest">ext_person</str>     <str name="entity">person</str>     <float name="boost">1.8</float>   </processor>   <processor class="solr.LogUpdateProcessorFactory" />   <processor class="solr.RunUpdateProcessorFactory" /> </updateRequestProcessorChain>   Register the defined update chain to the /update handler. <str name="update.chain">nlp</str>   Restart the instance and index documents. The extracted entities will be automatically added to the destination field. The following is an example of a resultant document. { "id": "1201", "description": "Bob Marley and Ricky were born in Jamaica", "ext_person": [   "Bob Marley ",   "Ricky " ] }

这段源代码只提取了一种类型的实体。在现实生活中,您可能希望提取多个实体,并将它们填充到不同的字段中。您可以通过加载多个模型并将提取的术语添加到单独的字段来扩展这个更新请求处理器,以适应所需的功能。

OpenNLP 为每个实体类型使用一个单独的模型。相比之下,另一个 NLP 软件包 Stanford NLP 对所有实体使用相同的模型。

这种实体提取的方法应该返回准确的结果,但是输出也取决于标记质量和模型训练的数据(记住,垃圾输入,垃圾输出)。此外,基于模型的方法在内存需求和处理速度方面成本很高。

语义丰富

在第四章中,您学习了使用SynonymFilterFactory来生成同义词以扩展令牌。这种方法的主要限制是它没有考虑语义。一个词可以是多义的(有多个意思),同义词可以根据它们的词性或上下文而变化。例如,一个典型的泛型synonym.txt文件可以将单词 large 指定为 big 的同义词,这将把查询 big brother 扩展为 big brother,这在语义上是不正确的。

不使用定义术语及其同义词列表的文本文件,而是使用受控词汇表(如 WordNet 或医学主题词(MeSH ))来应用更复杂的方法,这些词汇表不需要手动定义或手工制作。同义词扩展只是其中的一部分。词汇表和叙词表还包含其他有用的信息,如上义词、下义词和部分义词,您将进一步了解这些信息。该信息可用于理解语义关系和进一步扩展查询。

这些辞典通常由社区或企业维护,它们用最新的单词不断更新语料库。词汇表可以是通用的,也可以是特定于特定领域的。WordNet 是一个通用词库的例子,它可以被整合到任何搜索引擎中,用于任何领域。网格是一个适用于医学领域的词汇。

您还可以通过构建包含适用于您的领域的概念树的分类法和本体论来执行语义丰富。查询术语可以与分类法进行匹配,以提取更宽、更窄或相关的概念(例如altLabelprefLabel)),并执行所需的丰富。你可以从网上获得这些分类法,或者你可以让一个分类法专家为你定义一个。您还可以使用 DBpedia 等资源,这些资源以 RDF 三元组形式提供从 Wikipedia 中提取的结构化信息。Wikidata 是另一个链接数据库,它包含来自 Wikimedia 项目(包括维基百科)的结构化数据。

这些知识库可以通过各种方式集成到 Solr 中。以下是将所需的丰富内容插入 Solr 的方法:

  • 文本分析:编写一个定制的令牌过滤器,使用从受控词汇表中提取的同义词或其他关系来扩展令牌。实现可以类似于 Solr 提供的SynonymFilterFactory,它使用一个受控的词汇表文件来代替synonyms.txt文件。SynonymFilterFactory以最简单的形式支持 WordNet。在本章的后面,您将看到同义词扩展的自定义实现。

  • Query parser: Write a query parser for expanding the user query with its related or narrower term. For example, in an e-commerce web site for clothing, the user query men accessories can be expanded to search for belts or watches. This additional knowledge can be extracted from a taxonomy that contains accessories as a broader term, and belts and watches as its narrower terms. Figure 11-7 specifies the relationship between the broader and narrower concepts. Instead of just expanding the query, you can extend the system to reformulate the query to build a new query.

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

    图 11-7。

    Concept tree

同义词扩展

在本节中,您将学习如何自动扩展术语以包含它们的同义词。该过程需要执行以下两项任务:

Knowledge extraction: The first step is to extract the synonyms from the controlled vocabulary. The extraction process depends on the vocabulary and supported format. In the following example, you will extract knowledge from WordNet by using one of the available Java libraries.   Solr plug-in: The feature can be plugged into any appropriate extendable Solr component. In this section, you will expand the synonym by using a custom token filter.

在执行这些任务之前,您将学习 WordNet 的基础知识和它提供的信息,然后实际操作同义词扩展。

WordNet

WordNet 是一个大型的英语词汇数据库。它将名词、动词、形容词和副词分成称为同义词集的认知同义词集,每个同义词集表达一个不同的概念。同素集通过概念、语义和词汇关系相互联系。它的结构使它成为计算语言学和自然语言处理的有用工具。它包含 155,287 个单词,组织在 117,659 个同义词集中,总共有 206,941 个词义对。

WordNet 是在 BSD 风格的许可下发布的,可以从它的网站 https://wordnet.princeton.edu/ 免费下载。也可以在 http://wordnetweb.princeton.edu/perl/webwn 评测网络版词库。

WordNet 中单词之间的主要关系是同义词,因为它的同义词集将表示相同概念并在许多上下文中可以互换的单词组合在一起。除同义词之外,同义词词典还包含以下主要信息(以及其他信息):

  • 上下义关系:这是指词与词之间的关系。它将一个普通的 synset(如 accessory)链接到一个更具体的 synset(如 belt)。比如,accessory 是 belt 的上位词,belt 是 accessory 的下位词。这些关系保持了层次结构,并且是可传递的。
  • 部分关系:这是词与词之间的整体-部分关系。例如,纽扣是衬衫的部分名称。
  • 转喻:这是指表达越来越具体的方式来描述一个事件的动词。例如,whisper 是 talk 的词源。
  • 注释:这是一个词的简短定义。在大多数情况下,它还包含一个或多个说明 synset 成员用法的短句。

WordNet 需要词类和单词作为先决条件。

有一些 Java 库可以用来访问 WordNet,每一个都有自己的优缺点。你可以参考 http://projects.csail.mit.edu/jwi/download.php?f=finlayson.2014.procgwc.7.x.pdf 一篇比较初级库的特性和性能的论文。

用于同义词扩展的 Solr 插件

本节提供了开发术语简单扩展机制的步骤。下面是实现该特性所需的两个步骤

Write a client to extract synonmys from wordnet.   Write a Solr plugin to integrate the extracted synonyms for desired expansion. In this section, the enrichment is integrated to Solr using a custom token filter.

使用 WordNet 扩展同义词

要从 WordNet 中提取同义词,有两个先决条件:

Download the database from WordNet’s download page at https://wordnet.princeton.edu/wordnet/download/current-version/ and extract the dictionary from tar/zip to a folder.   You will need a Java library to access the dictionary. The example uses the Java WordNet Library (JWNL). You can use any other library that suits your requirements.

所提供的这些步骤是最简单的形式;您可能需要优化以使代码生产就绪。此外,该程序只提取同义词。您可以扩展它来提取前面讨论过的其他相关信息。从 WordNet 等通用同义词库中提取相关术语时,需要注意的另一件事是,您可能希望执行消歧,以便为要扩展的术语确定合适的同义词集;例如,像 bank 这样的词是多义的,根据上下文可以表示河岸或银行机构。如果您使用特定于领域的词汇表,歧义消除就不那么重要了。(涵盖歧义消除超出了本书的范围。)

以下是使用 WordNet 扩展同义词的步骤:

The JWNL requires an XML-based properties file to be defined. The following is a sample properties file. The path of the extracted WordNet dictionary should be defined in the dictionary_path parameter in this file. <?xml version="1.0" encoding="UTF-8"?> <jwnl_properties language="en">   <version publisher="Princeton" number="3.0" language="en"/>   <dictionary class="net.didion.jwnl.dictionary.FileBackedDictionary">     <param name="dictionary_element_factory"       value="net.didion.jwnl.princeton.data.PrincetonWN17FileDictionaryElementFactory"/>     <param name="file_manager"         value="net.didion.jwnl.dictionary.file_manager.FileManagerImpl">       <param name="file_type"           value="net.didion.jwnl.princeton.file.PrincetonRandomAccessDictionaryFile"/>       <param name="dictionary_path" value="/path/to/WordNet/dictionary"/>     </param>   </dictionary>   <resource class="PrincetonResource"/> </jwnl_properties>   Create a new FileInputStream specifying the path of the JWNL properties file and initialize the JWNL by providing the FileInputStream. Create an instance of Dictionary. JWNL.initialize(new FileInputStream(propFile)); Dictionary dictionary = Dictionary.getInstance();   WordNet requires the part of speech to be specified for the tokens, and that value should be converted to a POS enum that is accepted by the dictionary. The part of speech can be tagged by using the OpenNLP part-of-speech tagger, as discussed in the previous example, or any other package you are familiar with. POS pos = null; switch (posStr) { case "VB": case "VBD": case "VBG": case "VBN": case "VBP": case "VBZ":   pos = POS.VERB;   break; case "RB": case "RBR": case "RBS":   pos = POS.ADVERB;   break; case "JJS": case "JJR": case "JJ":   pos = POS.ADJECTIVE;   break; //  case "NN": //  case "NNS": //  case "NNP": //  case "NNPS": //   pos = POS.NOUN; //   break; } This code snippet has a section commented out to ignore the synonym expansion for nouns as nouns will introduce more ambiguity. You can uncomment it, if you introduce a disambiguation mechanism.   Invoke the getIndexWord() method of the Dictionary instance by providing the word and its POS. The returned value is IndexWord. IndexWord word = dictionary.getIndexWord(pos, term);   The getSenses() method of IndexWord returns an array of Synset, which can be traversed to get all the synonyms. The synset returned varies on the basis of the POS provided. The following block of code populates the synonyms Java set with all the extracted synonyms. Set<String> synonyms = new HashSet<>(); Synset[] synsets = word.getSenses();   for (Synset synset : synsets) {     Word[] words = synset.getWords();     for (Word w : words) {           String synonym = w.getLemma().toString()                          .replace("_", " ");           synonyms.add(synonym);   } }

用于同义词扩展的自定义令牌过滤器

在上一节中,您学习了从 WordNet 中提取同义词。现在,必须将提取的同义词添加到索引的术语中。在本节中,您将学习编写一个定制的标记过滤器,它可以被添加到一个字段的文本分析链中,以用它的同义词来丰富标记。

Lucene 在包org.apache.lucene.analysis.*中为令牌过滤器定义了类。为了编写您的定制令牌过滤器,您需要扩展以下两个 Lucene 类:

  • TokenFilterFactory:创建TokenFilter实例的工厂应该扩展这个抽象类。
  • TokenFilter:在第四章的中,你了解到令牌过滤器是一个令牌流,它的输入是另一个令牌流。TokenFilter是一个抽象类,提供对所有标记的访问,无论是从文档的字段还是从查询文本。自定义实现应该继承这个TokenFilter抽象类并覆盖incrementToken()方法。

以下是编写自定义令牌过滤器并将其插入字段的文本分析链时应遵循的步骤:

Write a custom implementation of TokenFilterFactory that creates the TokenFilter. Here are the steps: Extend the TokenFilterFactory abstract class. The ResourceLoaderAware interface is implemented by classes that optionally need to initialize or load a resource or file. public class CVSynonymFilterFactory extends TokenFilterFactory implements ResourceLoaderAware { }   Override the create() abstract method and return an instance of the custom TokenStream implementation, which you will create in the next step. You can optionally pass additional parameters to the implementing class. Here you pass the instantiated resources: @Override public TokenStream create(TokenStream input) {   return new CVSynonymFilter(input, dictionary, tagger, maxExpansion); }   If you want to read parameters from the factory definition in schema.xml, you can read it from the map that is available as an input to the constructor. You have read the path of the JWNL properties file, the OpenNLP POS model, and the maximum number of desired expansions. public CVSynonymFilterFactory(Map<String, String> args) {   super(args);   maxExpansion = getInt(args, "maxExpansion", 3);   propFile = require(args, "wordnetFile");   modelFile = require(args, "posModel"); }   Initialize the required resources by overriding the inform() method provided by the ResourceLoaderAware interface. @Override public void inform(ResourceLoader loader) throws IOException {   // initialize for wordnet   try {     JWNL.initialize(new FileInputStream(propFile));     dictionary = Dictionary.getInstance();   } catch (JWNLException ex) {     logger.error(ex.getMessage());     ex.printStackTrace();     throw new IOException(ex.getMessage());   }   // initialize for part of speech tagging   tagger = new PartOfSpeechTagger();   tagger.setup(modelFile); }     Create a custom class by extending the TokenFilter abstract class that performs the following tasks. Initialize the required attributes of the tokens. You have initialized the CharTermAttribute, PositionIncrementAttribute, PositionLengthAttribute, TypeAttribute, and OffsetAttribute that contain the term text, position increment information, information about the number of positions the token spans, the token type, and the start/end token information, respectively. private final CharTermAttribute termAttr = addAttribute(CharTermAttribute.class); private final PositionIncrementAttribute posIncrAttr     = addAttribute(PositionIncrementAttribute.class); private final PositionLengthAttribute posLenAttr     = addAttribute(PositionLengthAttribute.class); private final TypeAttribute typeAttr = addAttribute(TypeAttribute.class); private final OffsetAttribute offsetAttr = addAttribute(OffsetAttribute.class);   Define the constructor and do the required initialization. public CVSynonymFilter(TokenStream input,         Dictionary dictionary, PartOfSpeechTagger tagger, int maxExpansion) {   super(input);   this.maxExpansion = maxExpansion;   this.tagger = tagger;   this.vocabulary = new WordnetVocabulary(dictionary);   if (null == tagger || null == vocabulary) {     throw new IllegalArgumentException("fst must be non-null");   }   pendingOutput = new ArrayList<String>();   finished = false;   startOffset = 0;   endOffset = 0;   posIncr = 1; }   Override the incrementToken() method of TokenFilter. The method should play back any buffered output before running parsing and then do the required processing for each token in the token stream. The addOutputSynonyms() method extracts the synonym for each term provided. When the parsing is completed, the method should mandatorily return the Boolean value false. @Override public boolean incrementToken() throws IOException {   while (!finished) {     // play back any pending tokens synonyms     while (pendingTokens.size() > 0) {       String nextToken = pendingTokens.remove(0);       termAttr.copyBuffer(nextToken.toCharArray(), 0, nextToken.length());       offsetAttr.setOffset(startOffset, endOffset);       posIncAttr.setPositionIncrement(posIncr);       posIncr = 0;       return true;     }     // extract synonyms for each token     if (input.incrementToken()) {       String token = termAttr.toString();       startOffset = offsetAttr.startOffset();       endOffset = offsetAttr.endOffset();       addOutputSynonyms(token);     } else {       finished = true;   } }   // should always return false   return false; } private void addOutputSynonyms(String token) throws IOException { pendingTokens.add(token); List<PartOfSpeech> posList = tagger.tag(token);   if (null == posList || posList.size() < 1) {    return;   }   Set synonyms = vocabulary.getSynonyms(token, posList.get(0)     .getPos(), maxExpansion);   if (null == synonyms) {     return;   }   for (String syn : synonyms) {     pendingTokens.add(syn);   } } @Override public void reset() throws IOException {   super.reset();   finished = false;   pendingTokens.clear();   startOffset = 0;   endOffset = 0;   posIncr = 1; } The processing provided here tags the part of speech for each token instead of the full sentence, for simplicity. Also, the implementation is suitable for tokens and synonyms of a single word. If you are thinking of getting this feature to production, I suggest you refer to SynonymFilter.java in the org.apache.lucene.analysis.synonym package and extend it using the approach provided there.     Build the program and add the Java binary JAR to the Solr classpath. Alternatively, place the JAR in the $SOLR_HOME/core/lib directory. <lib dir="./lib" />   Define the custom filter factory to the analysis chain of the desired fieldType in schema.xml. <fieldType name="text_semantic" class="solr.TextField" positionIncrementGap="100">   <analyzer type="index">     <tokenizer class="solr.StandardTokenizerFactory"/>     <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />     <filter class="solr.LowerCaseFilterFactory"/>   </analyzer>   <analyzer type="query">     <tokenizer class="solr.StandardTokenizerFactory"/>     <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />     <filter class="com.apress.solr.pa.chapter``11``.enrichment.CVSynonymFilterFactory" maxExpansion="3" wordnetFile="path-of-jwnl-properties.xml" posModel="path-to-en-pos-maxent.bin" />     <filter class="solr.LowerCaseFilterFactory"/>   </analyzer> </fieldType>   Restart the instance, and you are good to query for semantic synonyms. If the token filter has been added to index-time analysis, the content should be reindexed. Verify the expansion in the Analysis tab in the Solr admin UI.

摘要

在这一章中,你学习了搜索引擎的语义方面。您看到了基于关键字的引擎的局限性,并了解了语义搜索增强用户体验和文档可查找性的方法。语义搜索是一个高级而广泛的话题。鉴于本书范围有限,我们将重点放在简单的自然语言处理技术上,用于识别句子中的重要单词,以及从非结构化文本中提取元数据的方法。您还了解了一种基本的语义丰富技术,用于发现之前完全被忽略但用户可能会非常感兴趣的文档。综上所述,本章提供了在 Solr 中集成这些特性的示例源代码。

你已经到了这本书的结尾。我真诚地希望它的内容对您开发实用的搜索引擎有所帮助,并有助于您了解 Apache Solr。

posted @   绝不原创的飞龙  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
历史上的今天:
2020-10-02 《线性代数》(同济版)——教科书中的耻辱柱
点击右上角即可分享
微信分享提示