練習 2:索引電影資料

練習 2:修改 Schema 並索引電影資料

本練習將以上一個練習為基礎,向您介紹索引 schema 和 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 主機字串>

建立新的集合

我們將在本練習中使用全新的資料集,因此最好建立一個新的集合,而不是嘗試重複使用我們先前的集合。

這樣做的原因之一是我們將使用 Solr 中稱為「欄位猜測」的功能,Solr 會在索引欄位時嘗試猜測欄位中的資料類型。 它也會自動在 schema 中為傳入文件中出現的新欄位建立新欄位。 此模式稱為「無 Schema」。 我們將看到這種方法的優點和限制,以協助您決定在實際應用程式中如何以及在哪裡使用它。

什麼是「schema」,我為什麼需要它?

Solr 的 schema 是一個單一檔案 (在 XML 中),用於儲存 Solr 預期瞭解的欄位和欄位類型詳細資訊。 Schema 不僅定義欄位或欄位類型名稱,還定義在索引欄位之前應對欄位進行的任何修改。 例如,如果您想確保輸入「abc」的使用者和輸入「ABC」的使用者都可以找到包含詞彙「ABC」的文件,則您會想要在索引時正規化(在本例中為小寫)「ABC」,並正規化使用者查詢以確保匹配。 這些規則在您的 schema 中定義。

在本教學課程的前面,我們提到了複製欄位,這些欄位是由來自其他欄位的資料組成的。 您還可以定義動態欄位,這些欄位使用萬用字元(例如 *_t*_s)來動態建立特定欄位類型的欄位。 這些類型的規則也在 schema 中定義。

當您在第一個練習中最初啟動 Solr 時,我們可以選擇要使用的 configset。我們選擇的 configset 具有預先針對我們稍後索引的資料定義的 schema。這次,我們將使用一個 schema 非常精簡的 configset,讓 Solr 從資料中自行判斷要新增哪些欄位。

您要索引的資料與電影相關,因此首先建立一個名為「films」的集合,該集合使用 _default configset。

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

等等,我們沒有指定 configset!沒關係,_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"}}}

該指令列印的第一件事是警告,不建議在生產環境中使用此 configset。這是因為我們稍後會介紹的一些限制。

不過,除此之外,應該會建立該集合。如果我們前往管理介面 https://127.0.0.1:8983/solr/#/films/collection-overview,應該會看到概觀畫面。

為電影資料準備 Schemaless

使用 _default configset 附帶的 schema 時,會同時發生兩件事。

首先,我們使用的是「受管理 schema」,該 schema 設定為僅能由 Solr 的 Schema API 修改。這表示我們不應手動編輯它,以免混淆哪些編輯來自哪個來源。Solr 的 Schema API 可讓我們變更欄位、欄位類型及其他類型的 schema 規則。

其次,我們使用的是「欄位猜測」,這是在 solrconfig.xml 檔案中設定的 (並且包含 Solr 的大多數設定)。欄位猜測旨在讓我們開始使用 Solr 時,不必先定義我們認為會出現在文件中的所有欄位就可嘗試索引它們。這就是我們稱其為「schemaless」的原因,因為您可以快速開始並讓 Solr 在文件中遇到欄位時為您建立欄位。

聽起來很棒!嗯,其實不然,它有一些限制。它有點像是暴力破解,如果猜錯了,在索引資料之後,您無法對欄位進行太多變更,而必須重新索引。如果我們只有幾千個文件,那可能還好,但如果您有數百萬個文件,甚至更糟的是,無法再存取原始資料,這就會是一個真正的問題。

基於這些原因,Solr 社群不建議在沒有您自己定義的 schema 的情況下進入生產環境。我們指的是 schemaless 功能可以先使用,但您仍應確保您的 schema 符合您對資料索引方式以及使用者將如何查詢資料的期望。

可以將 schemaless 功能與已定義的 schema 混合使用。透過使用 Schema API,您可以定義幾個您知道想要控制的欄位,並讓 Solr 猜測其他不重要的欄位,或者您 (透過測試) 確信會猜測到您滿意的欄位。這就是我們在這裡要做的。

建立「names」欄位

我們將要索引的電影資料,每個電影都有少數幾個欄位: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」(文字欄位)。它不允許有多個值,但會被儲存 (表示可以透過查詢擷取)。

您也可以使用管理介面建立欄位,但它對欄位的屬性提供的控制權較少。不過,它對我們的案例來說足夠了。

Adding a Field
圖 1. 建立欄位
建立「catchall」複製欄位

在開始索引之前,還需要進行一項變更。

在第一個練習中,當我們查詢已索引的文件時,我們不必指定要搜尋的欄位,因為我們使用的設定會將欄位複製到 text 欄位中,而且當查詢中沒有定義其他欄位時,該欄位是預設值。

我們現在使用的設定沒有該規則。我們需要為每個查詢定義要搜尋的欄位。不過,我們可以透過定義一個複製欄位來設定「catchall 欄位」,該欄位會從所有欄位中取得所有資料,並將其索引到名為 _text_ 的欄位中。現在就來做吧。

您可以使用管理介面或 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

在管理介面中,選擇 新增複製欄位,然後填寫欄位的來源和目的地,如下圖所示。

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

太棒了!

如果您前往電影的管理介面查詢畫面 (https://127.0.0.1:8983/solr/#/films/query) 並按下 執行查詢,您應該會看到 1100 個結果,並且前 10 個結果會顯示在螢幕上。

讓我們執行一個查詢,看看「catchall」欄位是否正常運作。在 q 方塊中輸入「comedy」,然後再次按下 執行查詢。您應該會看到 417 個結果。歡迎在我們繼續進行分面之前,試用其他搜尋。

分面

分面是 Solr 最受歡迎的功能之一。分面允許將搜尋結果排列成子集 (或儲存區或類別),並為每個子集提供計數。分面有幾種類型:欄位值、數值和日期範圍、樞紐 (決策樹) 以及任意查詢分面。

欄位分面

除了提供搜尋結果之外,Solr 查詢還可以傳回整個結果集中包含每個唯一值的文件的數量。

在管理介面查詢標籤上,如果您勾選 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,您可以使用它來限制僅包含一定數量文件的分面 (此參數未顯示在使用者介面中)。或者,您可能想要所有分面,並讓您應用程式的前端控制它向使用者顯示的方式。

如果您想要控制儲存區中的項目數,您可以執行類似以下的作業:

$ 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 Admin 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 來操作 Schema 檔案的知識。我們還了解了一些關於 Solr 中分面的知識,包括範圍分面和樞紐分面。在這兩方面,我們都只是淺嚐了可用選項的表面。如果您能夢想出來,它就有可能實現!

與我們之前的練習一樣,此資料可能與您的需求無關。我們可以透過刪除集合來清理我們的工作。為此,請在命令列中發出此命令

$ bin/solr delete -c films