Elasticsearch(四)-映射和分析

映射和分析

數據類型差異

映射(mapping)機制用於進行字段類型確認,將每個字段匹配爲一種確定的數據類型(string, number, booleans, date等)。

分析(analysis)機制用於進行全文文本(Full Text)的分詞,以建立供搜索用的反向索引。

通過下面的請求。查看document的mapping關係:

GET /gb/_mapping/tweet

在Elasticsearch中核心數據類型(strings, numbers, booleans及dates)以不同的方式進行索引。

但是更大的區別在於確切值(exact values)(比如string類型)及全文文本(full text)之間。

這兩者的區別才真的很重要 - 這是區分搜索引擎和其他數據庫的根本差異。

確切值對決全文文本

確切值(Exact values) vs. 全文文本(Full text)

Elasticsearch中的數據可以大致分爲兩種類型:確切值 及 全文文本。

確切值是確定的,正如它的名字一樣。比如一個date或用戶ID,也可以包含更多的字符串比如username或email地址。

確切值”Foo”和”foo”就並不相同。確切值2014和2014-09-15也不相同。

全文文本,從另一個角度來說是文本化的數據(常常以人類的語言書寫),比如一篇推文(Twitter的文章)或郵件正文。

爲了方便在全文文本字段中進行這些類型的查詢,Elasticsearch首先對文本分析(analyzes),然後使用結果建立一個倒排索引。

倒排索引

倒排索引

Elasticsearch使用一種叫做倒排索引(inverted index)的結構來做快速的全文搜索。倒排索引由在文檔中出現的唯一的單詞列表,以及對於每個單詞在文檔中的位置組成。

例如,我們有兩個文檔,每個文檔content字段包含:

The quick brown fox jumped over the lazy dog
Quick brown foxes leap over lazy dogs in summer

爲了創建倒排索引,我們首先切分每個文檔的content字段爲單獨的單詞(我們把它們叫做詞(terms)或者表徵(tokens)),把所有的唯一詞放入列表並排序,結果是這個樣子的:

Term Doc_1 Doc_2
Quick X
The X
brown X X
dog X
dogs X
fox X
foxes X
in X
jumped X
lazy X X
leap X
over X X
quick X
summer X
the X

現在,如果我們想搜索”quick brown”,我們只需要找到每個詞在哪個文檔中出現即可:

Term Doc_1 Doc_2
brown X X
quick X
Total 2 1

兩個文檔都匹配,但是第一個比第二個有更多的匹配項。 如果我們加入簡單的相似度算法(similarity algorithm),計算匹配單詞的數目,這樣我們就可以說第一個文檔比第二個匹配度更高——對於我們的查詢具有更多相關性。

IMPORTANT

這很重要。你只可以找到確實存在於索引中的詞,所以索引文本和查詢字符串都要標準化爲相同的形式。

這個標記化和標準化的過程叫做分詞(analysis),這個在下節中我們討論。

分析

分析和分析器

分析(analysis)是這樣一個過程:

  1. 首先,標記化一個文本塊爲適用於倒排索引單獨的詞(term)
  2. 然後標準化這些詞爲標準形式,提高它們的“可搜索性”或“查全率”

這個工作是分析器(analyzer)完成的。一個分析器(analyzer)只是一個包裝用於將三個功能放到一個包裏:

字符過濾器

首先字符串經過字符過濾器(character filter),它們的工作是在標記化前處理字符串。字符過濾器能夠去除HTML標記,或者轉換”&”爲”and”。

分詞器

下一步,分詞器(tokenizer)被標記化成獨立的詞。一個簡單的分詞器(tokenizer)可以根據空格或逗號將單詞分開。

標記過濾

最後,每個詞都通過所有標記過濾(token filters),它可以修改詞(例如將”Quick”轉爲小寫),去掉詞(例如停用詞像”a”、”and”、”the”等等),或者增加詞(例如同義詞像”jump”和”leap”)

Elasticsearch提供很多開箱即用的字符過濾器,分詞器和標記過濾器。這些可以組合來創建自定義的分析器以應對不同的需求。

內建的分析器

Elasticsearch還附帶了一些預裝的分析器,可以直接使用它們。下面來演示這個字符串分詞後的表現差異:

"Set the shape to semi-transparent by calling set_trans(5)"

標準分析器

標準分析器是Elasticsearch默認使用的分析器。對於文本分析,它對於任何語言都是最佳選擇。它根據Unicode Consortium的定義的單詞邊界(word boundaries)來切分文本,然後去掉大部分標點符號。最後,把所有詞轉爲小寫。產生的結果爲:

set, the, shape, to, semi, transparent, by, calling, set_trans, 5

簡單分析器

簡單分析器將非單個字母的文本切分,然後把每個詞轉爲小寫。產生的結果爲:

set, the, shape, to, semi, transparent, by, calling, set, trans

空格分析器

空格分析器依據空格切分文本。它不轉換小寫。產生結果爲:

Set, the, shape, to, semi-transparent, by, calling, set_trans(5)

語言分析器

特定語言分析器適用於很多語言。它們能夠考慮到特定語言的特性。例如,english分析器自帶一套英語停用詞庫——像and或the這些與語義無關的通用詞。這些詞被移除後,因爲語法規則的存在,英語單詞的主體含義依舊能被理解。

english分析器將會產生以下結果:

set, shape, semi, transpar, call, set_tran, 5

當分析器被使用

當我們索引(index)一個文檔,全文字段會被分析爲單獨的詞來創建倒排索引。不過,當我們在全文字段搜索(search)時,我們要讓查詢字符串經過同樣的分析流程處理,以確保這些詞在索引中存在。

全文查詢我們將在稍後討論,理解每個字段是如何定義的,這樣纔可以讓它們做正確的事:

  • 當你查詢全文(full text)字段,查詢將使用相同的分析器來分析查詢字符串,以產生正確的詞列表。
  • 當你查詢一個確切值(exact value)字段,查詢將不分析查詢字符串,但是你可以自己指定。

現在可以解釋《映射和分析》的開頭會產生那種結果:

  • date字段包含一個確切值:單獨的一個詞”2014-09-15”。
  • _all字段是一個全文字段,所以分析過程將日期轉爲三個詞:”2014”、”09”和”15”。

當我們在_all字段查詢2014,它一個匹配到12條推文,因爲這些推文都包含詞2014:

GET /_search?q=2014 # 12 results

當我們在_all字段中查詢2014-09-15,首先分析查詢字符串,產生匹配任一詞2014、09或15的查詢語句,它依舊匹配12個推文,因爲它們都包含詞2014。

GET /_search?q=2014-09-15 # 12 results !

當我們在date字段中查詢2014-09-15,它查詢一個確切的日期,然後只找到一條推文:

GET /_search?q=date:2014-09-15 # 1 result

當我們在date字段中查詢2014,沒有找到文檔,因爲沒有文檔包含那個確切的日期:

GET /_search?q=date:2014 # 0 results !

測試分析器

尤其當你是Elasticsearch新手時,對於如何分詞以及存儲到索引中理解起來比較困難。爲了更好的理解如何進行,你可以使用analyze API來查看文本是如何被分析的。在查詢字符串參數中指定要使用的分析器,被分析的文本做爲請求體:

GET /_analyze?analyzer=standard&text=Text to analyze

token是一個實際被存儲在索引中的詞。position指明詞在原文本中是第幾個出現的。start_offsetend_offset表示詞在原文本中佔據的位置。

指定分析器

當Elasticsearch在你的文檔中探測到一個新的字符串字段,它將自動設置它爲全文string字段並用standard分析器分析。

你不可能總是想要這樣做。也許你想使用一個更適合這個數據的語言分析器。或者,你只想把字符串字段當作一個普通的字段——不做任何分析,只存儲確切值,就像字符串類型的用戶ID或者內部狀態字段或者標籤。

爲了達到這種效果,我們必須通過映射(mapping)人工設置這些字段。

可以參考:elasticsearch 2.3.4 java API 連接,ik分詞器,設置集羣節點,創建index,mapping的幾種方式

映射

爲了能夠把日期字段處理成日期,把數字字段處理成數字,把字符串字段處理成全文本(Full-text)或精確的字符串值,Elasticsearch需要知道每個字段裏面都包含了什麼類型。這些類型和字段的信息存儲(包含)在映射(mapping)中。

核心簡單字段類型

類型 表示的數據類型
String string
Whole number byte, short, integer, long
Floating point float, double
Boolean boolean
Date date

注意

這意味着,如果你索引一個帶引號的數字——”123”,它將被映射爲”string”類型,而不是”long”類型。然而,如果字段已經被映射爲”long”類型,Elasticsearch將嘗試轉換字符串爲long,並在轉換失敗時會拋出異常。

查看映射

我們可以使用_mapping後綴來查看Elasticsearch中的映射。在本章開始我們已經找到索引gb類型tweet中的映射:GET /gb/_mapping/tweet

自定義字段映射

雖然大多數情況下基本數據類型已經能夠滿足,但你也會經常需要自定義一些特殊類型(fields),特別是字符串字段類型。 自定義類型可以使你完成以下幾點:

  • 區分全文(full text)字符串字段和準確字符串字段(譯者注:就是分詞與不分詞,全文的一般要分詞,準確的就不需要分詞,比如『中國』這個詞。全文會分成『中』和『國』,但作爲一個國家標識的時候我們是不需要分詞的,所以它就應該是一個準確的字符串字段)。
  • 使用特定語言的分析器(譯者注:例如中文、英文、阿拉伯語,不同文字的斷字、斷詞方式的差異)
  • 優化部分匹配字段
  • 指定自定義日期格式(譯者注:這個比較好理解,例如英文的 Feb,12,2016 和 中文的 2016年2月12日)
  • 以及更多

string類型的字段,默認的,考慮到包含全文本,它們的值在索引前要經過分析器分析,並且在全文搜索此字段前要把查詢語句做分析處理。

對於string字段,兩個最重要的映射參數是index和analyer。

index

index參數控制字符串以何種方式被索引。它包含以下三個值當中的一個:

解釋
analyzed 首先分析這個字符串,然後索引。換言之,以全文形式索引此字段。
not_analyzed 索引這個字段,使之可以被搜索,但是索引內容和指定值一樣。不分析此字段。
no 不索引這個字段。這個字段不能爲搜索到

string類型字段默認值是analyzed。如果我們想映射字段爲確切值,我們需要設置它爲

not_analyzed:
{
    "tag": {
        "type":     "string",
        "index":    "not_analyzed"
    }
}

其他簡單類型(long、double、date等等)也接受index參數,但相應的值只能是no和not_analyzed,它們的值不能被分析。

分析

對於analyzed類型的字符串字段,使用analyzer參數來指定哪一種分析器將在搜索和索引的時候使用。默認的,Elasticsearch使用standard分析器,但是你可以通過指定一個內建的分析器來更改它,例如whitespace、simple或english。

{
    "tweet": {
        "type":     "string",
        "analyzer": "english"
    }
}

更新映射

你可以在第一次創建索引的時候指定映射的類型。此外,你也可以晚些時候爲新類型添加映射(或者爲已有的類型更新映射)。

重要

你可以向已有映射中增加字段,但你不能修改它。如果一個字段在映射中已經存在,這可能意味着那個字段的數據已經被索引。如果你改變了字段映射,那已經被索引的數據將錯誤並且不能被正確的搜索到。

我們可以更新一個映射來增加一個新字段,但是不能把已有字段的類型那個從analyzed改到not_analyzed。

爲了演示兩個指定的映射方法,讓我們首先刪除索引gb:
DELETE /gb

然後創建一個新索引,指定tweet字段的分析器爲english:

PUT /gb <1>
{
  "mappings": {
    "tweet" : {
      "properties" : {
        "tweet" : {
          "type" :    "string",
          "analyzer": "english"
        },
        "date" : {
          "type" :   "date"
        },
        "name" : {
          "type" :   "string"
        },
        "user_id" : {
          "type" :   "long"
        }
      }
    }
  }
}

<1> 這將創建包含mappings的索引,映射在請求體中指定。

再後來,我們決定在tweet的映射中增加一個新的not_analyzed類型的文本字段,叫做tag,使用_mapping後綴:

PUT /gb/_mapping/tweet
{
  "properties" : {
    "tag" : {
      "type" :    "string",
      "index":    "not_analyzed"
    }
  }
}

注意到我們不再需要列出所有的已經存在的字段,因爲我們沒法修改他們。我們的新字段已經被合併至存在的那個映射中。

複合核心字段類型

除了之前提到的簡單的標量類型,JSON還有null值,數組和對象,所有這些Elasticsearch都支持:

多值字段

我們想讓tag字段包含多個字段,這非常有可能發生。我們可以索引一個標籤數組來代替單一字符串:

{ "tag": [ "search", "nosql" ]}

對於數組不需要特殊的映射。任何一個字段可以包含零個、一個或多個值,同樣對於全文字段將被分析併產生多個詞。

言外之意,這意味着數組中所有值必須爲同一類型。你不能把日期和字符竄混合。如果你創建一個新字段,這個字段索引了一個數組,Elasticsearch將使用第一個值的類型來確定這個新字段的類型。

當你從Elasticsearch中取回一個文檔,任何一個數組的順序和你索引它們的順序一致。你取回的_source字段的順序同樣與索引它們的順序相同。

然而,數組是做爲多值字段被索引的,它們沒有順序。在搜索階段你不能指定“第一個值”或者“最後一個值”。倒不如把數組當作一個值集合(bag of values)。

空字段

當然數組可以是空的。這等價於有零個值。事實上,Lucene沒法存放null值,所以一個null值的字段被認爲是空字段。

這四個字段將被識別爲空字段而不被索引:

"empty_string":             "",
"null_value":               null,
"empty_array":              [],
"array_with_null_value":    [ null ]

多層對象

我們需要討論的最後一個自然JSON數據類型是對象(object)——在其它語言中叫做hash、hashmap、dictionary 或者 associative array.

內部對象(inner objects)經常用於在另一個對象中嵌入一個實體或對象。例如,做爲在tweet文檔中user_name和user_id的替代,我們可以這樣寫:

{
    "tweet":            "Elasticsearch is very flexible",
    "user": {
        "id":           "@johnsmith",
        "gender":       "male",
        "age":          26,
        "name": {
            "full":     "John Smith",
            "first":    "John",
            "last":     "Smith"
        }
    }
}

內部對象的映射

Elasticsearch 會動態的檢測新對象的字段,並且映射它們爲 object 類型,將每個字段加到 properties 字段下

{
  "gb": {
    "tweet": { <1>
      "properties": {
        "tweet":            { "type": "string" },
        "user": { <2>
          "type":             "object",
          "properties": {
            "id":           { "type": "string" },
            "gender":       { "type": "string" },
            "age":          { "type": "long"   },
            "name":   { <3>
              "type":         "object",
              "properties": {
                "full":     { "type": "string" },
                "first":    { "type": "string" },
                "last":     { "type": "string" }
              }
            }
          }
        }
      }
    }
  }
}

<1> 根對象.

<2><3> 內部對象.

對user和name字段的映射與tweet類型自己很相似。事實上,type映射只是object映射的一種特殊類型,我們將 object 稱爲根對象。它與其他對象一模一樣,除非它有一些特殊的頂層字段,比如 _source, _all 等等。

內部對象是怎樣被索引的

Lucene 並不瞭解內部對象。 一個 Lucene 文件包含一個鍵-值對應的扁平表單。 爲了讓 Elasticsearch 可以有效的索引內部對象,將文件轉換爲以下格式:

{
    "tweet":            [elasticsearch, flexible, very],
    "user.id":          [@johnsmith],
    "user.gender":      [male],
    "user.age":         [26],
    "user.name.full":   [john, smith],
    "user.name.first":  [john],
    "user.name.last":   [smith]
}

內部欄位可被歸類至name,例如”first”。 爲了區別兩個擁有相同名字的欄位,我們可以使用完整路徑,例如”user.name.first” 或甚至類型名稱加上路徑:”tweet.user.name.first”。

注意: 在以上扁平化文件中,並沒有欄位叫作user也沒有欄位叫作user.name。 Lucene 只索引階層或簡單的值,而不會索引複雜的資料結構。

對象-數組

內部對象數組

最後,一個包含內部對象的數組如何索引。 我們有個數組如下所示:

{
    "followers": [
        { "age": 35, "name": "Mary White"},
        { "age": 26, "name": "Alex Jones"},
        { "age": 19, "name": "Lisa Smith"}
    ]
}

此文件會如我們以上所說的被扁平化,但其結果會像如此:

{
    "followers.age":    [19, 26, 35],
    "followers.name":   [alex, jones, lisa, smith, mary, white]
}

{age: 35}與{name: Mary White}之間的關聯會消失,因每個多值的欄位會變成一個值集合,而非有序的陣列。 這讓我們可以知道:

是否有26歲的追隨者?

但我們無法取得準確的資料如:

是否有26歲的追隨者且名字叫Alex Jones?

關聯內部對象可解決此類問題,我們稱之爲嵌套對象,我們之後會在嵌套對象中提到它。


參考資料:
Elasticsearch權威指南
備註:
轉載請註明出處:http://blog.csdn.net/wsyw126/article/details/71079841
作者:WSYW126

發佈了170 篇原創文章 · 獲贊 50 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章