练习 2:索引电影数据

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

本练习将以上一个练习为基础,向您介绍索引模式和 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:
https://127.0.0.1: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"}}}

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

但是,除此之外,应该会创建该集合。如果我们在 https://127.0.0.1:8983/solr/#/films/collection-overview 访问管理 UI,我们应该会看到概览屏幕。

为电影数据准备无模式

使用 _default 配置集中的模式时,会发生两个并行的事情。

首先,我们正在使用“托管模式”,该模式配置为只能由 Solr 的 Schema API 修改。这意味着我们不应该手动编辑它,以免混淆哪些编辑来自哪个来源。Solr 的 Schema API 允许我们更改字段、字段类型和其他类型的模式规则。

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

听起来很棒!好吧,事实并非如此,存在限制。它有点粗暴,如果它猜测错误,那么在数据被索引后,你不能对字段进行太多更改,除非重新索引。如果我们只有几千个文档,那可能还不错,但如果你有数百万甚至数百万的文档,或者更糟糕的是,你不再有权访问原始数据,这可能会成为一个真正的问题。

出于这些原因,Solr 社区不建议在没有你自己定义的模式的情况下投入生产。我们的意思是,无模式功能可以作为开始,但您仍然应该始终确保您的模式与您希望数据索引的方式以及用户将如何查询它的期望相符。

可以将无模式功能与定义的模式混合使用。使用 Schema API,您可以定义一些您知道要控制的字段,并让 Solr 猜测其他不太重要或您确信(通过测试)可以被猜测到令您满意的字段。这就是我们在这里要做的。

创建“名称”字段

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

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

好吧,这行不通。我们有诸如 A Mighty WindChicken Run 之类的标题,它们是字符串 - 绝对不是数字和浮点数。如果我们让 Solr 猜测“name”字段是一个浮点数,那么稍后的标题将会导致错误,并且索引将失败。这不会让我们走得很远。

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

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

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

您也可以使用管理 UI 创建字段,但它对字段的属性的控制较少。不过,它适用于我们的情况

Adding a Field
图 1. 创建字段
创建“捕获所有”复制字段

在我们开始索引之前,还有一处更改需要进行。

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

我们现在使用的配置没有该规则。我们需要为每个查询定义要搜索的字段。但是,我们可以通过定义一个复制字段来设置“捕获所有字段”,该字段将从所有字段中获取所有数据并将其索引到名为 _text_ 的字段中。现在就让我们这样做。

您可以使用管理 UI 或 Schema API 来执行此操作。

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

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

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

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

这样做是将所有字段的副本放入“_text_”字段中。

对您的生产数据执行此操作可能会非常昂贵,因为它会告诉 Solr 有效地对所有内容进行两次索引。它会使索引变慢,并使您的索引更大。对于您的生产数据,您需要确保只复制真正适合您的应用程序的字段。

好的,现在我们准备好索引数据并开始使用它了。

索引示例电影数据

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

示例 1. 索引 JSON 格式
$ bin/solr post -c films example/films/films.json
示例 2. 索引 XML 格式
$ bin/solr post -c films example/films/films.xml
示例 3. 索引 CSV 格式
$ 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=|"

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

  • -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 https://127.0.0.1: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 https://127.0.0.1:8983/solr/films/update...
Time spent: 0:00:00.878

好耶!

如果您转到电影的管理 UI 中的查询屏幕(https://127.0.0.1:8983/solr/#/films/query)并点击 执行查询,您应该会看到 1100 个结果,并且前 10 个结果会返回到屏幕。

让我们执行一个查询,看看“捕获所有”字段是否正常工作。在 q 框中输入“comedy”,然后再次点击 执行查询。您应该看到 417 个结果。在继续分面之前,请随意尝试其他搜索。

分面

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

字段分面

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

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

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

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

$ curl "https://127.0.0.1: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 "https://127.0.0.1:8983/solr/films/select?=&q=\*:*&facet.field=genre_str&facet.mincount=200&facet=on&rows=0"

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

还有许多其他参数可用于帮助您控制 Solr 如何构造分面和分面列表。我们将在本练习中介绍其中的一些参数,但您也可以查看 分面 部分以了解更多详细信息。

范围分面

对于数值或日期,通常需要将分面计数划分为范围而不是离散值。使用我们上一个练习中的示例 techproducts 数据,数值范围分面的一个主要示例是 price。电影数据包括电影的发行日期,我们可以使用它来创建日期范围分面,这是范围分面的另一个常见用途。

Solr 管理 UI 尚不支持范围分面选项,因此您需要使用 curl 或类似的命令行工具来执行以下示例。

如果我们构造一个如下所示的查询

$ curl "https://127.0.0.1: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 "https://127.0.0.1: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 来操作模式文件。我们还了解了 Solr 中的分面,包括范围分面和数据透视分面。在这两个方面,我们都只触及了可用选项的表面。如果您能梦想它,它可能会成为现实!

和我们之前的练习一样,这些数据可能与您的需求无关。我们可以通过删除集合来清理我们的工作。要做到这一点,请在命令行发出以下命令

$ bin/solr delete -c films