练习 2:索引电影数据

练习 2:修改模式并索引电影数据

本练习将基于上一个练习,向您介绍索引模式和 Solr 强大的分面功能。

重新启动 Solr

您在上一个练习后停止了 Solr 吗?没有?那么请继续进行下一部分。

但是,如果您确实停止了 Solr 并需要重新启动 Solr,请发出以下命令

$ bin/solr start -c -p 8983 -s example/cloud/node1/solr

这将启动第一个节点。完成后,启动第二个节点,并告诉它如何连接到 ZooKeeper

$ bin/solr start -c -p 7574 -s example/cloud/node2/solr -z localhost:9983
如果您已在 solr.in.sh/solr.in.cmd 中定义了 ZK_HOST(请参阅更新 Solr 包含文件),则可以从上述命令中省略 -z <zk host string>

创建新集合

我们将在本练习中使用全新的数据集,因此最好使用新集合,而不是尝试重复使用我们之前拥有的集合。

这样做的一个原因是,我们将使用 Solr 中的一项名为“字段猜测”的功能,在 Solr 索引字段时,它会尝试猜测字段中数据的类型。它还会在模式中为传入文档中出现的新字段自动创建新字段。此模式称为“无模式”。我们将了解此方法的优点和局限性,以帮助您决定如何在实际应用程序中使用它以及在何处使用它。

什么是“模式”,我为什么需要它?

Solr 的模式是一个(XML 中的)单一文件,其中存储了 Solr 预期理解的字段和字段类型的详细信息。模式不仅定义字段或字段类型名称,还定义在索引字段之前应发生的任何修改。例如,如果您想确保输入“abc”的用户和输入“ABC”的用户都可以找到包含术语“ABC”的文档,您将希望在索引“ABC”时对其进行规范化(在本例中为小写),并规范化用户查询以确保匹配。这些规则在您的模式中定义。

在本教程的前面部分,我们提到了复制字段,这些字段由源自其他字段的数据组成。您还可以定义动态字段,它们使用通配符(例如 *_t*_s)来动态创建特定字段类型的字段。这些类型的规则也在架构中定义。

当您在第一个练习中最初启动 Solr 时,我们可以选择要使用的配置集。我们选择的那个配置集具有为我们稍后索引的数据预定义的架构。这次,我们将使用具有极简架构的配置集,并让 Solr 从数据中找出要添加哪些字段。

您要索引的数据与电影相关,因此首先创建一个名为“films”的集合,该集合使用 _default 配置集

$ bin/solr create -c films -s 2 -rf 2

等等,我们没有指定配置集!没关系,_default 的命名很恰当,因为它是默认值,如果您根本没有指定,则会使用它。

不过,我们确实设置了两个参数 -s-rf。它们是将集合拆分为多个分片(2)的数量以及要创建的副本数(2)。这相当于我们在第一个练习的交互式示例中拥有的选项。

您应该看到如下输出

WARNING: Using _default configset. Data driven schema functionality is enabled by default, which is
         NOT RECOMMENDED for production use.

         To turn it off:
            bin/solr config -c films -p 7574 -action set-user-property -property update.autoCreateFields -value false

Connecting to ZooKeeper at localhost:9983 ...
INFO  - 2017-07-27 15:07:46.191; org.apache.solr.client.solrj.impl.ZkClientClusterStateProvider; Cluster at localhost:9983 ready
Uploading /{solr-full-version}/server/solr/configsets/_default/conf for config films to ZooKeeper at localhost:9983

Creating new collection 'films' using command:
http://localhost:7574/solr/admin/collections?action=CREATE&name=films&numShards=2&replicationFactor=2&collection.configName=films

{
  "responseHeader":{
    "status":0,
    "QTime":3830},
  "success":{
    "192.168.0.110:8983_solr":{
      "responseHeader":{
        "status":0,
        "QTime":2076},
      "core":"films_shard2_replica_n1"},
    "192.168.0.110:7574_solr":{
      "responseHeader":{
        "status":0,
        "QTime":2494},
      "core":"films_shard1_replica_n2"}}}

该命令打印的第一件事是关于在生产中不使用此配置集的警告。这是由于我们稍后将介绍的一些限制。

不过,除此之外,应该创建集合。如果我们转到 http://localhost:8983/solr/#/films/collection-overview 的管理用户界面,我们应该会看到概述屏幕。

为影片数据准备无模式

使用 _default 配置集附带的架构有两个并行发生的事情。

首先,我们正在使用“托管架构”,该架构配置为仅由 Solr 的架构 API 修改。这意味着我们不应手动编辑它,以免对哪些编辑来自哪个来源感到困惑。Solr 的架构 API 允许我们对字段、字段类型和其他类型的架构规则进行更改。

其次,我们正在使用“字段猜测”,它在 solrconfig.xml 文件中配置(并包括 Solr 的大多数各种配置设置)。字段猜测旨在让我们在尝试索引文档之前不必定义我们认为文档中将包含的所有字段的情况下开始使用 Solr。这就是我们称之为“无模式”的原因,因为您可以快速开始,并让 Solr 在文档中遇到字段时为您创建字段。

听起来不错!好吧,实际上并非如此,存在一些限制。它有点蛮力,如果它猜测错误,在重新索引之前,你无法在数据已编入索引后对字段进行太多更改。如果我们只有几千个文档,这可能还不错,但如果你有数百万个文档,或者更糟的是,无法再访问原始数据,这可能是一个真正的问题。

出于这些原因,Solr 社区不建议在没有自己定义的架构的情况下投入生产。我们的意思是,无架构特性一开始很好,但你仍应始终确保你的架构符合你对想要如何编制数据索引以及用户将如何查询它的期望。

可以将无架构特性与已定义的架构混合使用。使用架构 API,你可以定义一些你已知想要控制的字段,并让 Solr 猜测其他不那么重要的字段,或者你确信(通过测试)将根据你的满意度进行猜测。这就是我们在这里要做的。

创建“名称”字段

我们将要编制索引的电影数据为每部电影提供了少量字段:ID、导演姓名、电影名称、发行日期和类型。

如果你查看 example/films 中的一个文件,你将看到第一部电影名为 .45,发行于 2006 年。作为数据集中第一个文档,Solr 将根据记录中的数据猜测字段类型。如果我们继续编制此数据的索引,第一个电影名称将向 Solr 表明字段类型是“浮点”数字字段,并将创建一个类型为 FloatPointField 的“名称”字段。此记录之后的所有数据都应为浮点数。

好吧,这行不通。我们有诸如 A Mighty WindChicken Run 之类的标题,它们是字符串 - 绝对不是数字,也不是浮点数。如果我们让 Solr 猜测“名称”字段是浮点数,那么会发生的事情是,后面的标题将导致错误,并且索引编制将失败。这不会让我们走得太远。

我们可以做的是在编制数据索引之前在 Solr 中设置“名称”字段,以确保 Solr 始终将其解释为字符串。在命令行中,输入此 curl 命令

$ curl -X POST -H 'Content-type:application/json' --data-binary '{"add-field": {"name":"name", "type":"text_general", "multiValued":false, "stored":true}}' http://localhost:8983/solr/films/schema

此命令使用架构 API 来显式定义一个名为“名称”的字段,该字段具有“text_general”(文本字段)字段类型。它不允许具有多个值,但它将被存储(这意味着可以通过查询检索它)。

你还可以使用管理 UI 来创建字段,但它对字段的属性的控制力稍弱。不过,它适用于我们的案例

Adding a Field
图 1. 创建字段
创建一个“万能”复制字段

在开始索引之前,还需要进行一项更改。

在查询已索引文档的第一个练习中,我们不必指定要搜索的字段,因为我们使用的配置已设置为将字段复制到text字段,并且当查询中未定义其他字段时,该字段为默认字段。

我们现在使用的配置没有该规则。我们需要为每个查询定义一个要搜索的字段。但是,我们可以通过定义一个复制字段来设置一个“万能字段”,该字段将获取所有字段中的所有数据并将其编入索引到名为_text_的字段中。我们现在就来执行此操作。

你可以为此使用 Admin UI 或 Schema API。

在命令行中,再次使用 Schema API 来定义一个复制字段

$ curl -X POST -H 'Content-type:application/json' --data-binary '{"add-copy-field" : {"source":"*","dest":"_text_"}}' http://localhost:8983/solr/films/schema

在 Admin UI 中,选择添加复制字段,然后填写字段的源和目标,如下面的屏幕截图所示。

Adding a copy field
图 2. 创建复制字段

此操作将复制所有字段并将数据放入“_text_”字段中。

使用你的生产数据执行此操作可能会非常昂贵,因为它会告诉 Solr 有效地将所有内容编入索引两次。这会使索引编制速度变慢,并使索引变大。对于你的生产数据,你将希望确保仅复制真正需要复制的字段。

好的,现在我们可以对数据编制索引并开始对其进行操作。

编制示例电影数据索引

我们将编制索引的电影数据位于安装的example/films目录中。它有三种格式:JSON、XML 和 CSV。选择一种格式并将其编入“films”集合(在每个示例中,一个命令适用于 Unix/MacOS,另一个适用于 Windows)

编制 JSON 格式索引

Linux/Mac

$ bin/solr post -c films example/films/films.json

Windows

$ bin/solr post -c films example\films\films.json
编制 XML 格式索引

Linux/Mac

$ bin/solr post -c films example/films/films.xml

Windows

$ java -jar -Dc=films -Dauto example\exampledocs\post.jar example\films\*.xml
编制 CSV 格式索引

Linux/Mac

$ bin/solr post -c films example/films/films.csv -params "f.genre.split=true&f.directed_by.split=true&f.genre.separator=|&f.directed_by.separator=|"

Windows

$ java -jar -Dc=films -Dparams=f.genre.split=true&f.directed_by.split=true&f.genre.separator=|&f.directed_by.separator=| -Dauto example\exampledocs\post.jar example\films\*.csv

每个命令都包含以下主要参数

  • -c films:这是要将数据编入索引的 Solr 集合。

  • example/films/films.json(或 films.xmlfilms.csv):这是要索引的数据文件的路径。你可以简单地提供此文件所在的目录,但由于你了解要索引的格式,因此指定该格式的确切文件效率更高。

请注意,CSV 命令包含额外的参数。这是为了确保“genre”和“directed_by”列中的多值条目由管道 (|) 字符分隔,该文件使用该字符作为分隔符。通过这种方式告知 Solr 分隔这些列将确保正确索引数据。

每个命令都将生成类似于 JSON 索引时看到的以下输出

$ bin/solr post -c films example/films/films.json
Posting files to [base] url http://localhost:8983/solr/films/update...
Entering auto mode. File endings considered are xml,json,jsonl,csv,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,odp,ods,ott,otp,ots,rtf,htm,html,txt,log
POSTing file films.json (application/json) to [base]/json/docs
1 files indexed.
COMMITting Solr index changes to http://localhost:8983/solr/films/update...
Time spent: 0:00:00.878

万岁!

如果你转到影片的 Admin UI 中的查询屏幕 (http://localhost:8983/solr/#/films/query) 并点击 执行查询,你应该会看到 1100 个结果,前 10 个返回到屏幕。

让我们进行查询,看看“catchall”字段是否正常工作。在 q 框中输入“comedy”,然后再次点击 执行查询。你应该会看到 417 个结果。在我们继续讨论分面之前,可以随意尝试其他搜索。

分面

Solr 最受欢迎的功能之一是分面。分面允许将搜索结果排列成子集(或存储区或类别),并为每个子集提供计数。有几种分面类型:字段值、数字和日期范围、枢轴(决策树)和任意查询分面。

字段分面

除了提供搜索结果之外,Solr 查询还可以返回整个结果集中包含每个唯一值的文档数。

在 Admin UI 查询选项卡上,如果你选中 facet 复选框,你将看到一些与分面相关的选项出现

Solr Quick Start: Query tab facet options
图 3. 查询屏幕中的分面选项

要查看所有文档的分面计数 (q=*:*):启用分面 (facet=true),并通过 facet.field 参数指定要分面的字段。如果你只想获取分面,而不要文档内容,请指定 rows=0。下面的 curl 命令将返回 genre_str 字段的分面计数

$ curl "http://localhost:8983/solr/films/select?q=\*:*&rows=0&facet=true&facet.field=genre_str"`

在你的终端中,你将看到类似以下内容

{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":11,
    "params":{
      "q":"*:*",
      "facet.field":"genre_str",
      "rows":"0",
      "facet":"true"}},
  "response":{"numFound":1100,"start":0,"maxScore":1.0,"docs":[]
  },
  "facet_counts":{
    "facet_queries":{},
    "facet_fields":{
      "genre_str":[
        "Drama",552,
        "Comedy",389,
        "Romance Film",270,
        "Thriller",259,
        "Action Film",196,
        "Crime Fiction",170,
        "World cinema",167]},
        "facet_ranges":{},
        "facet_intervals":{},
        "facet_heatmaps":{}}}

我们在这里稍微截断了输出,但在 facet_counts 部分,您会看到默认情况下,您可以获得使用索引中每个流派的文档数量的计数。Solr 有一个参数 facet.mincount,您可以使用它将切面限制为仅包含一定数量文档的切面(UI 中未显示此参数)。或者,也许您确实想要所有切面,并且您将让应用程序的前端控制如何向用户显示它。

如果您想控制存储桶中的项目数量,则可以执行以下操作

$ curl "http://localhost:8983/solr/films/select?=&q=\*:*&facet.field=genre_str&facet.mincount=200&facet=on&rows=0"

您应该只看到返回 4 个切面。

有许多其他参数可用于帮助您控制 Solr 如何构建切面和切面列表。我们将在本练习中介绍其中一些,但您也可以参阅 切面 部分以获取更多详细信息。

范围切面

对于数字或日期,通常希望将切面计数划分为范围而不是离散值。使用我们之前练习中的示例技术产品数据进行数字范围切面的一个典型示例是 price。电影数据包括电影的发行日期,我们可以用它来创建日期范围切面,这是范围切面的另一种常见用途。

Solr Admin UI 还不支持范围切面选项,因此您需要对以下示例使用 curl 或类似的命令行工具。

如果我们构建一个类似这样的查询

$ curl "http://localhost:8983/solr/films/select?q=*:*&rows=0\
&facet=true\
&facet.range=initial_release_date\
&facet.range.start=NOW/YEAR-25YEAR\
&facet.range.end=NOW\
&facet.range.gap=%2B1YEAR"

这将请求所有电影,并要求按年份分组,从 25 年前(我们最早的发行日期是 2000 年)开始,到今天结束。请注意,此查询 URL 将 + 编码为 %2B

在终端中,您将看到

{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":8,
    "params":{
      "facet.range":"initial_release_date",
      "facet.limit":"300",
      "q":"*:*",
      "facet.range.gap":"+1YEAR",
      "rows":"0",
      "facet":"on",
      "facet.range.start":"NOW-25YEAR",
      "facet.range.end":"NOW"}},
  "response":{"numFound":1100,"start":0,"maxScore":1.0,"docs":[]
  },
  "facet_counts":{
    "facet_queries":{},
    "facet_fields":{},
    "facet_ranges":{
      "initial_release_date":{
        "counts":[
          "1997-01-01T00:00:00Z",0,
          "1998-01-01T00:00:00Z",0,
          "1999-01-01T00:00:00Z",0,
          "2000-01-01T00:00:00Z",80,
          "2001-01-01T00:00:00Z",94,
          "2002-01-01T00:00:00Z",112,
          "2003-01-01T00:00:00Z",125,
          "2004-01-01T00:00:00Z",166,
          "2005-01-01T00:00:00Z",167,
          "2006-01-01T00:00:00Z",173,
          "2007-01-01T00:00:00Z",45,
          "2008-01-01T00:00:00Z",13,
          "2009-01-01T00:00:00Z",5,
          "2010-01-01T00:00:00Z",1,
          "2011-01-01T00:00:00Z",0,
          "2012-01-01T00:00:00Z",0,
          "2013-01-01T00:00:00Z",2,
          "2014-01-01T00:00:00Z",0,
          "2015-01-01T00:00:00Z",1,
          "2016-01-01T00:00:00Z",0],
        "gap":"+1YEAR",
        "start":"1997-01-01T00:00:00Z",
        "end":"2017-01-01T00:00:00Z"}},
    "facet_intervals":{},
    "facet_heatmaps":{}}}

透视切面

另一种切面类型是透视切面,也称为“决策树”,允许两个或多个字段嵌套以实现所有可能的组合。使用电影数据,透视切面可用于查看“戏剧”类别(genre_str 字段)中有多少电影是由导演执导的。以下是如何获取此方案的原始数据

$ curl "http://localhost:8983/solr/films/select?q=\*:*&rows=0&facet=on&facet.pivot=genre_str,directed_by_str"

这将产生以下响应,其中显示了每个类别和导演组合的切面

{"responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":1147,
    "params":{
      "q":"*:*",
      "facet.pivot":"genre_str,directed_by_str",
      "rows":"0",
      "facet":"on"}},
  "response":{"numFound":1100,"start":0,"maxScore":1.0,"docs":[]
  },
  "facet_counts":{
    "facet_queries":{},
    "facet_fields":{},
    "facet_ranges":{},
    "facet_intervals":{},
    "facet_heatmaps":{},
    "facet_pivot":{
      "genre_str,directed_by_str":[{
          "field":"genre_str",
          "value":"Drama",
          "count":552,
          "pivot":[{
              "field":"directed_by_str",
              "value":"Ridley Scott",
              "count":5},
            {
              "field":"directed_by_str",
              "value":"Steven Soderbergh",
              "count":5},
            {
              "field":"directed_by_str",
              "value":"Michael Winterbottom",
              "count":4}}]}]}}}

我们也截断了此输出 - 您将在屏幕上看到许多流派和导演。

练习 2 总结

在本练习中,我们进一步了解了 Solr 如何在索引中组织数据,以及如何使用 Schema API 操纵 schema 文件。我们还了解了 Solr 中的切面,包括范围切面和透视切面。在这两件事中,我们只触及了可用选项的表面。如果您能梦想它,它就可能实现!

与我们之前的练习一样,此数据可能与您的需求无关。我们可以通过删除集合来清理我们的工作。要执行此操作,请在命令行中发出此命令

$ bin/solr delete -c films