ElasticSearch 2 (8) - 概覽與簡介
摘要
-
分布式集群架構,具有高擴充性,可隨時增加或移除節點,並保證數據正確。
-
使用Apache Lucene儲存JSON文件,提供全文搜索功能
-
所有操作均可透過RESTful API完成
-
跨平台,Java寫成
版本
elasticsearch版本: elasticsearch-2.2.0
內容
為了搜索,你懂的
有誰在使用?
還有誰在用?
用來做什么?
-
記錄
-
搜尋
-
分析
與關系數據庫有什么不一樣?
索引、類型?
關系數據庫 => 數據庫 => 表 => 行記錄 => 列
ElasticSearch => 索引 => 類型 => 文件 => 字段
如何使用
如何存入?
PUT /megacorp/employee/1
{
“first_name": "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I am hero",
"interests": [ "sports", "music" ]
}
- 創建megacorp的索引 Index
- 在里面創建一個employee的類型 Type
- 在里面建立一個_id是1的JSON文件 Document
如何讀取?
GET /megacorp/employee/1
或
GET /megacorp/employee/_search?q=music
或
GET /megacorp/_search?q=hero
如何回傳?
{
……
"took": 4,
"hits": {
"total": 1,
"hits": [
{
"_index": "megacorp",
"_type": "employee",
"_id": "1",
"_score": 0.095891505,
"_source": {
"first_name": "John",
……
}
}
]
}
}
集群里的日子
-
Cluster由一個或以上具有相同cluster.name的ElasticSearch節點所組成。當有節點加入或移除時,cluster會自動平均分配數據。
-
Cluster中會自動選出一個主節點負責cluster的變動,例如新增節點或創建新的Index。
-
主節點可以不參與文件操作或搜索,因此只有一個主節點不會導致瓶頸。
-
我們可以發送請求到任一節點,它會清楚在cluster中該如何處理,並回傳給我們最終結果。
Cluster的健康狀態
GET /_cluster/health
-
GREEN 所有的主要(master)與復制(replica)的shard都是啟動的。
-
YELLOW 所有的主要shard都是啟動的,但復制的沒有。
-
RED 所有的主要與復制的shard都沒有啟動。
創建索引
PUT /blogs
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
創建一個名為blogs的Index,並設定它有3個主shard每個主shard總共要有一個副本shard在其他機器。
一個節點的時候
此時cluster健康狀態為黃色,因為沒有分配副本shard到其他機器。此時ElasticSearch可以正常運作,但是數據若遭到硬件問題時無法復原。
加入第二、第三個節點
此時cluster健康狀態為綠色,因所有shard都啟動了。此時其中一個節點遇到硬件問題都不會有影響。
增加復制的shard
PUT /blogs/_settings
{
"number_of_replicas" : 2
}
如此一來,壞掉兩個節點也不影響。
數據如何分配到shard?
shard = hash(routing) % number_of_primary_shards
當有文件要儲存進入Index時,ElasticSearch經過上面的計算后決定要把該文件存儲到哪一個shard。
routing為任意字符串,預設為文件上的_id,可被改為其他的值。透過改變routing可以決定文件要儲存到哪個shard。
當新增或刪除文件的時候
- Node 1 收到新增或刪除請求。
- Node 1 算出請求的文件是屬於Shard 0,因此將請求轉給Node 3。
- Node 3 完成請求時,會再將請求轉給復制的shard所在的Node 1與Node 2,並確定他們也都完成,此請求才算是成功。
當取得指定文件的時候
- Node 1 收到獲取請求。
- Node 1 算出請求的文件是在Shard 0,而三台機器都有Shard 0,以上圖為例,它會將請求轉給Node 2。
- Node 2 將文件回傳給Node 1,再回傳給使用者。
問題:當多個Node上都具有相同shard時,主Node如何轉發請求?
當更新文件的時候
- Node 1 收到更新的請求。
- Node 1 算出請求的文件屬於Shard 0,因此將請求轉給Node 3。
- Node 3 將文件取出后更新_source並嘗試重新索引。此步驟可能重復retry_on_conflict次數。
- Node 3 完成請求時,會再將新的文件傳給復制的shard所在的Node 1與Node 2,並確定他們也都完成,此請求才算是成功。
問題:此種情況Node 1是否會優先將請求轉給主Shard所在Node?
問題:如果超過重復次數,系統行為如何?
分布式文件存儲
如何制作索引?
假如有12條date是2014-xx-xx的文件。但只有一個文件的date是2014-09-15。那我們發送以下請求:
GET /_search?q=2014
# 12 results
GET /_search?q=2014-09-15
# 12 results !
GET /_search?q=date:2014-09-15
# 1 result
GET /_search?q=date:2014
# 0 results !
結果為什么這么奇怪?
映射與分析
跨字段搜索
當存儲文件時,ElasticSearch預設會另外存儲一個_all字段。該字段預設由所有字段串接而成,並使用inverted index制作索引提供全文搜索。例如:
{
"tweet": "However did I manage before Elasticsearch?",
"date": "2014-09-14",
"name": "Mary Jones",
"user_id": 1
}
該文件的_all如下:
"However did I manage before Elasticsearch? 2014-09-14 Mary Jones 1"
映射
當有文件存儲進來時,ElasticSearch預設會為該type自動生成mapping,用來決定如何制作索引以提供搜索。
{
"gb": {
"mappings": {
"tweet": {
"properties": {
"date": {
"type": "date",
"format": "dateOptionalTime"
},
"name": {
"type": "string"
},
"tweet": {
"type": "string"
},
"user_id": {
"type": "long"
}
}
}
}
}
}
exact value 與full text
-
ElasticSearch把值分成兩類:exact value 與full text。
-
當針對exact value的字段搜索時,使用布爾判斷,例如:Foo != foo。
-
當針對full text的字段搜索時,則是計算相關程度,例如:UK與United Kingdom相關、jumping與leap也相關。
Inverted Index
ElasticSearch用inverted index建立索引,提供全文搜索。考慮以下兩份文件:
The quick brown fox jumped over the lazy dog
與
Quick brown foxes leap over lazy dogs in summer
-
建立出來的inverted index看起來大概如下表。
--------------------------------------- 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 ---------------------------------------
-
此表還可以優化,例如
- Quick可以變成quick
- foxes,dogs可以變成fox與dog
- jumped,leap可以變成jump
這種分詞(tokenization)、正規化(normalization)過程叫做analysis
-
優化結果如下
--------------------------------------- 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 ---------------------------------------
Analysis與Analyzers
Analysis程序由Analyzer完成,Analyzer由下面三個功能組成:
-
Character filters
首先,字符串先依次經過character filters處理過,再進行分詞。例如可能將html標簽移除,或將 & 轉換為 and。
-
Tokenizer
分詞器就是將字符串切為許多有意義的單詞。
-
Token filters
每個單詞再依序經過token filters做最后處理。例如可能將Quick變成quick、把leap換成jump。
中文分詞
-
ik分詞器或mmseg分詞器
-
Lucene Smart Chinese Analysis
回答之前的問題
假如有12條date是2014-xx-xx的文件。但只有一個文件的date是2014-09-15。那我們發送以下請求:
-
用2014去全文搜索_all字段
GET /_search?q=2014
# 12 results
-
2014-09-05經過分析后變成使用2014,09,15去全文搜索_all字段,由於每份文件都有2014所以全部相關。
GET /_search?q=2014-09-15
# 12 results !
-
針對date字段搜索exact value
GET /_search?q=date:2014-09-15
# 1 result
-
針對date字段搜索exact value,沒有文件的date是2014
GET /_search?q=date:2014# 0 results !
數據是分布式存儲的
分布式搜索
-
ElasticSearch將搜索分成兩個階段,來完成在分布式系統中的搜索與排序:query與fetch。
-
排序的其中一個目的是為了分頁,考慮一下請求:
GET /_search { "from": 90, "size": 10 }
query階段
- Node 3 收到搜索請求后制作一個大小為from + size = 100的priority queue來排序。
- Node 3 將搜索請求轉給其他每個shard,此例為0號與1號。每個shard將自己搜索,並用priority queue排序出前from + size = 100個結果。
- 每個shard將各自結果的IDs與排序值回傳給Node 3,Node 3再將這些結果加入priority queue中。
問題:為什么每個Node都是from + size = 100個結果?
fetch階段
- Node 3 將排序完的IDs取出需要的部分,即最后10筆,再發送Multi-GET請求跟文件所在的shard取得完整的文件。
- shard 各自收到請求后,取出文件,若需要的話再經過處理,例如加上metadata與片段高亮,再回傳給Node 3。
- Node 3 取得所有結果后再回傳給客戶端。
在分布式系統中的排序與分頁
-
ElasticSearch搜索結果預設只會回傳10個經過_score排序的文件。
-
我們可以透過size與from參數,取得其他分頁的結果。例如
GET /_search?size=10&from=10000
-
但是在分布式系統中分頁的成本非常高。預設一個Index有5個shard,若要取出第1000頁的內容,必須從每個shard取出前10010個文件。再將總共50050個結果重新排序取出10個(10001 ~ 100010)。因此若要取出大量數據,不建議使用排序與分頁的功能。
scan與scroll
GET /old_index/_search?search_type=scan&scroll=1m
{
"query": { "match_all": {}},
"size": 1000
}
-
設定 search_type = scan,這樣一來ElasticSearch不會對結果進行排序。
-
設定scroll = 1m,將會對這個搜索建立快照,並維持一分鍾。根據回傳的_scroll_id可以取得下一批的結果。經由size參數可以設定一個shard一批最多取多少文件,因此每一批取得的數量最多為
size * number_of_primary_shards
-
透過scan與scroll,我們可以批次取得大量的文件,而且不會有分頁成本。若有需要重新索引整個index時,可以使用此方法完成。
最佳化Index
Index 設定
雖然ElasticSearch在存入文件的時候就會自動創建Index,但使用預設設定可能不是個好主意。例如:
-
主要的shard數量不能夠被修改
-
自動mapping可能會猜錯
-
使用不符需求的Analyzer
這些設定都需要在存儲數據之前設定完成,否則將會需要重新索引整個index。
除了手動設定index之外,也可以使用index template自動套用設定。
mapping的部分則可以設定dynamic_templates自動套用。
別名零宕機Zero downtime
若想要改變已經既有字段的索引方式,例如改變Analyzer。將需要重新索引整個index,否則既有的數據與新索引的不一致。
利用Index別名,可以做到Zero downtime的重新索引。
-
Application請求的index叫做my_index。實際上my_index是個別名,指到的是my_index_v1。
-
若要重新索引,則創建新的my_index_v2,並套用新的設定。再透過scan與scroll將文件從my_index_v1放入my_index_v2。
-
最后將my_index別名導向my_index_v2即可。
Shard內部
- shard是一個低階的工作單元,事實上是一個Lucene實例,負責文件的存儲與搜索。
- inverted index由shard創建,並寫入磁盤,而且建立好的inverted index不會被更改。
不會被修改的inverted index
inverted index的不變性帶來許多好處,如:
-
當多個processes來讀取時,不需要鎖定。
-
一旦被讀入系統的cache,將會一直保留在cache中,如此一來便不用再訪問磁盤。
更新inverted index
Lucene 帶來了per-segment search的概念,segment就是一個inverted index。既有的segment不被改變,因此當有新文件存入的時候,就建立新的segment;若更新或刪除文件時,則另外將舊文件標記.del檔案。而搜索時,就依序對各個segment搜索。
合並segments
然而隨着時間經過,segment數量越來越多,搜索成本也越來越大。ElasticSearch會定期將segment合並,同時一除掉那些被標記為刪除的文件。
問題:如何定期?
參考
參考來源:
《ElasticSearch: The Definitive Guide》
SlideShare: What is in a Lucene index?