ElasticSearch權威指南學習(對映和分析)
阿新 • • 發佈:2018-11-15
概念
- 對映(mapping)機制用於進行欄位型別確認,將每個欄位匹配為一種確定的資料型別(string, number, booleans, date等)。+
- 分析(analysis)機制用於進行全文文字(Full Text)的分詞,以建立供搜尋用的反向索引。
資料型別差異
- 在索引中有12個tweets,只有一個包含日期2014-09-15,但是我們看看下面查詢中的total hits。
GET /_search?q=2014 # 12 個結果 GET /_search?q=2014-09-15 # 還是 12 個結果 ! GET /_search?q=date:2014-09-15 # 1 一個結果 GET /_search?q=date:2014 # 0 個結果 !
- 解讀我們的文件結構
GET /gb/_mapping/tweet { "gb": { "mappings": { "tweet": { "properties": { "date": { "type": "date", "format": "dateOptionalTime" }, "name": { "type": "string" }, "tweet": { "type": "string" }, "user_id": { "type": "long" } } } } } }
- Elasticsearch為對欄位型別進行猜測,動態生成了欄位和型別的對映關係。返回的資訊顯示了date欄位被識別為date型別。
- date型別的欄位和string型別的欄位的索引方式是不同的,因此導致查詢結果的不同
確切值(Exact values) vs. 全文文字(Full text)
- Elasticsearch中的資料可以大致分為兩種型別:確切值 及 全文文字。
- 確切值是確定的。確切值"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),計算匹配單詞的數目,這樣我們就可以說第一個文件比第二個匹配度更高——對於我們的查詢具有更多相關性。
- 但是這樣我們仍舊查不到像Quick,Dog這樣的詞
- 不過,如果我們使用相同的標準化規則處理查詢字串的content欄位,查詢將變成"+quick +fox",這樣就可以匹配到兩個文件。
這個標記化和標準化的過程叫做分析(analysis)
分析和分析器
- 分析(analysis)是這樣一個過程:
- 首先,標記化一個文字塊為適用於倒排索引單獨的詞(term)
- 然後標準化這些詞為標準形式,提高它們的“可搜尋性”或“查全率”
- 字元過濾器
- 首先字串經過字元過濾器(character filter),它們的工作是在標記化前處理字串。字元過濾器能夠去除HTML標記,或者轉換"&"為"and"。
- 分詞器
- 下一步,分詞器(tokenizer)被標記化成獨立的詞。一個簡單的分詞器(tokenizer)可以根據空格或逗號將單詞分開
- 標記過濾
- 最後,每個詞都通過所有標記過濾(token filters),它可以修改詞(例如將"Quick"轉為小寫),去掉詞(例如停用詞像"a"、"and"、"the"等等),或者增加詞(例如同義詞像"jump"和"leap")
- 內建的分析器
- 下面我們列出了最重要的幾個分析器,來演示這個字串分詞後的表現差異
"Set the shape to semi-transparent by calling set_trans(5)"
- 標準分析器
- 它根據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分析器將會產生以下結果:
set, shape, semi, transpar, call, set_tran, 5
- 測試分析器
為了更好的理解如何進行,你可以使用analyze API來檢視文字是如何被分析的。在查詢字串引數中指定要使用的分析器,被分析的文字做為請求體:
GET /_analyze?analyzer=standard&text=Text to analyze
- 結果中每個節點在代表一個詞:
{ "tokens": [ { "token": "text", "start_offset": 0, "end_offset": 4, "type": "<ALPHANUM>", "position": 1 }, { "token": "to", "start_offset": 5, "end_offset": 7, "type": "<ALPHANUM>", "position": 2 }, { "token": "analyze", "start_offset": 8, "end_offset": 15, "type": "<ALPHANUM>", "position": 3 } ] }
- token是一個實際被儲存在索引中的詞。position指明詞在原文字中是第幾個出現的。start_offset和end_offset表示詞在原文字中佔據的位置。
對映
- 為了能夠把日期欄位處理成日期,把數字欄位處理成數字,把字串欄位處理成全文字(Full-text)或精確的字串值,Elasticsearch需要知道每個欄位裡面都包含了什麼型別。這些型別和欄位的資訊儲存(包含)在對映(mapping)中。
- 核心簡單欄位型別
型別 | 表示的資料型別 |
---|---|
String | string |
Whole number | byte, short, integer, long |
Floating point | float, double |
Boolean | boolean |
Date | date |
- 當你索引一個包含新欄位的文件——一個之前沒有的欄位——Elasticsearch將使用動態對映猜測欄位型別,這型別來自於JSON的基本資料型別,使用以下規則:
JSON type | Field type |
---|---|
Boolean: true or false | "boolean" |
Whole number: 123 | "long" |
Floating point: 123.45 | "double" |
String, valid date: "2014-09-15" | "date" |
String: "foo bar" | "string" |
- 檢視對映
- 我們可以使用_mapping字尾來檢視Elasticsearch中的對映。在本章開始我們已經找到索引gb型別tweet中的對映:
GET /gb/_mapping/tweet
- 欄位的對映(叫做屬性(properties)),這些對映是Elasticsearch在建立索引時動態生成的:
{ "gb": { "mappings": { "tweet": { "properties": { "date": { "type": "date", "format": "strict_date_optional_time||epoch_millis" }, "name": { "type": "string" }, "tweet": { "type": "string" }, "user_id": { "type": "long" } } } } } }
- ps:錯誤的對映,例如把age欄位對映為string型別而不是integer型別,會造成查詢結果混亂。
要檢查對映型別,而不是假設它是正確的!
- 自定義欄位對映
- 對映中最重要的欄位引數是type
{ "number_of_clicks": { "type": "integer" } }
- index
- index引數控制字串以何種方式被索引。它包含以下三個值當中的一個
值 解釋 analyzed 首先分析這個字串,然後索引。換言之,以全文形式索引此欄位。 not_analyzed 索引這個欄位,使之可以被搜尋,但是索引內容和指定值一樣。不分析此欄位。 no 不索引這個欄位。這個欄位不能為搜尋到。 - 如果我們想對映欄位為確切值,我們需要設定它為not_analyzed:
{ "tag": { "type": "string", "index": "not_analyzed" } }
- 分析
- 對於analyzed型別的字串欄位,使用analyzer引數來指定哪一種分析器將在搜尋和索引的時候使用。預設的,Elasticsearch使用standard分析器,但是你可以通過指定一個內建的分析器來更改它,例如whitespace、simple或english。
{ "tweet": { "type": "text", "analyzer": "english" } }
- 更新對映
- 你可以在第一次建立索引的時候指定對映的型別。此外,你也可以晚些時候為新型別新增對映
- ps:你可以向已有對映中增加欄位,但你不能修改它。如果一個欄位在對映中已經存在,這可能意味著那個欄位的資料已經被索引。如果你改變了欄位對映,那已經被索引的資料將錯誤並且不能被正確的搜尋到。
- 建立一個新索引,指定tweet欄位的分析器為english:
PUT /gb { "mappings": { "tweet" : { "properties" : { "tweet" : { "type" : "text", "analyzer": "english" }, "date" : { "type" : "date" }, "name" : { "type" : "text" }, "user_id" : { "type" : "long" } } } } }
- 再後來,我們決定在tweet的對映中增加一個新的not_analyzed型別的文字欄位,叫做tag,使用_mapping字尾:
PUT /gb/_mapping/tweet { "properties" : { "tag" : { "type" : "string", "index": "not_analyzed" } } }
版本問題
- 如果你發現報錯
#! Deprecation: Expected a boolean [true/false] for property [index] but got [not_analyzed]
- 原因:該版本以後index這個只能用true或者false了,如果想要不被分詞就把資料型別設定為keyword,只能說優化了,使用更方便,更易理解了
複合核心欄位型別
- 多值欄位
- 我們可以索引一個標籤陣列來代替單一字串:
- { "tag": [ "search", "nosql" ]}
- 對於陣列不需要特殊的對映。任何一個欄位可以包含零個、一個或多個值,同樣對於全文欄位將被分析併產生多個詞。
- 言外之意,這意味著陣列中所有值必須為同一型別。你不能把日期和字元竄混合。如果你建立一個新欄位,這個欄位索引了一個數組,Elasticsearch將使用第一個值的型別來確定這個新欄位的型別。
- 空欄位
- 陣列可以是空的。這等價於有零個值。事實上,Lucene沒法存放null值,所以一個null值的欄位被認為是空欄位。
- 這四個欄位將被識別為空欄位而不被索引:
"empty_string": "", "null_value": null, "empty_array": [], "array_with_null_value": [ null ]
- 多層物件
- 內部物件(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": { //根物件 "properties": { "tweet": { "type": "string" }, "user": { //內部物件 "type": "object", "properties": { "id": { "type": "string" }, "gender": { "type": "string" }, "age": { "type": "long" }, "name": { //內部物件 "type": "object", "properties": { "full": { "type": "string" }, "first": { "type": "string" }, "last": { "type": "string" } } } } } } } } }
- 內部物件是怎樣被索引的
- 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] }
- 物件-陣列
- 內部物件陣列
{ "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}之間的關聯會消失,因每個多值的欄位會變成一個值集合,而非有序的陣列。