Apache Solr 的新特性

利用 Solr 1.3 的新特性和改进

使用 Apache Solr 实现更加灵巧的搜索系列文章发表之后,Apache Solr 又添加了很多新的特性和性能改进。在本文中,Solr 和 Lucene 的负责人 Grant Ingersoll 详细介绍了 Solr 1.3 的新改进,包括分布式搜索、轻松数据库导入、集成拼写检查和新的扩展 API 等等。

Grant Ingersoll, 技术员工成员, EMC

Grant IngersollGrant Ingersoll 是 Lucid Imaginationis 的技术团队的创办人之一,也是该团队的成员。Grant 的编程兴趣包括信息检索、机器学习、文本分类和提取。他是 Apache Lucene 和 Apache Solr 项目的负责人和发言人,也是 Apache Mahout 机器学习项目的创办人之一。



2008 年 11 月 25 日

Apache Solr 是一个开源的搜索服务器,主要基于 HTTP 和 Apache Lucene。2007 年,我在一个包含有两个部分的系列文章 使用 Apache Solr 实现更加灵巧的搜索中向 developerWorks 读者介绍了 Solr。由于最近又发布了 Solr 1.3 版本,所有应该详述一下 Solr 自 2007 年以后的许多新特性和增强功能。

Solr 包含有很多的企业就绪特性,比如轻松的配置和管理、多客户机语言绑定、索引复制、缓存、统计数据以及日志记录。Solr 的 1.3 版本以 Apache Lucene 2.3 版本的巨大性能提升为基础,并增加了一个新的、向后兼容的、即插即用组件架构。该架构使开发人员踊跃创建可以进一步增强 Solr 的组件。例如,1.3 版本就包含能够实现以下功能的组件:

  • “您是不是要找……” 拼写检查
  • 查找 “类似的” Document
  • 根据编辑输入(又称付费排序)覆盖搜索结果

另外,查询解析、搜索、分类以及调试这样的现有功能也被组件化了。现在,您可以通过组合这些组件来自定义创建 SolrRequestHandler。最后,Solr 还增加了直接为数据库内容创建索引的功能,并且通过分布式搜索来支持庞大的系统,这一点对很多企业都很重要。

本文快速温习了 Solr 的内容,但前提是您熟悉 Solr 的基本概念,包括(但不限于)schema.xml、solrconfig.xml、索引和搜索的基本概念,以及 SolrRequestHandler在 Solr 中所起的作用。如果不熟悉这些概念的话,您可以查看 使用 Apache Solr 实现更加灵巧的搜索系列文章,并参阅本文的 参考资料部分。

首先,我将简单复习一下 Solr,然后介绍如何获取和安装最新版本的 Solr 以及升级早期版本的要点。接着,我会介绍 Solr 1.3 的一些重要的增强功能,最后再看一下 Solr 的新特性。

复习:Solr 概念

从概念上,Solr 可以被分成四大块:

  • 模式(schema.xml)
  • 配置(solrconfig.xml)
  • 索引
  • 搜索

要理解模式,需要先理解 Lucene 对 Document的注释。一个 Document包含一个或多个 Field。一个 Field由名称、内容和给出了内容的处理方法的元数据组成。分析内容可以使其被搜索到。而分析则是通过将一个 Tokenizer与零个或零个以上的 TokenFilter链接到一起来完成的,Tokenizer 能够将输入流拆分为单词(标记),TokenFilter 能够改变(例如,词干)或移除标记。Solr 模式能够在没有代码的情况下轻松配置分析过程。它还提供了更强大的键入功能,让您能够将 Field具体指定为 Stringintfloat或其他原有的或自定义的类型。

在配置方面,solrconfig.xml 文件不仅指定了 Solr 如何处理索引、突出显示、分类、搜索以及其他请求,还指定了用于指定缓存的处理方法的属性,以及用于指定 Lucene 管理索引的方法的属性。配置取决于模式,但模式不取决于配置。

索引和搜索都是通过向 Solr 服务器发送请求来实现的。索引的实现方法很简单,用 POST一个描述所有 Field及其内容的 XML 文档就可以了,如位于 apache-solr-1.3.0/example/exampledocs/ 目录下的 hd.xml 样例文档,如清单 1 所示:

清单 1. 样例 XML 文档
 <add> 
 <doc> 
  <field name="id">SP2514N</field> 
  <field name="name">Samsung SpinPoint P120 SP2514N - 
  hard drive - 250 GB - ATA-133</field> 
  <field name="manu">Samsung Electronics Co. Ltd.</field> 
  <field name="cat">electronics</field> 
  <field name="cat">hard drive</field> 
  <field name="features">7200RPM, 8MB cache, IDE Ultra ATA-133</field> 
  <field name="features">NoiseGuard, SilentSeek technology, Fluid 
  Dynamic Bearing (FDB) motor</field> 
  <field name="price">92</field> 
  <field name="popularity">6</field> 
  <field name="inStock">true</field> 
 </doc> 
 </add>

要实现搜索则只需要发送 HTTP GET,比如:

http://localhost:8983/solr/select?indent=on&version=2.2&q=ipod&start=0&rows=10 
      &fl=*%2Cscore&qt=standard&wt=standard

在这个例子,查询 ipod被提交,它要求 10 个结果。想知道更多有关各种可选查询选项的信息,请参看 Solr wiki(参见 参考资料)。(现在,与 Solr 一起提供的还有一个称为 SolrJ 的客户机,它将 HTTP 请求的所有细节信息都隐藏在一组很容易使用的 Java™类中。我将在本文 后半部分介绍 SolrJ)。

对于从更大的上下文理解 Solr 设计,这些关于 Solr 概念的快速复习已经足够。


安装 Solr 1.3

要使用 Solr 和本文中的样例,您必须先安装以下软件:

  • Java 1.5 或更高版本。
  • Web 浏览器,您将用它查看管理页面。我使用的是 Firefox,但可以使用其他现代浏览器。
  • 要运行 DataImportHandler样例,需要一个数据库及其 JDBC 驱动。在本文的样例中,我使用的是 PostgreSQL;MySQL 或其他数据库应该也可以,但可能需要修改我编写的 SQL 以使它适合您的数据库。
  • 需要一个 servlet 容器。我在本文中使用的是 Jetty,它与 Solr 打包在一起,所以也就没必要使用其他容器了。但如果您偏爱 Tomcat 或其它容器的话,Solr 也能很好地适应它们。

Solr 的新起点

安装了上述软件之后,从 Apache Mirrors Web 站点下载 Solr 1.3.0 版本,并将其解压缩到特定的目录下。解压缩包将创建一个名为 apache-solr-1.3.0 的目录。然后在一个终端(命令提示符)中完成以下步骤:

  1. cd apache-solr-1.3.0/example(在 Windows®上使用 \)。
  2. java -jar start.jar

    然后等待,直到在日志输出中看到如下几行,它表明服务器已经启动:

     2008-10-01 09:57:06.336::INFO:  Started SocketConnector @ 0.0.0.0:8983 
     Oct 1, 2008 9:57:06 AM org.apache.solr.core.SolrCore registerSearcher 
     INFO: [] Registered new searcher Searcher@d642fd main
  3. 将 Web 浏览器转到 http://localhost:8983/solr,您会看到一个 Solr 欢迎页面。
  4. 在另外一个终端中会出现 cd apache-solr-1.3.0/example/exampledocs
  5. java -jar post.jar *.xml。这会自动向 Solr 添加一组文档。
  6. 在浏览器的管理员页面上尝试查询 (http://localhost:8983/solr/admin/form.jsp)。

    图 1 显示了在我的浏览器上尝试查询 ipod产生的结果(有删节):

    图 1. 示例搜索结果
    示例搜索结果

现在,您的电脑上已经安装并运行 Solr 1.3,可以工作了。在本文中,我将使用并修改位于 pache-solr-1.3.0/example/solr/conf 目录中的样例 solrconfig.xml 和 schema.xml。我先介绍一下与升级到 Solr 1.3 版本有关的一些问题,然后再介绍一下该最新版本中的增强功能。如果不需要升级的话,您可以直接跳到 增强功能部分。

升级 Solr

Solr 1.3.0 与早期的 Solr 版本是兼容的。但升级时仍有几件事情需要注意。对于启动器来说,如果使用 复制的话,需要首先升级 worker 节点,然后再升级 master 节点。

Solr 复制

Solr 中的复制可能会涉及到一个或多个 worker 节点,它们都运行 Solr,将索引的本地副本与 master 节点上的更改进行同步。复制允许 Solr 进行扩展,以很高的查询容量来满足应用程序的需求,而且不会降低性能。Solr 能够很有效地处理该过程。要获取更多的信息,请参见 参考资料

第二,这个版本的 Solr 包含新版本的 Lucene。其实,这就意味着 Solr 将会升级内部的 Lucene 文件格式,也就是说旧版的 Solr 可能无法读取新的版本。所以,在升级前先备份索引是明智之举,避免以后需要降级版本。

第三,Solr 1.3 还包含 Dr. Martin Porter 的新版 Snowball 派生器。如果用它们派生单词的话,那么对于过去用某种方法派生的单词,现在可能(虽然可能性不大)不再使用相同的方法了。最保险的做法是为内容重新创建索引,这样就避免查询时间分析和索引分析不匹配。

除了有些用户可能会遇到的这些问题以外,Solr 1.3 的确应该代替早期的版本。现在您已经准备好学习本文的重用部分:增强 Solr 现有的功能。


增强功能

Solr 1.1 和 1.2 使用起来很方便,但是,和所有最简单的软件一样,它们都留有改进的空间。Solr 1.3 包含有很多对服务器的稳定性和性能的故障修复功能和改进。

性能增强

首先,最新的版本将 Lucene 库升级到了最近的版本,该版本含有很多性能改进。在测试中,我看到索引速度提高了 5 倍,而有些人则声称提高了 2 到 8 倍。幸运的是,所有的 Solr 用户都可以享受到更快的索引,而且大部分的性能增进都不需要改变配置。

但是,很容易改变 solrconfig.xml 中的一个配置,让应用程序更好地控制索引期间使用的内存量。在 1.1 和 1.2 版本中,Solr 会在内存中的文档达到一定数量时将索引的文档写到磁盘中,而不管文档的大小。这经常导致内存不被充分利用,因为文档较小时,尽管内存有剩余,文档还是被过度刷新;而文档较大需要更大内存时,又不能及时刷新它。现在,solrconfig.xml 的 <indexDefaults>部分有了 <ramBufferSizeMB>选项,您可以指定用于缓冲内存中的文档的内存量,而不是由文档的数量来决定。

更多扩展点

在 Solr 1.3 中,扩展 Solr 以及配置和重新整理扩展变得十分简单。以前,您需要编写一个 SolrRequestHandler来实现新功能。这个方法的问题是其他 SolrRequestHandler很难重用该功能。例如,您可能有更好的分类方法,但却想保留现有的查询与突出显示功能。为了解决这个问题,Solr 项目提出了将各种 SolrRequestHandler(比如 StandardRequestHandlerDismaxRequestHandler)重构为组件 —称为 SearchComponent—的想法,这些组件可以链接起来,形成一个新的 SolrRequestHandler。现在,您只要关注 SearchComponent的新功能就可以了,不用再费神思考怎样才能最好地扩展、重用或复制其他功能。

不过请放心,现有的 SolrRequestHandler仍然可以像以前一样无缝地工作,但它们现在仅仅是负责实际工作的围绕 SearchComponent的包装器而已。表 1 介绍了一些新 SearchComponent的详细信息。稍后,我还将在本文中提供有关表 1 中的两个组件的更多信息(MoreLikeThisComponentSpellCheckComponent。参见 参考资料中的 SearchComponent链接)。

表 1. 常用的 SearchComponent
名称说明和查询样例
QueryComponent负责将查询提交到 Lucene 并返回 Document的列表。

http://localhost:8983/solr/select?&q=iPod&start=0&rows=10
FacetComponent决定结果集的分类。

http://localhost:8983/solr/select?&q=iPod&start=0&rows=10&facet=true&facet.field=inStock
MoreLikeThisComponent为每个搜索结果查找与结果类似的文档,并返回这些结果。

http://localhost:8983/solr/select?&q=iPod&start=0&rows=10&mlt=true&mlt.fl=features&mlt.count=1
HighlightComponent在搜索结果的正文中突出显示查询词语的位置。

http://localhost:8983/solr/select?&q=iPod&start=0&rows=10&hl=true&hl.fl=name
DebugComponent返回有关查询的解析方式的信息,以及每个文档的记录方式的详细信息。

http://localhost:8983/solr/select?&q=iPod&start=0&rows=10&debugQuery=true
SpellCheckComponent根据索引的内容对输入查询进行拼写检查,并提供其他备选方法。

http://localhost:8983/solr/spellCheckCompRH?&q=iPood&start=0&rows=10&spellcheck=true&spellcheck.build=true

默认情况下,所有 SolrRequestHandler都附带有 QueryComponentFacetComponentMoreLikeThisComponentHighlightComponentDebugComponent。要添加自己的组件,您需要:

  1. 扩展 SearchComponent类。
  2. 使 Solr 可以使用这些代码(参见 参考资料中链接到 Solr Plugins wiki 页面的链接)。
  3. 在 solrconfig.xml 中配置它。

例如,假定我创建了一个名为 com.grantingersoll.MyGreatComponentSearchComponent,并让 Solr 可以使用它,而现在我想要将其插入到 SolrRequestHandler中以查询它。那么我首先需要声明该组件,如清单 2 所示,这样 Solr 才能知道如何实例化这个类:

清单 2. 组件声明
  <searchComponent name="myGreatComp" class="com.grantingersoll.MyGreatComponent"/>

接下来,我需要告知 Solr 要将其连接到哪个 SolrRequestHandler。在这个用例中,我可以使用三个选择之一:

  • 显式地声明所有 SearchComponent,如清单 3 所示:
    清单 3. 显式地声明所有 SearchComponent
     <requestHandler name="/greatHandler" class="solr.SearchHandler"> 
        <arr name="components"> 
          <str>query</str> 
          <str>facet</str> 
          <str>myGreatComp</str> 
          <str>highlight</str> 
          <str>debug</str> 
        </arr> 
     </requestHandler>
  • 预先将组件添加到现有的链接上,如清单 4 所示:
    清单 4. 预先将组件添加到现有的链接上
     <requestHandler name="/greatHandler" class="solr.SearchHandler"> 
        <arr name="first-components"> 
          <str>myGreatComp</str> 
        </arr> 
     </requestHandler>
  • 将组件追加到现有链接上,如清单 5 所示:
    清单 5. 将组件追加到现有链接上
     <requestHandler name="/greatHandler" class="solr.SearchHandler"> 
        <arr name="last-components"> 
          <str>myGreatComp</str> 
        </arr> 
     </requestHandler>

关于 DebugComponent的说明

当您使用 first-componentslast-components方法时,DebugComponent必须是链接的最后组件。当组件改变 DebugComponent报告的值时(比如查询结果),这尤为有用。

现在,与 SearchComponent重构类似,也可以将查询解析和 SolrRequestHandler分开。因此,您可以把 DismaxQParser与任何 SolrRequestHandler一起使用。您可以通过输入 defType参数来实现。例如:

http://localhost:8983/solr/select?&q=iPod&start=0&rows=10&defType=dismax&qf=name

使用 Dismax 查询解析器来解析查询,而不是标准 Lucene 查询解析器。

另外,您也可以通过扩展 QParserQParserPlugin来创建您自己的查询解析器,并让 Solr 可以使用它们,然后在 solrconfig.xml 中配置它。例如,如果我创建了 com.grantingersoll.MyGreatQParsercom.grantingersoll.MyGreatQParserPlugin,并使让 Solr 可以使用它们,那么我应该在 solrconfig.xml 中按以下方式配置它们:

<queryParser name="greatParser" class="com.grantingersoll.MyGreatQParserPlugin"/>

随后,我可以将 defType=greatParser键 / 值对添加到一个查询请求中,以查询这个新的解析器。

Solr 最近版本还包含了很多其他的改进。如果您有兴趣学习更多内容的话,可以看一下 参考资料中的发布说明链接。从这里开始我们将学习 Solr 的新特性。


新特性

Solr 1.3 拥有很多功能强大的特性,这使它充满了吸引力。本文剩余的部分将介绍新 Solr 特性,以及将它们合并到您的应用程序中的方法。为了展示这些内容,我将构建一个简单的应用程序,它将 RSS 提要和该提要的评级结合起来。评级将储存在一个数据库中,RSS 提要来自我的 Lucene 博客的 RSS 提要。完成这个简单的设置后 ??? 我将展示如何使用:

如果要实践这个示例,请 下载样例应用程序,并按以下说明进行操作:

  1. 将 sample.zip 拷贝到 apache-solr-1.3.0/example/ 目录。
  2. 解压缩 sample.zip
  3. 启动(或重启动)Solr:java -Dsolr.solr.home=solr-dw -jar start.jar
  4. 以数据库管理员的身份创建一个名为 solr_dw的数据库用户。具体做法请参看数据库说明。在 PostgreSQL 中,我的创建方法为:create user solr_dw;
  5. 为上述用户创建一个名为 solr_dw的数据库:create database solr_dw with OWNER = solr_dw;
  6. 在命令行上执行 src/sql/create.sql 语句:psql -U solr_dw -f create.sql solr_dw。我的输出为:
     gsi@localhost>psql -U solr_dw -f create.sql solr_dw 
     psql:create.sql:1: ERROR:  table "feeds" does not exist 
     psql:create.sql:2: NOTICE:  CREATE TABLE / PRIMARY KEY will create \ 
      implicit index "feeds_pkey" for table "feeds"
     CREATE TABLE 
     INSERT 0 1 
     INSERT 0 1 
     INSERT 0 1 
     INSERT 0 1 
     INSERT 0 1

从数据库和其他数据源导入数据

在这个结构化数据和非结构化数据的数量都很庞大的年代,经常需要从数据库、XML/HTML 文件或其他数据源导入数据,并使数据可搜索。过去,要编写自定义代码才能创建到数据库、文件系统或 RSS 提要的自定义连接。但现在,Solr 的 DataImportHandler(DIH)填补了这个空白,它使您能够从数据库(通过 JDBC)、RSS 提要、Web 页面和文件中导入数据。DIH 位于 apache-1.3.0/contrib/dataimporthandler 中,是 apache-1.3.0/dist/apache-solr-dataimporthandler-1.3.0.jar 中的一个 JAR 文件。

DataImportHandler警告

DataImportHandler不是文件 /Web 爬行器(crawler),它不直接支持从二进制文件格式中提取内容,比如 MS Office、Adobe PDF 或其他专有格式。本文没有详尽地介绍 DIH,如果要了解更多信息,请参见 参考资料

在概念上,DIH 可以分解为几个简单的部分:

  • DataSource:获取内容的数据库、Web 页面、RSS 提要或 XML 文件。
  • 文档 / 实体声明:指定 DataSource的内容与 Solr 模式之间的映射。
  • 导入:Solr 命令,使用它既可以进行完全导入,也可以只导入已经更改的实体的 增量导入
  • EntityProcessor:用于映射的代码。Solr 自带四个工具:
    • FileListEntityProcessor:在目录上迭代并导入文件。
    • SqlEntityProcessor:连接到一个数据库并导入记录。
    • CachedSqlEntityProcessor:将缓存添加到 SqlEntityProcessor
    • XPathEntityProcessor:使用 XPath 语句从 XML 文件抽取内容。
  • Transformer:用户定义的、可选的代码,用于在添加到 Solr 之前转换导入的内容。例如,DateFormatTransformer能够标准化日期。
  • 变量替代:用运行时的值替代占位符变量。

首先,我需要设置一个 SolrRequestHandler将 DIH 和 Solr 关联起来。该设置要在 solr-dw/rss/conf/solrconfig.xml 文件中进行,如清单 6 所示:

清单 6. 将 DIH 和 Solr 关联起来
 <requestHandler name="/dataimport"
  class="org.apache.solr.handler.dataimport.DataImportHandler"> 
 <lst name="defaults"> 
  <str name="config">rss-data-config.xml</str> 
 </lst> 
 </requestHandler>

该配置表明:我可以通过 http://localhost:8983/solr/rss/dataimport 找到 DataImportHandler实例;该实例必须使用一个名为 rss-data-config.xml 的配置文件(位于 solr_dw/rss/conf 目录)来获取它的设置信息。到目前为止,一切都相当简单。

拨开下一层面纱,rss-data-config.xml 文件就是声明和使用 DataSource、实体和 Transformer的地方。在这个例子中,首先遇到的 XML 标记(在根元素后面)为 DataSource声明,如清单 7 所示:

清单 7. DataSource声明
 <dataSource name="ratings" driver="org.postgresql.Driver"
      url="jdbc:postgresql://localhost:5432/solr_dw" user="solr_dw" /> 
 <dataSource name="rss" type="HttpDataSource" encoding="UTF-8"/>

清单 7 中的第一个声明设置一个与我的数据库相连接的 DataSource。它被命名为 ratings,因为我的评级信息就储存在里面。注意,虽然我没有为数据库用户设置密码,但实际可以向标记添加密码属性。如果了解 JDBC 设置的话,那么就应该很熟悉这个 DataSource声明了。第二个 DataSource名为 rss,它声明内容将要通过 HTTP 来获取。稍后将声明这个 DataSource的 URL。

下一个值得讨论的标记是 <entity>标记。它用来指定如何将 RSS 提要和数据库的内容映射到 Solr Document。一个实体就是被索引为一个单一文档的内容单位。例如,在一个数据库中,实体声明规定了如何将每一行转换成 Document中的 Field。一个实体里又可以包含一个或多个实体,因此子实体就变成整体 DocumentField结构。

至此,来自 rss-data-config.xml 的带注释的示例可以清楚地说明与实体相关的大部分信息。在这个例子中,主实体从一个 RSS 提要获取内容,并将其与数据库中的行相关联以获得评级。清单 8 是一个缩略的 RSS 提要示例:

清单 8. 缩略的 RSS 反提要
 <rss version="2.0"
 xmlns:content="http://purl.org/rss/1.0/modules/content/"
 xmlns:wfw="http://wellformedweb.org/CommentAPI/"
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:atom="http://www.w3.org/2005/Atom"
 > 
 <channel> 
 <title>Grant's Grunts: Lucene Edition</title> 
 <link>http://lucene.grantingersoll.com</link> 
 <description>Thoughts on Apache Lucene, Mahout, 
    Solr, Tika and Nutch</description> 
 <pubDate>Wed, 01 Oct 2008 12:36:02 +0000</pubDate> 
 <item> 
  <title>Charlotte JUG >> OCT 15TH - 6PM - 
    Search and Text Analysis</title> 
  <link>http://lucene.grantingersoll.com/2008/10/01/ 
    charlotte-jug-%c2%bb-oct-15th-6pm-search-and-text-analysis/</link> 
  <pubDate>Wed, 01 Oct 2008 12:36:02 +0000</pubDate> 
  <category><![CDATA[Lucene]]></category> 
  <category><![CDATA[Solr]]></category> 
  <guid isPermaLink="false">http://lucene.grantingersoll.com/?p=112</guid> 
  <description><![CDATA[Charlotte JUG >> OCT 15TH - 6PM - Search and Text Analysis 
 I will be speaking at the Charlotte Java Users Group on Oct. 15th, covering things 
 like Lucene, Solr, OpenNLP and Mahout, amongst other things. 
 ]]></description> 
 </item> 
 </channel>

与此同时,数据库中的一行包含提要中的文章的 URL、一个评级(我随便编的)和一个修改日期。现在,我只需将它映射到 Solr 就可以了。为了完成此工作,我将逐行解释 rss-data-config.xml 中的实体声明,如清单 9 所示(它包含行数和换行符,以获得良好的格式):

清单 9. 实体声明
 1. <entity name="solrFeed"
 2.pk="link"
 3.url="http://lucene.grantingersoll.com/category/solr/feed"
 4.processor="XPathEntityProcessor"
 5.forEach="/rss/channel | /rss/channel/item"
 6.            dataSource="rss"
 7.        transformer="DateFormatTransformer"> 
 8.  <field column="source" xpath="/rss/channel/title"
        commonField="true" /> 
 9.  <field column="source-link" xpath="/rss/channel/link"
        commonField="true" /> 
 10.  <field column="title" xpath="/rss/channel/item/title" /> 
 11.  <field column="link" xpath="/rss/channel/item/link" /> 
 12.  <field column="description"
        xpath="/rss/channel/item/description" /> 
 13.  <field column="category" xpath="/rss/channel/item/category" /> 
 14.  <field column="content" xpath="/rss/channel/item/content" /> 
 15.  <field column="date" xpath="/rss/channel/item/pubDate"
        dateTimeFormat="EEE, dd MMM yyyy HH:mm:ss Z" /> 
 16.  <entity name="rating" pk="feed"
      query="select rating from feeds where feed = '${solrFeed.link}'"
 17.   deltaQuery="select rating from feeds where feed = '${solrFeed.link}'
            AND last_modified > '${dataimporter.last_index_time}'"
 18.          dataSource="ratings"
 19.          > 
 20.    <field column="rating" name="rating"/> 
 21.  </entity> 
 22. </entity>
  • 第 1 行:实体名(solrFeed)。
  • 第 2 行:该项的可选主键,只有在导入增量时才用得到。
  • 第 3 行:将要获取的 URL —在这个用例中是我在 Solr 上的博客站点。
  • 第 4 行:用于从原始源映射内容的 EntityProcessor
  • 第 5 行:用于指定如何从 XML 获取记录的 XPath 表达。(XPath 提供一种在 XML 文件中指定特定元素或属性的方法。如果不熟悉 XPath 表达的话,请参阅 参考资料)。
  • 第 6 行:要使用的 DataSource的名称。
  • 第 7 行:用于将字符串解析成 java.util.DateDateFormatTransformer
  • 第 8 行:将通道名称(博客名称)映射到以 Solr 模式字段命名的数据源。此过程每个通道只发生一次,因此 commonField属性指定该值必须用于每一个数据项。
  • 第 9-14 行:将 RSS 提要的其他部分映射到 Solr Field
  • 第 15 行:映射出版日期,但使用 DateFormatTransformer将值解析为一个 java.util.Date对象。
  • 第 16-21 行:从数据库获取每一篇文章的评级的子实体。
  • 第 16 行query属性指定要运行的 SQL。${solrFeed.link}值被代替变量解析为每一篇文章的 URL。
  • 第 17 行:导入增量时要运行的查询。${dataimporter.last_index_time}由 DIH 提供。
  • 第 18 行:使用 JDBC DataSource
  • 第 20 行:将数据库中的评级栏映射到评级字段。如果未指定名称属性,将默认使用栏名。

下一步是运行导入。这可以通过提交 HTTP 请求来实现:

http://localhost:8983/solr/rss/dataimport?command=full-import

该请求先将所有的文档从索引中移除,然后再进行完全导入。再强调一遍,这个请求首先从索引中移除全部文档,一定要警惕这一点。您可以随时浏览 http://localhost:8983/solr/rss/dataimport 获取 DIH 的状态。在这个用例中,我的输出如清单 10 所示:

清单 10. 导入结果
 <response> 
 <lst name="responseHeader"> 
 <int name="status">0</int> 
 <int name="QTime">0</int> 
 </lst> 
 <lst name="initArgs"> 
 <lst name="defaults"> 
  <str name="config">rss-data-config.xml</str> 
 </lst> 
 </lst> 
 <str name="status">idle</str> 
 <str name="importResponse"/> 
 <lst name="statusMessages"> 
 <str name="Total Requests made to DataSource">11</str> 
 <str name="Total Rows Fetched">13</str> 
 <str name="Total Documents Skipped">0</str> 
 <str name="Full Dump Started">2008-10-03 10:51:07</str> 
 <str name="">Indexing completed. Added/Updated: 10 documents. 
  Deleted 0 documents.</str> 
 <str name="Committed">2008-10-03 10:51:18</str> 
 <str name="Optimized">2008-10-03 10:51:18</str> 
 <str name="Time taken ">0:0:11.50</str> 
 </lst> 
 <str name="WARNING">This response format is experimental.  It is 
  likely to change in the future.</str> 
 </response>

增量导入功能

使用数据库时,在完全导入之后,下一次只需导入那些改变了的记录。这个功能就叫做 增量导入。不幸的是,它还不能适用于 RSS 提要。要是可以的话,命令应该是这样的:
http://localhost:8983/solr/rss/dataimport?command=delta-import

您为其创建索引的文档的数量可能与我不同(因为我有可能会把其他 Solr 文章添加到提要)。为文档创建索引之后,我就可以查询索引了,就像在 http://localhost:8983/solr/rss/select/?q=*%3A*&version=2.2&start=0&rows=10&indent=on中一样,它返回了带索引的全部文档,共 10 篇。

有了这些准备,您就可以使用 DIH 了。再深入一些,就是如何替换变量和如何编写 Transformer了。要想学习更多有关此话题的知识,请参见 参考资料中的 DataImportHandlerwiki 页面链接。下面将介绍:如何使用 MoreLikeThisComponent查找相似页面。

查找相似页面

MoreLikeThisComponent和 Solr 模式

MLT 要求字段被储存或使用 检索词向量,检索词向量以一种以文档为中心的方式储存信息。MLT 通过文档的内容来计算文档中关键词语,然后使用原始查询词语和这些新词语创建一个新的查询。提交新查询就会返回其他查询结果。所有这些都可以用检索词向量来完成:只需将 termVectors="true"添加到 schema.xml 中的 <field>声明。

在 Google 上尝试一个查询,您会注意到每一个结果都包含一个 “相似页面” 链接,单击该链接,就会发布另一个搜索请求,查找出与起初结果类似的文档。Solr 使用 MoreLikeThisComponent(MLT)和 MoreLikeThisHandler实现了一样的功能。如上所述,MLT 是与标准 SolrRequestHandler集成在一起的;MoreLikeThisHandler与 MLT 结合在一起,并添加了一些其他选项,但它要求发布一个单一的请求。我将着重讲述 MLT,因为使用它的可能性更大一些。幸运的是,不需要任何设置就可以查询它,所以您现在就可以开始查询。

您可以向请求添加很多 HTTP 查询参数,并且大部分参数都有智能的默认值,因此我将着重讲述使用 MLT 必须了解的参数。(要了解更多的详细信息,请参见 参考资料获得 Solr wiki 的 MLT 页面链接)。

表 2. MoreLikeThisComponent参数
参数说明值域
mlt在查询时,打开 / 关闭 MoreLikeThisComponent的布尔值。真 | 假
mlt.count可选。每一个结果要检索的相似文档数。> 0
mlt.fl用于创建 MLT 查询的字段。模式中任何被储存的或含有检索词向量的字段。
mlt.maxqt可选。查询词语的最大数量。由于长文档可能会有很多关键词语,这样 MLT 查询可能会很大,从而导致反应缓慢或可怕的 TooManyClausesException,该参数只保留最关键的词语。> 0

尝试下面的样例查询,然后检查返回结果中的 moreLikeThis部分:

http://localhost:8983/solr/rss/select/?q=*%3A*&start=0&rows=10&mlt=true 
  &mlt.fl=description&mlt.count=3
http://localhost:8983/solr/rss/select/?q=solr&version=2.2&start=0&rows=10 
  &indent=on&mlt=true&mlt.fl=description&mlt.fl=title&mlt.count=3

接下来,我将介绍如何向应用程序添加 “您是不是要找……”(拼写检查)。

提供拼写建议

Lucene 和 Solr 很久以前就开始提供拼写检查功能了,但直到添加了 SearchComponent架构之后,这些功能才可以无缝使用。现在您可以输入一个查询,让它不仅返回查询结果,并且为查询词语提供拼写建议(如果存在的话)。然后可以利用这些建议像 Google 那样显示 “您是不是要找……”,或者像 Yahoo! 那样显示 “请尝试……”。

集成拼写检查的妙处在于它能够(而且必须)根据索引中的标记给出建议。也就是说,它不必根据词典给出正确拼写的词语,而是根据与查询词语相似的拼写给出拼写建议(包括错误拼写)。例如,假设很多很多的人都将单词 hockey错误拼写成 hockei。查询 hockey的用户很可能是想查找里面带有单词 hockei的文档,因为它们是相关的(尽管是这些文档的作者不会拼写)。

SpellCheckComponent与 MLT 不同,它不需要在 solrconfig.xml 和 schema.xml 文件中进行配置。首先,模式必须先声明一个 Field和一个相关联的、其内容能够发挥拼写词典的作用的 FieldType。按常规,该 FieldType的分析过程要保持简单,而且不要派生词语或修改其他标志。我的样例 FieldType声明了它的 <analyzer>,如清单 11 所示:

清单 11. 声明一个 <analyzer>
 <fieldType name="textSpell" class="solr.TextField" positionIncrementGap="100" > 
  <analyzer> 
    <tokenizer class="solr.StandardTokenizerFactory"/> 
    <filter class="solr.LowerCaseFilterFactory"/> 
    <filter class="solr.RemoveDuplicatesTokenFilterFactory"/> 
  </analyzer> 
 </fieldType>

<analyzer>负责基本的标志化(尤其是拆分空格),然后将标志变成小写并移除复制。不用派生词语,也不用扩展同义词。就是这么简单。接下来我在 schema.xml 文件中声明了一个 field,名为 spell,它使用 textSpell <fieldType>。接着,我声明了 <searchComponent>,将 solrconfig.xml 文件中的必要的部分连接起来,如清单 12 所示:

清单 12. 声明 <searchComponent>
 <searchComponent name="spellcheck" class="solr.SpellCheckComponent"> 
    <str name="queryAnalyzerFieldType">textSpell</str> 
    <lst name="spellchecker"> 
      <str name="name">default</str> 
      <str name="field">spell</str> 
      <str name="spellcheckIndexDir">./spellcheckerDefault</str> 
    </lst> 
 </searchComponent>

在这个例子中,我将前面声明的 textSpell <fieldType>queryAnalyzerFieldType关联起来。(注意,我使用前面讲述的 last-components技术将组件到 Dismax 和 solrconfig.xml 中的标准 SolrRequestHandler声明)。这能够确保正确分析输入查询,从而与拼写索引进行比较。其余的配置选项指定拼写检查器的名称、包含构建拼写索引所用的内容的 Field,以及索引在磁盘上的储存位置。

完成全部配置之后,您必须构建拼写索引。这可以通过用 HTTP 向组件发送请求来完成,比如:

http://localhost:8983/solr/rss/select/?q=foo&spellcheck=true&spellcheck.build=true

拼写检查构建工作流程

要查询拼写检查索引,必须先构建它。初始构建完成后,您需要确定(通过您的应用程序)重新构建索引的频率。您也可以在用 solrconfig.xml 中的 postCommit事件监听器完成提交之后再重新构建它。重构建的频率一定要以索引的更改数量为基准,但这一点并不是很重要,因为初始索引创建之后,很大地改动词典的可能性不大。

构建了索引之后,像往常一样查询并添加 spellcheck=true参数就会返回建议了。例如,清单 13 打开了拼写检查特性:

清单 13. 显示拼写检查的查询
 http://localhost:8983/solr/rss/select/?q=holr&spellcheck=true

运行清单 13 中的查询会返回零个结果,但是它会提供以下建议:

 <lst name="spellcheck"> 
 <lst name="suggestions"> 
  <lst name="holr"> 
	 <int name="numFound">1</int> 
	 <int name="startOffset">0</int> 

	 <int name="endOffset">4</int> 
	 <arr name="suggestion"> 
	 <str>solr</str> 
	 </arr> 
  </lst> 
 </lst> 
 </lst>

再深入一步,多个词语的查询也可以使用拼写检查。组件甚至能够自动地创建一个推荐的新查询,该查询将所有词语的最佳建议结合起来。这可以通过添加 spellcheck.collate=true参数来实现。就像在错误拼写查询中一样,

 http://localhost:8983/solr/rss/select/?q=holr+foo&spellcheck=true&indent=on 
 &spellcheck.collate=true

它生成了作为建议的一部分的结果 <str name="collation">solr for</str>。但是要注意,这个合并的结果可能不会返回结果,这取决于您是否用 AND将查询词语连接起来。

另外,拼写检查器还能采用与返回的建议数量和结果质量有关的查询参数。要想更多地了解 SpellCheckComponent的详细信息,请参见 参考资料中的 Solr wiki 页面链接。

接下来,我将介绍如何用 “付费排序” 来覆盖结果的自然排序。

编辑结果排序

在理想的情况下,搜索引擎只返回与用户查询相关的文档。而在现实的查询中,编辑(没发现更合适的表达)通常需要指定特定文档在搜索结果中的特定位置。这样做有很多原因。或许 “置顶” 的文档就是最好的查询结果。也可能是公司想让客户从相似的选择中找到利润率较高的产品。还可能是由第三方付费,提高某些查询词语的排名。不管是什么原因,对于一般的查询,要根据相关度来排名,让特定的文档出现在特定的位置,通常是很困难的(甚至是不可能的)。而且,即便搜索引擎能为某个查询达到这个目的,它也很可能会在这个过程中破坏其他 50 个查询。因此,现实中的搜索有这样一条基本规则:用户输入查询并不等于您必须搜索索引并给文档评级。我知道,以构建搜索引擎为生的人说这件事有点奇怪,但这是事实。您可以缓存普通的查询,或只查找结果(Solr 可以完成),或根据上述的某个原因 “硬编码” 结果。

Solr 使用一个神秘命名的 SearchComponent(即 QueryElevationComponent)实现了简单排名。为了在样例应用程序中配置它,我按清单 14 所示的方法声明它:

清单 14. 声明一个 QueryElevationComponent
 <searchComponent name="elevator"
  class="org.apache.solr.handler.component.QueryElevationComponent"
  > 
    <!-- pick a fieldType to analyze queries --> 
    <str name="queryFieldType">string</str> 
    <str name="config-file">elevate.xml</str> 
  </searchComponent>

queryFieldType属性指定如何将传入的查询与要提升的查询相匹配。为简单起见,string FieldType意味着查询必须是一个精确匹配的字符串,因为在 string FieldType上是不会执行任何分析的。config-file属性指定包含查询和相关联的结果的文件。它储存在一个单独的文件中,这样才能够从外部编辑它。文件必须位于 Solr 配置目录中或 Solr 数据目录中。如果它不在数据目录中,那么它会在 Solr 需要重新装载索引时再载入。

样例应用程序将 elevate.xml 储存在配置目录中。在它的内部,我为查询 “Charlotte” 添加了一个条目,以及其他 3 个条目,如清单 15 所示:

清单 15. 样例 elevate.xml 配置
 <query text="Solr"> 
 <doc 
 id="http://lucene.grantingersoll.com/2008/06/21/solr-spell-checking-addition/"/> 
 <doc 
  <!-- Line break is for formatting purposes --> 
 id="http://lucene.grantingersoll.com/2008/10/01/\ 
      charlotte-jug-%c2%bb-oct-15th-6pm-search-and-text-analysis/"
          /> 
 <doc 
 id="http://lucene.grantingersoll.com/2008/08/27/solr-logo-contest/" exclude="true"/> 
 </query>

清单 15 表明第一个链接出现的位置应该高于第二个链接,而第三个链接必须排除在结果之外。此后的结果按正常的顺序排列。想要查看正常的结果(包含这个组件时,默认打开提升),运行以下查询:

http://localhost:8983/solr/rss/select/?q=Solr&version=2.2&start=0&rows=10&indent=on 
  &fl=link&enableElevation=false

想要查看提升打开时的结果,请尝试:

http://localhost:8983/solr/rss/select/?q=Solr&version=2.2&start=0&rows=10&indent=on 
  &fl=link&enableElevation=true

应该会看到插入的提升输出。

这就是编辑排序。现在您已经能够轻松地为搜索改变查询结果,而且不会损害其他结果的质量。

SolrJ

在系列文章 使用 Apache Solr 实现更加灵巧的搜索中,我借用了一个简单的客户机,它通过 Java 平台使用 Apache HTTPClient 与 Solr 通信。现在,在 1.3 版本中,Solr 提供了一个易于使用的、基于 Java 的 API,它避免了 HTTP 链接和 XML 命令的所有弊端。这个称为 SolrJ 的新客户机使得通过 Java 代码处理 Solr 更加轻松。SolrJ API 通过良好定义的方法调用简化了索引创建、搜索、排序和分类。

同样,简单的例子或许是最好的老师。样例下载包含一个名为 SolrJExample.java 的 Java 文件。(参见下载中的 README.txt,查看有关编译的说明)。它展示了如何为 Solr 创建一些文档的索引,然后再运行一个对结果进行分类的查询。它做的第一件事是建立一个到 Solr 实例的连接,就像在 SolrServer server = new CommonsHttpSolrServer("http://localhost:8983/solr/rss");中一样。这会创建一个 SolrServer实例,该实例通过 HTTP 和 Solr 通信。接下来,我将创建一个 SolrInputDocument,用它将要创建索引的内容打包起来,如清单 16 所示:

清单 16. 使用 SolrJ 创建索引
 Collection<SolrInputDocument> docs = new HashSet<SolrInputDocument>(); 
 for (int i = 0; i < 10; i++) { 
  SolrInputDocument doc = new SolrInputDocument(); 
  doc.addField("link", "http://non-existent-url.foo/" + i + ".html"); 
  doc.addField("source", "Blog #" + i); 
  doc.addField("source-link", "http://non-existent-url.foo/index.html"); 
  doc.addField("subject", "Subject: " + i); 
  doc.addField("title", "Title: " + i); 
  doc.addField("content", "This is the " + i + "(th|nd|rd) piece of content."); 
  doc.addField("category", CATEGORIES[rand.nextInt(CATEGORIES.length)]); 
  doc.addField("rating", i); 
  //System.out.println("Doc[" + i + "] is " + doc); 
  docs.add(doc); 
 }

清单 16 中的循环只是创建了 SolrInputDocument(实际是一个夸张的 Map),然后给它添加 Field。我将它添加到了一个集合中,这样一次就能将所有的文档发送到 Solr。借助这个功能可以极大地加索引的创 ??,并减少通过 HTTP 发送请求导致的开销。然后我调用了 UpdateResponse response = server.add(docs);,它负责序列化文档并将其提交到 Solr。UpdateResponse返回的值包含处理文档所用的时间的信息。为了让这些文档能够被搜索到,我又发出一个提交命令:server.commit();

当然,创建索引之后必须查询服务器,如清单 17 带注释的代码所示:

清单 17. 查询服务器
 //create the query 
 SolrQuery query = new SolrQuery("content:piece"); 
 //indicate we want facets 
 query.setFacet(true); 
 //indicate what field to facet on 
 query.addFacetField("category"); 
 //we only want facets that have at least one entry 
 query.setFacetMinCount(1); 
 //run the query 
 QueryResponse results = server.query(query); 
 System.out.println("Query Results: " + results); 
 //print out the facets 
 List<FacetField> facets = results.getFacetFields(); 
 for (FacetField facet : facets) { 
  System.out.println("Facet:" + facet); 
 }

在这个简单的查询例子中,我设置了一个带有 content:piece请求的 SolrQuery实例。接下来,我表明自己对至少一个条目的所有的分类的分类信息感兴趣。最后,我通过 server.query(query)调用提交查询,然后把一些结果打印了出来。这的确是一个过于简单的例子,但是它展示使用 Solr 时常见的任务,因此使您想到可以实现什么功能(突出显示、排序等)。要学习更多有关用 SolrJ 查询的可用选项的知识,请参见 参考资料中的 SolrJ 链接。

用分布式搜索扩展索引大小

直到 1.3 版本,Solr 才能通过复制轻松进行扩展,以满足更大容量的查询需求。但是,如果没有应用程序帮助完成大部分工作,要提供超出单个机器的承载额度的索引还是很困难的。例如,通常可以在 Solr 中设置多个服务器,其中每一个服务器都有自己的索引,然后再让应用程序来管理搜索 —但这需要大量的自定义代码。在 1.3 版本中,Solr 添加了分布式搜索功能。应用程序将文档分布到几个计算机上,Solr(和其他程序)通常称之为 片(shard)。每一个片都包含自己的独立索引,而且 Solr 能够跨片协调索引查询。不幸的是,应用程序仍然需要将要创建索引的文档发送到每一个片,但这可能会添加到将来的 Solr 版本中。同时,可以使用一个简单的散列函数根据文档的唯一 ID 确定将文档发送到什么片。与此同时,我将关注搜索的等式方面。

Solr 机器容量

一个机器可以容纳的索引大小取决于机器的配置(RAM、CPU、磁盘等)、查询和索引的量,以及文档的大小和搜索模式。但是通常一个机器能够容纳的文档数量约在几百万到 1 亿之间。

要开始使用分布式搜索,用户需要花些时间考虑架构。如果仅需要几个片,而且不考虑复制的话,那么可以在每个机器上放置一个片,并且每一个片都能够创建索引和提供搜索。但如果索引和查询量很大的话,就必须复制每一个片。设置这种系统的常用的方法就是将每一个片及其复制放到一个载入平衡器的后面。图 2 展示了这个架构:

图 2. 分布式和复制 Solr 架构
示例搜索架构

分布式搜索的问题

Solr 的分布式搜索有几个缺点。第一,master 节点不容错,因此它一旦停止,系统就不能为新的文档创建索引或执行复制了。这并不会阻止搜索,而且对于可以手动或通过脚本和外部监控工具来管理片的较小分布式设置而言,这并不是个问题。换句话说,您不会使用当前的架构来构建下一个 Google,但一定能够提供一个大索引。第二,并不是所有的 1.3 版本中的 SearchComponent都是分布式感知的。搜索、分类、调试和突出显示组件是分布是感知的;而其他的不常用的组件,正在努力实现这个功能。在分布式搜索的 wiki 链接中,可以看到一些小警告和所有细节信息(参见 参考资料)。

注意,图 2 中输入的请求可以进入任何一个复制的片中,因为它们是功能齐全的 Solr 实例。然后,检索节点会将请求发送到其他片。这些请求仅仅是普通的 Solr 请求。要将请求提交到 Solr 服务器并分发请求,需要将 shards参数添加到请求,比如:

http://localhost:8983/solr/select? 
  shards=localhost:8983/solr,localhost:7574/solr&q=ipod+solr

在这个例子中,我假定在本地主机上运行了两个 Solr 服务器(它不是真正的分布式的;它适合于这里的论述,但不能用于您的设置),主服务器在端口 8983 上,从服务器在端口 7574 上。输入的请求进入端口 8983 上的实例,然后它将请求发送到片式服务器上。应用程序很可能将 shards参数值设置成 solrconfig.xml 文件中的 SolrRequestHandler的默认配置的一部分,这样就不需要在每次查询时都传入所有片式服务器的名称了。


结束语

Solr 1.3 发生了很大的变化。在本文中,您学习了很多新的特性,比如拼写检查、数据导入、编辑排序和分布式搜索。此外,还学习了 Solr 的增强功能,包括一个更新、更快的 Lucene 版本。Solr 有许多地方改变了,也有许多地方没有改变。Solr 仍然是一个可靠的、可行的、支持良好的搜索服务器,并且已经可以部署到企业中。现在,Solr 开发人员开始研究添加文档聚合、更多的分析选项、Windows 友好的复制以及复制文档检测等特性。


下载

描述名字大小
新特性示例j-solr-update.zip437KB

参考资料

学习

获得产品和技术

  • Apache Mirrors:下载 Solr 1.3 或最新版本。
  • PostgreSQL:下载 PostgreSQL。
  • Get Luke:这是检查 Lucene 索引的便捷工具。如果对索引的内容有疑问或查询不能正常工作,请咨询 Luke。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Open source
ArticleID=354489
ArticleTitle=Apache Solr 的新特性
publish-date=11252008