只列出了感覺重要的部分,完整內容請查看這里。
適用于Elasticsearch 2.x版本
添加故障轉移
當集群中只有一個節點在運行時,意味著會有一個單點故障問題——沒有冗余。 幸運的是,我們只需再啟動一個節點即可防止數據丟失。
當你在同一臺機器上啟動了第二個節點時,只要它和第一個節點有同樣的 cluster.name
配置,它就會自動發現集群并加入到其中。 但是在不同機器上啟動節點的時候,為了加入到同一集群,你需要配置一個可連接到的單播主機列表。
當第二個節點加入到集群后,3個副本分片將會分配到這個節點上——每個主分片對應一個副本分片。 這意味著當集群內任何一個節點出現問題時,我們的數據都完好無損。
所有新近被索引的文檔都將會保存在主分片上,然后被并行的復制到對應的副本分片上。這就保證了我們既可以從主分片又可以從副本分片上獲得文檔。
搜索——最基本的工具
文檔中的每個字段都將被索引并且可以被查詢 。
搜索(search)可以做到:
- 在類似于 gender 或者 age 這樣的字段上使用結構化查詢,join_date 這樣的字段上使用排序,就像SQL的結構化查詢一樣。
- 全文檢索,找出所有匹配關鍵字的文檔并按照相關性(relevance)排序后返回結果。
- 以上二者兼而有之。
很多搜索都是開箱即用的,為了充分挖掘Elasticsearch的潛力,你需要理解以下三個概念:
映射(Mapping)
- 描述數據在每個字段內如何存儲
分析(Analysis)
- 全文是如何處理使之可以被搜索的
領域特定查詢語言(Query DSL)
- Elasticsearch中強大靈活的查詢語言
timeout
timed_out
值告訴我們查詢是否超時。默認情況下,搜索請求不會超時。 如果低響應時間比完成結果更重要,你可以指定 timeout 為 10 或者 10ms(10毫秒),或者 1s(1秒):
GET /_search?timeout=10ms
在請求超時之前,Elasticsearch 將會返回已經成功從每個分片獲取的結果。
應當注意的是 timeout 不是停止執行查詢,它僅僅是告知正在協調的節點返回到目前為止收集的結果并且關閉連接。在后臺,其他的分片可能仍在執行查詢即使是結果已經被發送了。
使用超時是因為SLA(服務等級協議)對你是很重要的,而不是因為想去中止長時間運行的查詢。
多索引,多類型
如果不對某一特殊的索引或者類型做限制,就會搜索集群中的所有文檔。Elasticsearch 轉發搜索請求到每一個主分片或者副本分片,匯集查詢出的前10個結果,并且返回給我們。
/_search
在所有的索引中搜索所有的類型
/gb/_search
在 gb 索引中搜索所有的類型
/gb,us/_search
在 gb 和 us 索引中搜索所有的文檔
/g*,u*/_search
在任何以 g 或者 u 開頭的索引中搜索所有的類型
分頁
和 SQL 使用 LIMIT
關鍵字返回單個 page 結果的方法相同,Elasticsearch 接受 from
和 size
參數:
size
- 顯示應該返回的結果數量,默認是 10
from
- 顯示應該跳過的初始結果數量,默認是 0
如果每頁展示 5 條結果,可以用下面方式請求得到 1 到 3 頁的結果:
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
考慮到分頁過深以及一次請求太多結果的情況,結果集在返回之前先進行排序。 但請記住一個請求經常跨越多個分片,每個分片都產生自己的排序結果,這些結果需要進行集中排序以保證整體順序是正確的。
在分布式系統中深度分頁
理解為什么深度分頁是有問題的,我們可以假設在一個有 5 個主分片的索引中搜索。 當我們請求結果的第一頁(結果從 1 到 10 ),每一個分片產生前 10 的結果,并且返回給協調節點 ,協調節點對 50 個結果排序得到全部結果的前 10 個。
現在假設我們請求第 1000 頁--結果從 10001 到 10010 。所有都以相同的方式工作除了每個分片不得不產生前10010個結果以外。然后協調節點對全部 50050 個結果排序最后丟棄掉這些結果中的 50040 個結果。
可以看到,在分布式系統中,對結果排序的成本隨分頁的深度成指數上升。這就是 web 搜索引擎對任何查詢都不要返回超過 1000 個結果的原因。
“輕量”搜索
有兩種形式的 搜索 API:一種是 “輕量的” 查詢字符串 版本,要求在查詢字符串中傳遞所有的 參數,另一種是更完整的請求體版本,要求使用 JSON 格式和更豐富的查詢表達式作為搜索語言。
查詢字符串搜索非常適用于通過命令行做即席查詢。例如,查詢在 tweet 類型中 tweet 字段包含 elasticsearch 單詞的所有文檔:
GET /_all/tweet/_search?q=tweet:elasticsearch
但是查詢字符串參數所需要的百分比編碼(譯者注:URL編碼)實際上更加難懂:
GET /_search?q=%2Bname%3Ajohn+%2Btweet%3Amary
查詢字符串搜索允許任何用戶在索引的任意字段上執行可能較慢且重量級的查詢,這可能會暴露隱私信息,甚至將集群拖垮。因為這些原因,不推薦直接向用戶暴露查詢字符串搜索功能,除非對于集群和數據來說非常信任他們。
相反,我們經常在生產環境中更多地使用功能全面的 request body 查詢API,除了能完成以上所有功能,還有一些附加功能。
映射和分析
讓我們看一看 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"
}
}
}
}
}
}
基于對字段類型的猜測, Elasticsearch 動態為我們產生了一個映射。這個響應告訴我們 date
字段被認為是 date
類型的。由于 _all
是默認字段,所以沒有提及它。但是我們知道 _all
字段是 string
類型的。
所以 date
字段和 string
字段 索引方式不同,因此搜索結果也不一樣。這完全不令人吃驚。你可能會認為核心數據類型 strings、numbers、Booleans 和 dates 的索引方式有稍許不同。沒錯,他們確實稍有不同。
但是,到目前為止,最大的差異在于 代表 精確值 (它包括 string
字段)的字段和代表 全文 的字段。這個區別非常重要——它將搜索引擎和所有其他數據庫區別開來。
在Elasticsearch 5.X版本中有變化。
精確值 VS 全文
Elasticsearch 中的數據可以概括的分為兩類:精確值和全文。
精確值如它們聽起來那樣精確。例如日期或者用戶 ID,但字符串也可以表示精確值,例如用戶名或郵箱地址。對于精確值來講,Foo 和 foo 是不同的,2014 和 2014-09-15 也是不同的。
另一方面,全文是指文本數據(通常以人類容易識別的語言書寫),例如一個推文的內容或一封郵件的內容。
精確值很容易查詢。結果是二進制的:要么匹配查詢,要么不匹配。這種查詢很容易用 SQL 表示:
WHERE name = "John Smith"
AND user_id = 2
AND date > "2014-09-15"
查詢全文數據要微妙的多。我們問的不只是“這個文檔匹配查詢嗎”,而是“該文檔匹配查詢的程度有多大?”換句話說,該文檔與給定查詢的相關性如何?
我們很少對全文類型的域做精確匹配。相反,我們希望在文本類型的域中搜索。不僅如此,我們還希望搜索能夠理解我們的意圖 :
- 搜索 UK ,會返回包含 United Kindom 的文檔。
- 搜索 jump ,會匹配 jumped , jumps , jumping ,甚至是 leap 。
- 搜索 johnny walker 會匹配 Johnnie Walker , johnnie depp 應該匹配 Johnny Depp 。
- fox news hunting 應該返回福克斯新聞( Foxs News )中關于狩獵的故事,同時, fox hunting news 應該返回關于獵狐的故事。
為了促進這類在全文域中的查詢,Elasticsearch首先分析文檔,之后根據結果創建 倒排索引 。
倒排索引
Elasticsearch 使用一種稱為倒排索引 的結構,它適用于快速的全文搜索。一個倒排索引由文檔中所有不重復詞的列表構成,對于其中每個詞,有一個包含它的文檔列表。
例如,假設我們有兩個文檔,每個文檔的 content
域包含如下內容:
- The quick brown fox jumped over the lazy dog
- Quick brown foxes leap over lazy dogs in summer
為了創建倒排索引,我們首先將每個文檔的 content
域拆分成單獨的詞(我們稱它為詞條或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
兩個文檔都匹配,但是第一個文檔比第二個匹配度更高。如果我們使用僅計算匹配詞條數量的簡單 相似性算法,那么我們可以說,對于我們查詢的相關性來講,第一個文檔比第二個文檔更佳。
分析與分析器
分析包含下面的過程:
- 首先,將一塊文本分成適合于倒排索引的獨立的詞條
- 之后,將這些詞條統一化為標準格式以提高它們的“可搜索性”,或者
recall
分析器執行上面的工作。 分析器實際上是將三個功能封裝到了一個包里:
字符過濾器
- 首先,字符串按順序通過每個字符過濾器 。他們的任務是在分詞前整理字符串。一個字符過濾器可以用來去掉HTML,或者將
&
轉化成and
。
分詞器
- 其次,字符串被分詞器分為單個的詞條。一個簡單的分詞器遇到空格和標點的時候,可能會將文本拆分成詞條。
Token 過濾器
- 最后,詞條按順序通過每個 token 過濾器 。這個過程可能會改變詞條(例如,小寫化 Quick ),刪除詞條(例如, 像
a
,and
,the
等無用詞),或者增加詞條(例如,像 jump 和 leap 這種同義詞)。
Elasticsearch提供了開箱即用的字符過濾器、分詞器和token 過濾器。 這些可以組合起來形成自定義的分析器以用于不同的目的。
內置分析器
但是, Elasticsearch還附帶了可以直接使用的預包裝的分析器。 接下來我們會列出最重要的分析器。為了證明它們的差異,我們看看每個分析器會從下面的字符串得到哪些詞條:
"Set the shape to semi-transparent by calling set_trans(5)"
1、標準分析器
標準分析器是Elasticsearch默認使用的分析器。它是分析各種語言文本最常用的選擇。它根據 Unicode 聯盟定義的單詞邊界劃分文本。刪除絕大部分標點。最后,將詞條小寫。它會產生:
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
2、簡單分析器
簡單分析器在任何不是字母的地方分隔文本,將詞條小寫。它會產生
set, the, shape, to, semi, transparent, by, calling, set, trans
3、空格分析器
空格分析器在空格的地方劃分文本。它會產生
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
4、語言分析器
特定語言分析器可用于很多語言。它們可以考慮指定語言的特點。例如,英語 分析器附帶了一組英語無用詞(常用單詞,例如 and 或者 the ,它們對相關性沒有多少影響),它們會被刪除。 由于理解英語語法的規則,這個分詞器可以提取英語單詞的詞干。
英語 分詞器會產生下面的詞條:
set, shape, semi, transpar, call, set_tran, 5
注意看 transparent
、 calling
和 set_trans
已經變為詞根格式。
什么時候使用分析器
當我們 索引 一個文檔,它的全文域被分析成詞條以用來創建倒排索引。 但是,當我們在全文域搜索的時候,我們需要將查詢字符串通過相同的分析過程 ,以保證我們搜索的詞條格式與索引中的詞條格式一致。
全文查詢理解每個域是如何定義的,因此它們可以做正確的事:
- 當你查詢一個 全文 域時,會對查詢字符串應用相同的分析器,以產生正確的搜索詞條列表。
- 當你查詢一個 精確值 域時,不會分析查詢字符串,而是搜索你指定的精確值。
現在你可以理解在開始章節的查詢為什么返回那樣的結果:
date
域包含一個精確值:單獨的詞條 2014-09-15
。
_all
域是一個全文域,所以分詞進程將日期轉化為三個詞條: 2014
, 09
, 和 15
。
當我們在 _all
域查詢 2014
,它匹配所有的12條推文,因為它們都含有 2014
。
映射
為了能夠將時間域視為時間,數字域視為數字,字符串域視為全文或精確值字符串, Elasticsearch 需要知道每個域中數據的類型。這個信息包含在映射中。
Elasticsearch 支持 如下簡單域類型:
- 字符串:
string
- 整數 :
byte
,short
,integer
,long
- 浮點數:
float
,double
- 布爾型:
boolean
- 日期:
date
當你索引一個包含新域的文檔--之前未曾出現-- Elasticsearch 會使用 動態映射 ,通過JSON中基本數據類型,嘗試猜測域類型,使用如下規則:
JSON類型 | 域類型 |
---|---|
布爾型: true 或者 false
|
boolean |
整數: 123 | long |
浮點數: 123.45 | double |
字符串,有效日期: 2014-09-15 | date |
字符串: foo bar | string |
注意:這些類型在Elasticsearch 5.X版本中有變化,以后會更新。
這意味著如果你通過引號( "123" )索引一個數字,它會被映射為 string
類型,而不是 long
。但是,如果這個域已經映射為 long
,那么 Elasticsearch 會嘗試將這個字符串轉化為 long
,如果無法轉化,則拋出一個異常。
查看映射
通過 /_mapping
,我們可以查看 Elasticsearch 在一個或多個索引中的一個或多個類型的映射 。取得索引 gb 中類型 tweet 的映射:
GET /gb/_mapping/tweet
返回結果:
{
"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
,會導致查詢出現令人困惑的結果。
自定義域映射
自定義映射允許你執行下面的操作:
- 全文字符串域和精確值字符串域的區別
- 使用特定語言分析器
- 優化域以適應部分匹配
- 指定自定義數據格式
- 還有更多
域最重要的屬性是 type
。對于不是 string
的域,你一般只需要設置 type
:
{
"number_of_clicks": {
"type": "integer"
}
}
默認, string
類型域會被認為包含全文。也就是說它們的值在索引前會通過 一個分析器,針對于這個域的查詢在搜索前也會經過一個分析器。
string
域映射的兩個最重要的屬性是 index
和 analyzer
。
index
index
屬性控制怎樣索引字符串。它可以是下面三個值:
analyzed(默認)
- 首先分析字符串,然后索引它。換句話說,以全文索引這個域。
not_analyzed
- 索引這個域,所以可以搜索到它,但索引指定的精確值。不對它進行分析。
no
- 不索引這個域。這個域不會被搜索到。
如果我們想映射string
類型域為一個精確值,我們需要設置它的index
屬性為 not_analyzed
:
{
"tag": {
"type": "string",
"index": "not_analyzed"
}
}
其他簡單類型(例如 long
, double
, date
等)也接受 index
參數,但有意義的值只有 no
和 not_analyzed
, 因為它們永遠不會被分析。
analyzer
對于index
被設置為analyzed
的字符串域,用analyzer
屬性指定在搜索和索引時使用的分析器。默認, Elasticsearch 使用 standard
分析器, 但你可以指定一個內置的分析器替代它,例如 whitespace
、 simple
和 english
:
{
"tweet": {
"type": "string",
"analyzer": "english"
}
}
更新映射
當你首次創建一個索引的時候,可以指定類型的映射。你也可以使用 /_mapping
為新類型增加映射。
盡管你可以增加一個映射,你不能修改 存在的域映射。如果一個域的映射已經存在,那么該域的數據可能已經被索引。如果你意圖修改這個域的映射,索引的數據可能會出錯,不能被正常的搜索。
我們決定在 tweet 映射增加一個新的名為 tag 的 not_analyzed
的文本域,使用 _mapping
:
PUT /gb/_mapping/tweet
{
"properties" : {
"tag" : {
"type" : "string",
"index": "not_analyzed"
}
}
}
注意,我們不需要再次列出所有已存在的域,因為無論如何我們都無法改變它們。新域已經被合并到存在的映射中。
復雜類型
除了我們提到的簡單標量數據類型, JSON 還有 null
值,數組,和對象,這些 Elasticsearch 都是支持的。
數組
很有可能,我們希望 tag 域包含多個標簽。我們可以以數組的形式索引標簽:
{ "tag": [ "search", "nosql" ]}
對于數組,沒有特殊的映射需求。任何域都可以包含0、1或者多個值。
這暗示數組中所有的值必須是相同數據類型的 。你不能將日期和字符串混在一起。如果你通過索引數組來創建新的域,Elasticsearch 會用數組中第一個值的數據類型作為這個域的類型。
未完,待續...