映射(mapping)機制用於進行字段類型確認,將每一個字段匹配爲一種肯定的數據類型(string
, number
,booleans
, date
等)。算法
分析(analysis)機制用於進行全文文本(Full Text)的分詞,以創建供搜索用的反向索引。sql
當在索引中處理數據時,咱們注意到一些奇怪的事。有些東西彷佛被破壞了:數據庫
在索引中有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 個結果 !
爲何全日期的查詢返回全部的tweets,而針對date
字段進行年度查詢卻什麼都不返回? 爲何咱們的結果因查詢_all
字段(譯者注:默認全部字段中進行查詢)或date
字段而變得不一樣?app
想必是由於咱們的數據在_all
字段的索引方式和在date
字段的索引方式不一樣而致使。dom
讓咱們看看Elasticsearch在對gb
索引中的tweet
類型進行mapping(也稱之爲模式定義[注:此詞有待從新定義(schema definition)])後是如何解讀咱們的文檔結構:nosql
GET /gb/_mapping/tweet
返回:elasticsearch
{ "gb": { "mappings": { "tweet": { "properties": { "date": { "type": "date", "format": "dateOptionalTime" }, "name": { "type": "string" }, "tweet": { "type": "string" }, "user_id": { "type": "long" } } } } } }
Elasticsearch爲對字段類型進行猜想,動態生成了字段和類型的映射關係。返回的信息顯示了date
字段被識別爲date
類型。_all
由於是默認字段因此沒有在此顯示,不過咱們知道它是string
類型。工具
date
類型的字段和string
類型的字段的索引方式是不一樣的,所以致使查詢結果的不一樣,這並不會讓咱們以爲驚訝。測試
你會指望每一種核心數據類型(strings, numbers, booleans及dates)以不一樣的方式進行索引,而這點也是現實:在Elasticsearch中他們是被區別對待的。
可是更大的區別在於確切值(exact values)(好比string
類型)及全文文本(full text)之間。
這二者的區別才真的很重要 - 這是區分搜索引擎和其餘數據庫的根本差別。
Elasticsearch中的數據能夠大體分爲兩種類型:
確切值 及 全文文本。
確切值是肯定的,正如它的名字同樣。好比一個date或用戶ID,也能夠包含更多的字符串好比username或email地址。
確切值"Foo"
和"foo"
就並不相同。確切值2014
和2014-09-15
也不相同。
全文文本,從另外一個角度來講是文本化的數據(經常以人類的語言書寫),好比一片推文(Twitter的文章)或郵件正文。
1
全文文本經常被稱爲非結構化數據
,實際上是一種用詞不當的稱謂,實際上天然語言是高度結構化的。
問題是天然語言的語法規則是如此的複雜,計算機難以正確解析。例如這個句子:
May is fun but June bores me.
究竟是說的月份仍是人呢?
確切值是很容易查詢的,由於結果是二進制的 -- 要麼匹配,要麼不匹配。下面的查詢很容易以SQL表達:
WHERE name = "John Smith" AND user_id = 2 AND date > "2014-09-15"
而對於全文數據的查詢來講,卻有些微妙。咱們不會去詢問這篇文檔是否匹配查詢要求?
。 可是,咱們會詢問這篇文檔和查詢的匹配程度如何?
。換句話說,對於查詢條件,這篇文檔的相關性有多高?
咱們不多確切的匹配整個全文文本。咱們想在全文中查詢包含查詢文本的部分。不只如此,咱們還指望搜索引擎能理解咱們的意圖:
一個針對"UK"
的查詢將返回涉及"United Kingdom"
的文檔
一個針對"jump"
的查詢同時可以匹配"jumped"
, "jumps"
, "jumping"
甚至"leap"
"johnny walker"
也能匹配"Johnnie Walker"
, "johnnie depp"
及"Johnny Depp"
"fox news hunting"
能返回有關hunting on Fox News的故事,而"fox hunting news"
也能返回關於fox hunting的新聞故事。
爲了方便在全文文本字段中進行這些類型的查詢,Elasticsearch首先對文本分析(analyzes),而後使用結果創建一個倒排索引。咱們將在如下兩個章節討論倒排索引及分析過程。
Elasticsearch使用一種叫作倒排索引(inverted index)的結構來作快速的全文搜索。倒排索引由在文檔中出現的惟一的單詞列表,以及對於每一個單詞在文檔中的位置組成。
例如,咱們有兩個文檔,每一個文檔content
字段包含:
爲了建立倒排索引,咱們首先切分每一個文檔的content
字段爲單獨的單詞(咱們把它們叫作詞(terms)或者表徵(tokens))(譯者注:關於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"
和"quick"
被認爲是不一樣的單詞,可是用戶可能認爲它們是相同的。"fox"
和"foxes"
很類似,就像"dog"
和"dogs"
——它們都是同根詞。"jumped"
和"leap"
不是同根詞,但意思類似——它們是同義詞。上面的索引中,搜索"+Quick +fox"
不會匹配任何文檔(記住,前綴+
表示單詞必須匹配到)。只有"Quick"
和"fox"
都在同一文檔中才能夠匹配查詢,可是第一個文檔包含"quick fox"
且第二個文檔包含"Quick foxes"
。(譯者注:這段真囉嗦,說白了就是單複數和同義詞無法匹配)
用戶能夠合理地但願兩個文檔都能匹配查詢,咱們也能夠作得更好。
若是咱們將詞爲統一爲標準格式,這樣就能夠找到不是確切匹配查詢,可是足以類似從而能夠關聯的文檔。例如:
"Quick"
能夠轉爲小寫成爲"quick"
。"foxes"
能夠被轉爲根形式"fox"
。同理"dogs"
能夠被轉爲"dog"
。"jumped"
和"leap"
同義就能夠只索引爲單個詞"jump"
如今的索引:
Term | Doc_1 | Doc_2 |
---|---|---|
brown | X | X |
dog | X | X |
fox | X | X |
in | X | |
jump | X | X |
lazy | X | X |
over | X | X |
quick | X | X |
summer | X | |
the | X | X |
但咱們還未成功。咱們的搜索"+Quick +fox"
依舊失敗,由於"Quick"
的確切值已經不在索引裏,不過,若是咱們使用相同的標準化規則處理查詢字符串的content
字段,查詢將變成"+quick +fox"
,這樣就能夠匹配到兩個文檔。
IMPORTANT
這很重要。你只能夠找到確實存在於索引中的詞,因此索引文本和查詢字符串都要標準化爲相同的形式。
這個標記化和標準化的過程叫作分詞(analysis),這個在下節中咱們討論。
分析(analysis)是這樣一個過程:
這個工做是分析器(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
這些與語義無關的通用詞。這些詞被移除後,由於語法規則的存在,英語單詞的主體含義依舊能被理解(譯者注:stem English words
這句不知道該如何翻譯,查了字典,我理解的大概意思應該是將英語語句比做一株植物,去掉無用的枝葉,主幹依舊存在,停用詞比如枝葉,存在與否並不影響對這句話的理解。)。
2
english
分析器將會產生如下結果:
set, shape, semi, transpar, call, set_tran, 5
注意"transparent"
、"calling"
和"set_trans"
是如何轉爲詞幹的。
當咱們索引(index)一個文檔,全文字段會被分析爲單獨的詞來建立倒排索引。不過,當咱們在全文字段搜索(search)時,咱們要讓查詢字符串通過一樣的分析流程處理,以確保這些詞在索引中存在。
全文查詢咱們將在稍後討論,理解每一個字段是如何定義的,這樣纔可讓它們作正確的事:
如今你能夠明白爲何《映射和分析》的開頭會產生那種結果:
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
結果中每一個節點在表明一個詞:
{ "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
表示詞在原文本中佔據的位置。
analyze
API 對於理解Elasticsearch索引的內在細節是個很是有用的工具,隨着內容的推動,咱們將繼續討論它。
當Elasticsearch在你的文檔中探測到一個新的字符串字段,它將自動設置它爲全文string
字段並用standard
分析器分析。
你不可能老是想要這樣作。也許你想使用一個更適合這個數據的語言分析器。或者,你只想把字符串字段看成一個普通的字段——不作任何分析,只存儲確切值,就像字符串類型的用戶ID或者內部狀態字段或者標籤。
爲了達到這種效果,咱們必須經過映射(mapping)人工設置這些字段。
爲了可以把日期字段處理成日期,把數字字段處理成數字,把字符串字段處理成全文本(Full-text)或精確的字符串值,Elasticsearch須要知道每一個字段裏面都包含了什麼類型。這些類型和字段的信息存儲(包含)在映射(mapping)中。
正如《數據吞吐》一節所說,索引中每一個文檔都有一個類型(type)。 每一個類型擁有本身的映射(mapping)或者模式定義(schema definition)。一個映射定義了字段類型,每一個字段的數據類型,以及字段被Elasticsearch處理的方式。映射還用於設置關聯到類型上的元數據。
在《映射》章節咱們將探討映射的細節。這節咱們只是帶你入門。
Elasticsearch支持如下簡單字段類型:
類型 | 表示的數據類型 |
---|---|
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" |
注意
這意味着,若是你索引一個帶引號的數字——
"123"
,它將被映射爲"string"
類型,而不是"long"
類型。然而,若是字段已經被映射爲"long"
類型,Elasticsearch將嘗試轉換字符串爲long,並在轉換失敗時會拋出異常。
咱們可使用_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" } } } } } }
小提示
錯誤的映射,例如把
age
字段映射爲string
類型而不是integer
類型,會形成查詢結果混亂。要檢查映射類型,而不是假設它是正確的!
雖然大多數狀況下基本數據類型已經可以知足,但你也會常常須要自定義一些特殊類型(fields),特別是字符串字段類型。 自定義類型可使你完成一下幾點:
Feb,12,2016
和 中文的 2016年2月12日
)映射中最重要的字段參數是type
。除了string
類型的字段,你可能不多須要映射其餘的type
:
{ "number_of_clicks": { "type": "integer" } }
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" } } }
注意到咱們再也不須要列出全部的已經存在的字段,由於咱們無法修改他們。咱們的新字段已經被合併至存在的那個映射中。
你能夠經過名字使用analyze
API測試字符串字段的映射。對比這兩個請求的輸出:
GET /gb/_analyze?field=tweet&text=Black-cats <1> GET /gb/_analyze?field=tag&text=Black-cats <2>
<1>
<2>
咱們想要分析的文本被放在請求體中。
tweet
字段產生兩個詞,"black"
和"cat"
,tag
字段產生單獨的一個詞"Black-cats"
。換言之,咱們的映射工做正常。
除了以前提到的簡單的標量類型,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}
之間的關聯會消失,因每一個多值的欄位會變成一個值集合,而非有序的陣列。 這讓咱們能夠知道:
但咱們沒法取得準確的資料如:
關聯內部對象可解決此類問題,咱們稱之爲嵌套對象,咱們之後會在嵌套對象中提到它。