作者 | 蔡柱梁
審校 | 重樓
目錄
- ES是什么
- 倒排索引
- 使用ES必須知道的基本概念
- 了解常用的DSL
1 ES是什么
Elasticsearch 是一個分布式的 RESTful 搜索和分析引擎,可用來集中存儲您的數(shù)據(jù),以便您對形形色色、規(guī)模不一的數(shù)據(jù)進(jìn)行搜索、索引和分析。
上面是??官網(wǎng)-API文檔??對的定位描述。ES 是一個分布式的搜索引擎,數(shù)據(jù)存儲形式與我們常用的 MySQL 的存儲形式 — rows 不同,ES 會將數(shù)據(jù)以 JSON 結(jié)構(gòu)存儲到一個文檔。一個文檔寫入 ES 后,我們可以在 1 秒左右查詢到它,因此我們稱 ES 在分布式中數(shù)據(jù)查詢是準(zhǔn)實時的。
提問:那么這種將一行行數(shù)據(jù)變成
我們傳統(tǒng)的關(guān)系型數(shù)據(jù)庫一般的存儲形式是數(shù)據(jù)結(jié)構(gòu)不固定,長度不固定。這時如果用關(guān)系型數(shù)據(jù)庫做存儲,那么我們表設(shè)計上,只能用一個
為了可以適應(yīng)高并發(fā),又能快速檢索、分析數(shù)據(jù)的搜索分析引擎,像倒排索引實現(xiàn)可以通過詞條快速查找文檔的,而倒排索引的實現(xiàn)與這種文檔存儲數(shù)據(jù)的方式密不可分。
ES 的適用場景所具有的特點:
- 海量數(shù)據(jù)的搜索服務(wù)
- 對實時性要求較高
- 對事務(wù)要求不高
2 倒排索引
倒排索引是文檔檢索系統(tǒng)中最常用的數(shù)據(jù)結(jié)構(gòu)。
說到幫助搜索引擎檢索數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),我們最熟悉的應(yīng)該就是倒排索引了。過去很多人喜歡用字典來舉例,因為它的原理和我們使用中文字典查找漢字是相似的。
ES 會在我們保存一份文檔的時候,將文檔根據(jù)指定分詞器進(jìn)行分詞,然后維護(hù)關(guān)鍵詞和文檔的關(guān)系——倒排索引。后面我們通過一些詞條進(jìn)行檢索的時候,就可以通過這個索引找到對應(yīng)相關(guān)的文檔。
2.1 例子
下面舉個例子。
插入兩份文檔,內(nèi)容如下:
- we like java java java
- we like lucene lucene lucene
建立倒排索引大體流程如下:
- 首先對所有數(shù)據(jù)的內(nèi)容進(jìn)行拆分,拆分成唯一的一個個詞語(詞條)
- 然后建立詞條和對應(yīng)文檔的對應(yīng)關(guān)系,具體如下:
詞條? | (文檔ID,頻率)? | 詞條在文檔中的位置? |
we | (1,1) (2,1) | (0) (0) |
like | (1,1) (2,1) | (1) (1) |
java | (1,3) | (2,3,4) |
lucene | (2,3) | (2,3,4) |
注意:這里用表格來展示是為了方便理解,但是倒排索引其實是樹結(jié)構(gòu)。
那這時我檢索詞條:
3 使用ES必須知道的基本概念
這里的概念是我們在使用過程中絕對無法繞開的概念,所以我們需要知道,否則無法和同事交流,哪怕僅僅是使用級別。
3.1 document(文檔)
在 ES 中,一份文檔相當(dāng)于 MySQL 中的一行記錄,數(shù)據(jù)以 JSON 格式保存。文檔被更新時,版本號會被增加。
3.2 Index(索引)
存儲文檔的地方,類似 MySQL 中的表。
3.3 Mapping(映射)
映射是定義一個文件和它所包含的字段如何被存儲和索引的過程(??這是官方定義??)。
文檔里面有許多字段,這些字段有自己的類型,采用什么分詞器等等,我們可以通過。
3.4 type(類型)
這是比較老舊版本會用到的定義,在 ES5 的時代,它可以對 Index 做更精細(xì)地劃分,那個時代的 Index 更像 MySQL 的實例,而 type 類似 MySQL 的 table。
ES 5.x 中一個index可以有多種type。
ES 6.x 中一個index只能有一種type。
ES 7.x 以后,將逐步移除type這個概念,現(xiàn)在的操作已經(jīng)不再使用,默認(rèn)_doc。
4 了解常用的DSL
在 MySQL 中,我們經(jīng)常使用 SQL 通過客戶端操作 MySQL,而 DSL 正是我們通過客戶端發(fā)送給 ES 的操作指令。
下面只寫一些現(xiàn)在我們常常接觸的簡單的 DSL,更多的請看 官網(wǎng)。
4.1 Index
官網(wǎng)API:??https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html??
4.1.1 創(chuàng)建索引
可以先建索引,再設(shè)置 mapping,也可以直接一次完成。
一次建好
4.1.2 查詢 index 信息
GET index_name
4.1.3 刪除 index
DELETE index_name
4.1.4 關(guān)閉 index
POST index_name/_close
當(dāng)索引進(jìn)入關(guān)閉狀態(tài),是不能操作文檔的。
4.1.5 打開 index
POST index_name/_open
4.1.6 Aliases(別名) & Reindex
實際工作中,有很多情況可能都會需要重建 index,同時將舊的數(shù)據(jù)遷移到新 index 上,并且期望這個過程可以零停機(jī),那么這時我們就可以用到 aliases 和 reindex 了。
事實上,我們程序訪問 index,很少是訪問真正的 indexName,一般我們會對 index 建別名,程序訪問的是別名。因為如果使用別名,那么此別名背后的索引需要進(jìn)行更換的時候?qū)Τ绦蚩梢宰龅綗o感知。
下面是一個需要添加分詞器而導(dǎo)致需要重建 index 和數(shù)據(jù)遷移的場景(這里只是舉個簡單場景,方便感受這些命令如何使用而已)。
1)先建立了一個 person,具體如下:
2)后端程序訪問是用別名
3)添加了一些數(shù)據(jù)
4)添加分詞器,更改 mapping 設(shè)置
5)別名操作(支持多個操作,并具有原子性)
這時我們后端程序只能對 person_index 進(jìn)行讀操作,無法進(jìn)行寫操作。
6)將 person 中的數(shù)據(jù)導(dǎo)入到 person2 中(如果是不同進(jìn)程,支持遠(yuǎn)程訪問)
7)去掉 person
這時后端程序?qū)?person_index 的讀寫操作均恢復(fù)正常。
更多信息可以查閱官網(wǎng):??reindex??? ??aliases??
4.2 設(shè)置 Mapping
添加 index。
已經(jīng)建好索引 person,但是沒有設(shè)置 mapping,現(xiàn)在設(shè)置。
index 確定后,不能修改已有字段,只能添加,以下增加一個 test字段作為例子。
查詢 mapping 信息
4.3 使用頻率較高的查詢
這里只寫一些比較常接觸的語句,不過像 wildcard 這種,也有很多公司是禁止使用的,所以用的時候一定要了解公司規(guī)范要求。
先設(shè)置一個商品 index,具體如下:
字段說明:
- title:商品標(biāo)題
- price:商品價格
- num:商品庫存
- category:商品類別
- brand:品牌名稱
4.3.1 分頁與排序
4.3.1.1 深度翻頁
ES 深度分頁存在的問題:
- 性能問題
- 深度分頁會導(dǎo)致搜索引擎遍歷大量的數(shù)據(jù),因此會對性能產(chǎn)生負(fù)面影響。尤其是在數(shù)據(jù)量龐大的情況下,可能會導(dǎo)致搜索請求變得非常慢。
- 排序問題
- 這是由于不同分片上的數(shù)據(jù)排序不一致所導(dǎo)致的(每個分片需要將自己的處理結(jié)果給到協(xié)調(diào)節(jié)點,再由協(xié)調(diào)節(jié)點來計算出最后的結(jié)果)。
- 索引更新問題
- 如果在進(jìn)行深度分頁時,索引被更新了,那么可能會導(dǎo)致部分?jǐn)?shù)據(jù)被遺漏或重復(fù)顯示(為了避免這個問題,可以使用游標(biāo)或滾動搜索等機(jī)制來遍歷數(shù)據(jù))。
- 內(nèi)存問題
- 在進(jìn)行深度分頁時,Elasticsearch 需要將所有的搜索結(jié)果都存儲在內(nèi)存中。如果結(jié)果集非常大,那么會占用大量的內(nèi)存,甚至可能導(dǎo)致內(nèi)存溢出(為了避免這個問題,可以使用游標(biāo)或滾動搜索等機(jī)制來逐步處理數(shù)據(jù))。
在 Elasticsearch 7.0 之前,我們是采用 scroll 來解決深度分頁的,但是到了 Elasticsearch 7.0 就開始不再推薦采用 scroll 了,推薦采用 search_after。
4.3.1.1.1 scroll
詳細(xì)請看??官方文檔??。
以下例子來自于官網(wǎng)
1)先查詢并生成快照
scroll=1m 是保留1分鐘快照的意思,即是符合當(dāng)前查詢條件的數(shù)據(jù)的結(jié)果集合保留快照1分鐘
假設(shè)返回的 scroll_id 是 DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==
2)那么,我們就可以使用這個 ID 進(jìn)行滾動翻頁了
3)查詢完后,記得刪除游標(biāo)
這里詳細(xì)說下游標(biāo)的工作方式:
當(dāng)?shù)谝淮伟l(fā)起 scroll 請求時,ES 會創(chuàng)建一個包含搜索結(jié)果的快照,并返回一個唯一的滾動 ID。在接下來的每個 scroll 請求中,都需要帶上這個滾動 ID,表示要獲取與該搜索上下文匹配的下一批結(jié)果。因為每個 scroll 請求都使用了相同的搜索上下文,所以每個請求返回的結(jié)果都是相同的,只是可能包含不同的文檔。如果 scroll 請求返回的結(jié)果集合大小不足以填滿請求的大小限制,則 ES 會在后臺繼續(xù)搜索,并將結(jié)果添加到當(dāng)前結(jié)果集中,直到結(jié)果集合大小達(dá)到請求的大小限制或搜索完成為止。
由于 scroll 機(jī)制的實現(xiàn)方式,每次請求返回的結(jié)果可以是任意大小,可以避免一次性讀取所有結(jié)果可能導(dǎo)致的內(nèi)存問題。同時,由于滾動 ID 只在指定的時間段內(nèi)有效,所以可以在不消耗過多內(nèi)存的情況下,分批次處理大量數(shù)據(jù)。但是,需要注意的是,如果時間段設(shè)置得過短,可能會導(dǎo)致滾動 ID 過期,需要重新發(fā)起搜索請求。
4.3.1.1.2 search_after
詳細(xì)請看??官網(wǎng)??。
以下例子來自于官網(wǎng)
1)先查詢
假設(shè)響應(yīng)如下:
2)接著,使用上面響應(yīng)結(jié)果中最后一個文檔的排序鍵
作為參數(shù)傳遞到下一次查詢中(這里其實就是對應(yīng)了查詢示例中的兩個排序字段 date 和 tie_breaker_id)
這里有一個問題,如果我在第2頁準(zhǔn)備翻到第3頁時,refresh 了可能會打亂排序,那么這個分頁的結(jié)果就不對了。為了避免這種情況,我們可以使用 PIT 來保存當(dāng)前搜索的索引狀態(tài)。
具體使用如下:
1)先得到 PIT ID
響應(yīng)如下:
2)使用 PIT ID 搜索
響應(yīng)如下:
3)pit id + 排序鍵 翻頁
4)查詢完后,刪除 PIT
scroll 和 search after 都是用來處理大數(shù)據(jù)時避免深度翻頁的,它們區(qū)別如下:
- 實現(xiàn)方式不同
- scroll 使用游標(biāo)來保持搜索上下文,而 search after 使用排序鍵來跟蹤搜索進(jìn)度。
- 參數(shù)設(shè)置不同
- scroll 需要指定一個時間段來保持搜索上下文,而 search after 需要指定一個排序字段和一個起始排序鍵來開始搜索。
- 數(shù)據(jù)處理方式不同
- scroll 適用于一次性處理所有數(shù)據(jù)的場景,每次請求返回的結(jié)果可以是任意大小,直到搜索上下文過期或搜索完成為止。而。
- 排序方式不同
- scroll 可能會導(dǎo)致排序不穩(wěn)定的問題,而 search after 使用排序鍵來跟蹤搜索進(jìn)度,可以避免這個問題。
- 兼容性不同
- scroll 是 Elasticsearch 5.x 及之前版本的遺留功能,而 search after 是 Elasticsearch 7.0 中引入的新特性,Elasticsearch 7.0 開始推薦使用 search after。
4.3.2 match
想對搜索關(guān)鍵字進(jìn)行分詞,搜索的結(jié)果更全面。
特點
- 會對查詢條件進(jìn)行分詞
- 然后將分詞后的查詢條件和詞條進(jìn)行等值匹配
- 默認(rèn)取并集
4.3.3 term
不想對搜索關(guān)鍵字進(jìn)行分詞,搜索的結(jié)果更加精確。
4.3.4 range
當(dāng)想對數(shù)值類型的字段做區(qū)間的搜索,例如商品價格。
4.3.5 wildcard
當(dāng)使用match搜索仍然查詢不到數(shù)據(jù),可以嘗試使用模糊查詢,范圍更廣。
運(yùn)行結(jié)果:
可以發(fā)現(xiàn)查詢的結(jié)果中,那些title包含“華為”的數(shù)據(jù)查不出來,因為那些數(shù)據(jù),沒有分出"華"這一個字,而分出的就是"華為",這個時候我們?nèi)粝氚寻?華為"的數(shù)據(jù)都查出來,就可以使用模糊查詢。
4.3.6 query_string
當(dāng)不知道搜索的內(nèi)容存儲在哪個字段時,可以使用字符串搜索。
特點
- 會對查詢條件進(jìn)行分詞
- 將分詞后的查詢條件和詞條進(jìn)行等值匹配
- 默認(rèn)取并集(OR)
- 可以指定多個查詢字段
1)不指定字段
2)指定字段
運(yùn)行結(jié)果:
4.3.7 bool?
當(dāng)存在多個查詢條件時
語法
must(and):條件必須成立。
must_not(not):條件必須不成立,必須和must或filter連接起來使用。
should(or):條件可以成立。
filter:條件必須成立,性能比must高(不會計算得分)。
運(yùn)行結(jié)果:
4.3.8 Aggregations?
聚合查詢
聚合類型:
- 指標(biāo)聚合:相當(dāng)于MySQL的聚合函數(shù)。比如max、min、avg、sum等。
- 桶聚合:相當(dāng)于MySQL的 group by 操作。(不要對text類型的數(shù)據(jù)進(jìn)行分組,會失?。?/li>
4.3.8.1 指標(biāo)聚合
運(yùn)行結(jié)果:
4.3.8.2 桶聚合
運(yùn)行結(jié)果:
4.3.9 highlight(高亮查詢)
運(yùn)行結(jié)果:
5 總結(jié)
這篇文章的宗旨是希望可以幫助剛接觸ES 的人可以快速了解ES,和掌握ES 的一些常用查詢。