1. 程式人生 > >Elasticsearch原理解析與效能調優

Elasticsearch原理解析與效能調優

## 基本概念 ### 定義 - 一個分散式的實時文件儲存,*每個欄位* 可以被索引與搜尋 - 一個分散式實時分析搜尋引擎 - 能勝任上百個服務節點的擴充套件,並支援 PB 級別的結構化或者非結構化資料 ### 用途 - 全文檢索 - 結構化搜尋 - 分析 ### VS傳統資料庫 - 傳統資料庫 - 提供精確匹配 - ES - 提供精確匹配 - 全文檢索 - 處理同義詞 - 給文件相關性評分 - 生成分析與聚合資料 - 實時 ### 專有名詞 - 索引(名詞) 類似於資料庫 - 索引(動詞) 類似於insert。例如索引一個文件到一個索引 - 倒排索引 預設每個屬性都會有一個倒排索引,可以設定屬性不被索引,它只能被覆蓋,不能被修改 - 型別 類似表,同一索引的不同型別,可以擁有不同的欄位,但應該擁有大部分相似的欄位。它可以包含大小寫,不能包含句號,不能以下劃線開頭,長度限制為256. - Id 文件的id,可以在生成文件時指定或自動生成,自動生成的ID,在大部分情況下多個節點的時候唯一。如果在建立文件時,ID衝突,伺服器會返回409 - 文件 類似於記錄,文件只能被替換,而不能被修改,文件的欄位型別需要一致,否則無法進行精確匹配 - 精確值欄位 對於數字,日期,布林和一個not_analyzed欄位,進行查詢,會適用精確匹配 - 全文搜尋欄位 否則,進行相關性搜尋 ```shell 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 ```shell curl -X '://:/?' -d '' ``` | `VERB` | 適當的 HTTP *方法* 或 *謂詞* : `GET`、 `POST`、 `PUT`、 `HEAD` 或者 `DELETE`。 | | -------------- | ------------------------------------------------------------ | | `PROTOCOL` | `http` 或者 `https`(如果你在 Elasticsearch 前面有一個 `https` 代理) | | `HOST` | Elasticsearch 叢集中任意節點的主機名,或者用 `localhost` 代表本地機器上的節點。 | | `PORT` | 執行 Elasticsearch HTTP 服務的埠號,預設是 `9200` 。 | | `PATH` | API 的終端路徑(例如 `_count` 將返回叢集中文件數量)。Path 可能包含多個元件,例如:`_cluster/stats` 和 `_nodes/stats/jvm` 。 | | `QUERY_STRING` | 任意可選的查詢字串引數 (例如 `?pretty` 將格式化地輸出 JSON 返回值,使其更容易閱讀) | | `BODY` | 一個 JSON 格式的請求體 (如果請求需要的話) | #### 例子 request: ```shell curl -XGET 'http://localhost:9200/_count?pretty' -d ' { "query": { "match_all": {} } } ' ``` response: ```shell { "count" : 0, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 } } ``` ### 檢索文件功能 - 獲取一個文件 ```shell GET /megacorp/employee/1 ``` - 簡單查詢 ```shell GET /megacorp/employee/_search #查詢前十條記錄 ``` ```shell GET /megacorp/employee/_search?q=last_name:Smith #查詢smith的前十條記錄 ``` - 表示式查詢 ```shell GET /megacorp/employee/_search { "query" : { "match" : { "last_name" : "Smith" } } } ``` ```shell GET /megacorp/employee/_search { "query" : { "bool": { "must": { "match" : { "last_name" : "smith" } }, "filter": { "range" : { "age" : { "gt" : 30 } } } } } } ``` - 全文檢索 ```shell GET /megacorp/employee/_search { "query" : { "match" : { "about" : "rock climbing" } } } ``` - 短語查詢 ```shell GET /megacorp/employee/_search { "query" : { "match_phrase" : { "about" : "rock climbing" } } } ``` - 分析(類似於聚合group by) request: ```shell GET /megacorp/employee/_search { "aggs" : { "all_interests" : { "terms" : { "field" : "interests" }, "aggs" : { "avg_age" : { "avg" : { "field" : "age" } } } } } } ``` response: ```shell ... "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 } } ] } ``` - 修改文件 ```shell 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,也可以自定義 > > 分片的副本數可以隨時修改 #### 建立索引 ```shell PUT /blogs { "settings" : { "number_of_shards" : 3, "number_of_replicas" : 1 } } ``` ![ES-cluster](http://upload-images.jianshu.io/upload_images/24077087-db0af4d6f7d05f3c.PNG) #### 故障轉移 ##### 增加一臺機器到叢集 ![ES-addonenode](http://upload-images.jianshu.io/upload_images/24077087-86f5fd13b9486a64.PNG) ##### 刪除節點 ![ES-delonenode](http://upload-images.jianshu.io/upload_images/24077087-b94f5f7404434653.PNG) #### 分散式寫入衝突 ![ES-datalose](http://upload-images.jianshu.io/upload_images/24077087-de70030d5aabecf2.PNG) 對於多個client的寫入ES,有可能造成寫入衝突,導致資料的丟失 在一些場景下,資料丟失是可以接受的 但是在某些場景下,是不允許的。 ##### 悲觀控制併發 傳統資料庫的控制方式。通過對記錄加鎖,來實現併發的序列執行 ##### 樂觀併發控制 ES採用樂觀控制 所謂樂觀控制,就是伺服器假設大部分情況下,是不會發生衝突的,如果發生衝突,則拒絕修改,客戶端可以需要通過重新獲取並重試進行處理。 過程如下圖 ![ES-versionConflict](http://upload-images.jianshu.io/upload_images/24077087-80b48eff19aca62e.PNG) #### 分散式文件儲存 ##### 確定文件位於那個shard ``` shard = hash(routing) % number_of_primary_shards ``` > API支援帶routing引數,來自定義路由,來確保相關文件路由到同一個分片 ##### 以ID新建,寫入和刪除文件 ![ES-writedocbyid](http://upload-images.jianshu.io/upload_images/24077087-6149cdabc7fca3f5.PNG) ###### 一致性保證 - none: 主分片活躍,允許寫入 - all: 在所有分片活躍,允許寫入 - quorum: 半數以上節點活躍,允許寫入 > 如果暫時沒有足夠的分片活躍,ES會等待,預設等待1分鐘,可以通過引數timeout改變這個值,如果超時,則失敗返回 > > 新索引預設有 `1` 個副本分片,這意味著為滿足 `規定數量` *應該* 需要兩個活動的分片副本。 但是,這些預設的設定會阻止我們在單一節點上做任何事情。為了避免這個問題,要求只有當 `number_of_replicas` 大於1的時候,規定數量才會執行。 ##### 以ID檢索文件 與上圖類似 ##### 以ID更新文件 與上圖類似,但在更新完文件後,會重建索引 > 在區域性更新文件的時候,主分片會以整份文件來同步給副本,來保證資料的完整性 ##### 通過條件獲取多個文件 ![ES-mget](http://upload-images.jianshu.io/upload_images/24077087-66f17cc9cd81d201.PNG) ## 搜尋 ### 返回特殊欄位 ``` GET /_search ``` ```shell { "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進行資料的深度查詢 ### 倒排索引 對一下文件進行倒排: 1. The quick brown fox jumped over the lazy dog 2. 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有針對的進行快取,而不用在再次查詢的時候,重新查詢倒排索引。 對於多個查詢可以有下圖 ![ES-bitset](http://upload-images.jianshu.io/upload_images/24077087-38e3b4fb045bccff.PNG) > 當倒排索引重建的時候,bitset在快取會自動失效 #### 快取的策略 - 最近256次被使用的bitset,會被快取 - 段內記錄小於1w的,不會被快取 ## 索引管理 ### 建立索引 可以顯式建立,也可以隱式建立。 在大叢集下,索引的建立,涉及元資料的同步,有可能導致叢集負載的大量增加。此時需要禁用索引的隱式建立 ``` action.auto_create_index: false ``` ### 刪除索引 刪除索引,會涉及大量資料的刪除,如果使用者意外地試圖通過一條命令,把所有索引刪掉,這可能導致可怕的後果 通過禁用此操作,可以設定如下 ``` action.destructive_requires_name: true ``` ### 分析器 每個索引都可以設定自己的分析器,分析器的用途主要是在全文索引上面,通過對不同的語言,使用不同的分詞,不同的詞轉換來構造倒排索引和計算相關性。 ## 分片 ### 倒排索引的不變性 #### 好處 - 一旦被讀入系統快取,就會一直留在那裡,直到LRU演算法把不常用的倒排索引剔除。這對ES的讀取效能提供了非常大的提升 #### 不好 - 新的文件加入,不能增量更新,只能重建索引並替換 ### 如何保證新資料能實時能查詢到 用更多的索引。 對於新的文件,不馬上重建索引,而是通過新增額外的索引。在查詢資料時,通過輪詢所有的索引,併合並結果返回。 ![ES-shar](http://upload-images.jianshu.io/upload_images/24077087-0068a03f06a5cbb6.PNG) > 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,加快命中速度 Ref: - https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html - https://zhuanlan.zhihu.com/p/99718374 - https://blog.csdn.net/wangnan9279/article/details/7