基本概念
定義
- 一個分布式的實時文檔存儲,每個字段 可以被索引與搜索
- 一個分布式實時分析搜索引擎
- 能勝任上百個服務節點的擴展,并支持 PB 級別的結構化或者非結構化數據
用途
- 全文檢索
- 結構化搜索
- 分析
VS傳統數據庫
- 傳統數據庫提供精確匹配
- ES提供精確匹配全文檢索處理同義詞給文檔相關性評分生成分析與聚合數據實時
專有名詞
- 索引(名詞)類似于數據庫
- 索引(動詞)類似于insert。例如索引一個文檔到一個索引
- 倒排索引默認每個屬性都會有一個倒排索引,可以設置屬性不被索引,它只能被覆蓋,不能被修改
- 類型類似表,同一索引的不同類型,可以擁有不同的字段,但應該擁有大部分相似的字段。它可以包含大小寫,不能包含句號,不能以下劃線開頭,長度限制為256.
- Id文檔的id,可以在生成文檔時指定或自動生成,自動生成的ID,在大部分情況下多個節點的時候唯一。如果在創建文檔時,ID沖突,服務器會返回409
- 文檔類似于記錄,文檔只能被替換,而不能被修改,文檔的字段類型需要一致,否則無法進行精確匹配
- 精確值字段對于數字,日期,布爾和一個not_analyzed字段,進行查詢,會適用精確匹配
- 全文搜索字段否則,進行相關性搜索
PUT /megacorp/employee/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
例如
- megacorp是索引
- employee是類型
- 1是文檔id
- json內容是文檔
交互
RESTful API
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
VERB適當的 HTTP 方法 或 謂詞 : GET、 POST、 PUT、 HEAD 或者 DELETE。PROTOCOLhttp 或者 https(如果你在 Elasticsearch 前面有一個 https 代理)HOSTElasticsearch 集群中任意節點的主機名,或者用 localhost 代表本地機器上的節點。PORT運行 Elasticsearch HTTP 服務的端口號,默認是 9200 。PATHAPI 的終端路徑(例如 _count 將返回集群中文檔數量)。Path 可能包含多個組件,例如:_cluster/stats 和 _nodes/stats/jvm 。QUERY_STRING任意可選的查詢字符串參數 (例如 ?pretty 將格式化地輸出 JSON 返回值,使其更容易閱讀)BODY一個 JSON 格式的請求體 (如果請求需要的話)
例子
request:
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
"query": {
"match_all": {}
}
}
'
response:
{
"count" : 0,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
}
}
檢索文檔功能
- 獲取一個文檔GET /megacorp/employee/1
- 簡單查詢GET /megacorp/employee/_search #查詢前十條記錄 GET /megacorp/employee/_search?q=last_name:Smith #查詢smith的前十條記錄
- 表達式查詢GET /megacorp/employee/_search { "query" : { "match" : { "last_name" : "Smith" } } } GET /megacorp/employee/_search { "query" : { "bool": { "must": { "match" : { "last_name" : "smith" } }, "filter": { "range" : { "age" : { "gt" : 30 } } } } } }
- 全文檢索GET /megacorp/employee/_search { "query" : { "match" : { "about" : "rock climbing" } } }
- 短語查詢GET /megacorp/employee/_search { "query" : { "match_phrase" : { "about" : "rock climbing" } } }
- 分析(類似于聚合group by)request:GET /megacorp/employee/_search { "aggs" : { "all_interests" : { "terms" : { "field" : "interests" }, "aggs" : { "avg_age" : { "avg" : { "field" : "age" } } } } } } response: ... "all_interests": { "buckets": [ { "key": "music", "doc_count": 2, "avg_age": { "value": 28.5 } }, { "key": "forestry", "doc_count": 1, "avg_age": { "value": 35 } }, { "key": "sports", "doc_count": 1, "avg_age": { "value": 25 } } ] }
- 修改文檔PUT /website/blog/123 { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" } 舊文檔不會馬上刪掉新文檔會被索引文檔的version會加一
- 刪除文檔DELETE /megacorp/employee/123 文檔的version依然會加一
分布式特性
ES自動執行的分布式動作
- 分配文檔到不同的容器 或 分片 中,文檔可以儲存在一個或多個節點中
- 按集群節點來均衡分配這些分片,從而對索引和搜索過程進行負載均衡
- 復制每個分片以支持數據冗余,從而防止硬件故障導致的數據丟失
- 將集群中任一節點的請求路由到存有相關數據的節點
- 集群擴容時無縫整合新節點,重新分配分片以便從離群節點恢復
水平擴展VS垂直擴展
ES對水平擴展是友好的,通過購置更多的機器,可以更好的使用ES的分布式功能
集群
集群擁有一個或多個節點,當有節點加入或者退出集群時,集群會重新平均分配所有數據的分布
主節點功能
- 增加/刪除索引
- 增加/刪除節點不涉及文檔的變更和搜索,因此單一的主節點不會成為集群的性能瓶頸
索引分片的元數據在每個ES節點都有存儲,每個節點在接到請求后,都知道到哪臺ES node找到數據,通過轉發請求到ES node所在的機器
一個分片的最大文檔數:(2^31-128)
一個索引的主分片數在建立時被確定,且無法修改:因為文檔的存儲是用shard = hash(routing) % number_of_primary_shards來確定文檔的位置的。routing默認是id,也可以自定義
分片的副本數可以隨時修改
建立索引
PUT /blogs
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
故障轉移
增加一臺機器到集群
刪除節點
分布式寫入沖突
對于多個client的寫入ES,有可能造成寫入沖突,導致數據的丟失
在一些場景下,數據丟失是可以接受的
但是在某些場景下,是不允許的。
悲觀控制并發
傳統數據庫的控制方式。通過對記錄加鎖,來實現并發的串行執行
樂觀并發控制
ES采用樂觀控制
所謂樂觀控制,就是服務器假設大部分情況下,是不會發生沖突的,如果發生沖突,則拒絕修改,客戶端可以需要通過重新獲取并重試進行處理。
過程如下圖
分布式文檔存儲
確定文檔位于那個shard
shard = hash(routing) % number_of_primary_shards
API支持帶routing參數,來自定義路由,來確保相關文檔路由到同一個分片
以ID新建,寫入和刪除文檔
一致性保證
- none: 主分片活躍,允許寫入
- all: 在所有分片活躍,允許寫入
- quorum: 半數以上節點活躍,允許寫入
如果暫時沒有足夠的分片活躍,ES會等待,默認等待1分鐘,可以通過參數timeout改變這個值,如果超時,則失敗返回
新索引默認有 1 個副本分片,這意味著為滿足 規定數量 應該 需要兩個活動的分片副本。 但是,這些默認的設置會阻止我們在單一節點上做任何事情。為了避免這個問題,要求只有當 number_of_replicas 大于1的時候,規定數量才會執行。
以ID檢索文檔
與上圖類似
以ID更新文檔
與上圖類似,但在更新完文檔后,會重建索引
在局部更新文檔的時候,主分片會以整份文檔來同步給副本,來保證數據的完整性
通過條件獲取多個文檔
搜索
返回特殊字段
GET /_search
{
"hits" : {
"total" : 14,
"hits" : [
{
"_index": "us",
"_type": "tweet",
"_id": "7",
"_score": 1,
"_source": {
"date": "2014-09-17",
"name": "John Smith",
"tweet": "The Query DSL is really powerful and flexible",
"user_id": 2
}
},
... 9 RESULTS REMOVED ...
],
"max_score" : 1
},
"took" : 4,
"_shards" : {
"failed" : 0,
"successful" : 10,
"total" : 10
},
"timed_out" : false
}
- took執行的毫秒數
- _shards查詢分片的狀態,例如有幾個分片是失敗的,幾個是成功的
- timeout可以通過在查詢設定超時,如果查詢超過時間,則只返回已經成功獲得的數據,剩余的數據將丟棄
- _index數據來源的Lucene索引,文檔的每一個字段,都擁有一個不同的Lucene索引在查詢中可以指定Lucene的索引,默認是不指定的,所以會查詢該文檔的所有索引,并匯總結果。如果指定,則會限定僅在指定的Lucene索引中查詢數據
分頁
GET /_search?size=5&from=5
此方式只適用于淺分頁,如果查詢過深,會導致嚴重的性能問題。
因為例如查詢size為5,from=10000。那么ES會從各分片中都查詢10005條記錄,如果有100個shard,那么就會有100*10005條記錄,ES再對這100*10005排序,并僅返回5條記錄
深分頁
使用游標scroll
它在ES中建立了一個有有效期的快照,提供給scroll進行數據的深度查詢
倒排索引
對一下文檔進行倒排:
- The quick brown fox jumped over the lazy dog
- Quick brown foxes leap over lazy dogs in summer
得到:
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跟quick,用戶有可能認為它們是相同的,也有可能認為是不同的
- dog和dogs非常接近,在相關性搜索時,它們應該都被搜索到
- jump和leap是同義詞,在相關性搜索時,它們應該都被搜索到
理解相關性
相關性的分數是一個模糊的概念。沒有精確值,沒有唯一正確的答案。是一種根據各種規則對文檔進行的一種量化的估計。
它評分的準則如下:
- 檢索詞頻率檢索詞在該字段出現的頻率越高,分數越高
- 反向文檔頻率檢索詞在索引出現的頻率越高,分數越低
- 字段長度準則字段的長度越長,分數越低
相關性破壞
在使用全文檢索某個關鍵字的時候,會出現,相關度低的文檔的得分高于相關度高的文檔的得分。
例如檢索詞milk。索引內有兩個主分片,milk在P1出現了5次,在P2出現了6次。由于P1和P2的詞分布不一樣。
P1的詞量比P2的詞量高,那么milk算在P1出現占比小,導致在P1得相關性得分高,而在P2,占比da,導致在P2的相關性得分低。
原因
是因為局部數據分布不均勻導致的
解決方法
- 插入更多的文檔
- 使用?search_type=dfs_query_then_fetch進行全局評分。但會有嚴重的性能問題。不推薦使用。
查詢過濾bitset
每次使用檢索詞查詢,都會為檢索詞建立一個bitset,bitset包含了匹配的文檔的序號。在熱搜索的檢索詞,ES會對這些bitset有針對的進行緩存,而不用在再次查詢的時候,重新查找倒排索引。
對于多個查詢可以有下圖
當倒排索引重建的時候,bitset在緩存會自動失效
緩存的策略
- 最近256次被使用的bitset,會被緩存
- 段內記錄小于1w的,不會被緩存
索引管理
創建索引
可以顯式創建,也可以隱式創建。
在大集群下,索引的創建,涉及元數據的同步,有可能導致集群負載的大量增加。此時需要禁用索引的隱式創建
action.auto_create_index: false
刪除索引
刪除索引,會涉及大量數據的刪除,如果用戶意外地試圖通過一條命令,把所有索引刪掉,這可能導致可怕的后果
通過禁用此操作,可以設置如下
action.destructive_requires_name: true
分析器
每個索引都可以設置自己的分析器,分析器的用途主要是在全文索引上面,通過對不同的語言,使用不同的分詞,不同的詞轉換來構造倒排索引和計算相關性。
分片
倒排索引的不變性
好處
- 一旦被讀入系統緩存,就會一直留在那里,直到LRU算法把不常用的倒排索引剔除。這對ES的讀取性能提供了非常大的提升
不好
- 新的文檔加入,不能增量更新,只能重建索引并替換
如何保證新數據能實時能查詢到
用更多的索引。
對于新的文檔,不馬上重建索引,而是通過新增額外的索引。在查詢數據時,通過輪詢所有的索引,并合并結果返回。
ES并不是嚴格意義上的實時,準確來說是準實時,由于data從插入到建立倒排索引這段時間,新數據是不能訪問的
聚合
像數據庫的group by。只是語法不一樣。功能相通
應用層性能調優
調大 refresh interval
默認刷新時間是1s,每次刷新都會有一次磁盤寫入,并創建一個新的段。通過設置更大的刷新時間,可以讓磁盤寫入的次數更低,寫入的段更大。減少段合并的次數。
禁止OS把ES置換出去
OS的內核會在內存緊張的時候,把進程置換到外村。而對于性能跟內存強相關的ES來說,置換到外存是致命的。通過設置進程在內核的參數,禁止置換,可以避免OS的這種動作
預留大量的文件系統緩存給ES
由于ES大部分數據的不變性,使得ES的大部分磁盤操作,都可以通過文件系統的緩存來加快速度。一旦ES的倒排索引和數據緩存到系統,如果沒有其他進程的干擾,而且是比較頻繁訪問的數據,則會一直駐留在系統緩存,使得ES的大部分操作都是走內存的。一般來說,分配一半的內存給文件系統,是合適的。
使用自動生成ID
如果指定ID,ES會在集群內檢查是否ID已經存在,這對大集群來說,是昂貴的。如果ID是自動生成的,ES會跳過檢查,直接插入文檔
更好的硬件
- 更大的內存
- SSD
- 本地磁盤
不要使用join關聯查詢
ES不適合做關聯查詢,會導致嚴重的性能問題。
如果業務一定要join,可以把關聯的數據都寫到一個索引內,或者通過應用程序來做關聯的動作。
強制merge只讀索引
merge成一個單一的段,會得到更好的性能
增加副本
有更多的機器,通過提高副本數,可以提高讀效率
不要返回大數據
ES不適合這場景
避免稀疏
不要把不相關的信息存入同一個索引
數據預熱
對于熱點數據,可以通過一個客戶端請求ES,讓數據先占據filesystem cache。
冷熱數據分離
冷熱數據部署在不同的機器,可以讓熱數據在緩存內不會被冷數據沖走
內核層性能調優
限流
如果ES出現高負載的請求,ES的協調節點會累積大量的請求在內存在等待處理,隨著請求數的增加,協調節點的內存占用會越來越大,最后導致OOM。
通過限流,可以有效緩解。
大查詢
如果客戶端發來了一個復雜的查詢,使得需要返回的數據異常的大,這也會導致OOM問題。
通過修改內核,讓如果請求的內存占用超過系統可以承受的范圍,則截斷來解決
FST過大引發OOM
FST是對倒排索引在內存的索引,它通過前綴狀態機的方法,快速的定位檢索詞在倒排索引的磁盤位置,達到減少磁盤訪問次數而加快檢索速度的目的。
但由于FST是常駐內存的,如果倒排索引達到一定規模時,FST必然會引起OOM問題。而且FST是存放在JVM堆內內存的。堆內內存的上限時32G。
而10 TB的數據就需要10G到15G的內存來存放FST。
- 通過把FST的存儲放到堆外內存
- 通過LRU算法來管理FST,對不常用的FST置換出內存
- 修改ES訪問FST的邏輯,使得ES可以從堆內直接訪問堆外的FST
- 在堆內增加FST的cache,加快命中速度
作者:譚英智
來源:https://www.cnblogs.com/kukafeiso/p/13947142.html