簡單操作可以直接使用ElasticsearchRespositor接口,復雜的使用ElasticsearchTemplate
查詢
?1.單字符串全文查詢
SearchQuery searchQuery=newNativeSearchQueryBuilder().withQuery(queryStringQuery(word)).withPageable(pageable).build();
2.某字段按字符串模糊查詢
查詢某個字段中模糊包含目標字符串,使用matchQuery
SearchQuery searchQuery=newNativeSearchQueryBuilder().withQuery(matchQuery("content",content)).withPageable(pageable).build();
3.PhraseMatch查詢,短語匹配
和match查詢類似,match_phrase查詢首先解析查詢字符串來產生一個詞條列表。然后會搜索所有的詞條,但只保留包含了所有搜索詞條的文檔,并且詞條的位置要鄰接。一個針對短語“中華共和國”的查詢不會匹配“中華人民共和國”,因為沒有含有鄰接在一起的“中華”和“共和國”詞條。
這種完全匹配比較嚴格,類似于數據庫里的“%落日熔金%”這種,使用場景比較狹窄。如果我們希望能不那么嚴格,譬如搜索“中華共和國”,希望帶“我愛中華人民共和國”的也能出來,就是分詞后,中間能間隔幾個位置的也能查出來,可以使用slop參數。
SearchQuery searchQuery=newNativeSearchQueryBuilder().withQuery(matchPhraseQuery("content",content)).withPageable(pageable).build();
4.multi_match多個字段匹配某字符串
如果我們希望title,content兩個字段去匹配某個字符串,只要任何一個字段包括該字符串即可,就可以使用multimatch
SearchQuery searchQuery=newNativeSearchQueryBuilder().withQuery(multiMatchQuery(title,"title","content")).withPageable(pageable).build();
5.完全包含查詢
之前的查詢中,當我們輸入“我天”時,ES會把分詞后所有包含“我”和“天”的都查詢出來,如果我們希望必須是包含了兩個字的才能被查詢出來,那么我們就需要設置一下Operator。
SearchQuery searchQuery=newNativeSearchQueryBuilder().withQuery(matchQuery("title",title).operator(MatchQueryBuilder.Operator.AND)).build();
6合并查詢
即boolQuery,可以設置多個條件的查詢方式。它的作用是用來組合多個Query,有四種方式來組合,must,mustnot,filter,should。
must代表返回的文檔必須滿足must子句的條件,會參與計算分值;
filter代表返回的文檔必須滿足filter子句的條件,但不會參與計算分值;
should代表返回的文檔可能滿足should子句的條件,也可能不滿足,有多個should時滿足任何一個就可以,通過minimum_should_match設置至少滿足幾個。
mustnot代表必須不滿足子句的條件。
譬如我想查詢title包含“XXX”,且userId=“1”,且weight最好小于5的結果。那么就可以使用boolQuery來組合。
SearchQuery searchQuery=newNativeSearchQueryBuilder().withQuery(boolQuery().must(termQuery("userId",userId)).should(rangeQuery("weight").lt(weight)).must(matchQuery("title",title))).build();
7過濾查詢bool的使用
Bool查詢對應Lucene中的BooleanQuery,它由一個或者多個子句組成,每個子句都有特定的類型
must
返回的文檔必須滿足must子句的條件,并且參與計算分值
filter
返回的文檔必須滿足filter子句的條件,但是不會像must一樣,參與計算分值
should
返回的文檔可能滿足should子句的條件.在一個bool查詢中,如果沒有must或者filter,有一個或者多個should子句,那么只要滿足一個就可以返回.minimum_should_match參數定義了至少滿足幾個子句.
must_not
返回的文檔必須不滿足定義的條件
如果一個查詢既有filter又有should,那么至少包含一個should子句.
bool查詢也支持禁用協同計分選項disable_coord.一般計算分值的因素取決于所有的查詢條件.
bool查詢也是采用more_matches_is_better的機制,因此滿足must和should子句的文檔將會合并起來計算分值.
8高亮查詢
//高亮查詢
HighlightBuilder highlightBuilder =new HighlightBuilder();
highlightBuilder.field(new HighlightBuilder.Field("message"));
highlightBuilder.preTags("<span style=\"color:red\">");? //高亮設置
highlightBuilder.postTags("</span>");
1.概念介紹
1.索引(index)
相當于Mysql中的數據庫(database),用于存放文檔
2. 類型(type)
相當于Mysql中的表(table),定義了相同數據結構的文檔存放
3. 文檔(document)
相當于Mysql中的一行記錄(row),是索引的基本單元,包含一個或多個鍵值對,表現形式就是json對象
4. 字段(filed)
相當于Mysql中的一列(column),是key-value形式的
一個運行中的ES實例是一個節點,一個集群中有一個或者多個節點,這些節點中的cluste.name是相同的。
我們在創建索引時可以指定這個索引的主分片數和副本分片數,默認是5個主分片,1個副本分片(5*1=5個分片)。這些分片會均勻的分布在集群的所有數據節點中。
在客戶端發送請求時,集群中所有的節點都能處理這個請求,節點會找到文檔所存儲的分片,并在該分片所在的節點做檢索,將結果返回給請求的那個節點做最終的過濾和組合,然后返回給客戶端。
那么在往這個索引上面存儲文檔時,會存儲到5個分片中的一個分片上,而這個路由是由下面的公式決定的
shard = hash(routing) % number_of_primary_shards
routing是可以設置的一個值,默認是文檔的id
number_of_primary_shards是主分片的數量(5)
也正是這個公式的原因,所以規定了索引創建后不能修改主分片數
集群, 由多個節點組成,一個集群中有很多的很多個索引庫
setting, 配置集群中索引庫的信息, 每個索引庫默認是5個分片和2個副本
節點?, 一臺物理機器
分片, 節點中存放的是某個索引庫的一個分片.
文檔, 文檔就是數據庫中的一行數據或者是一個爬蟲爬取回來的網頁信息或者是一個訂單信息.
字段, 字段是文檔中的屬性(id, title, contend, author,time)
mapping, 描述字段的信息(是否分析,是否存儲,是否索引等)
副本, 就是分片的備份, 保存數據不丟失.
文檔
字段
mapping
2.使用方法
1.客戶端連接
spring-boot-data-elasticsearch(ElasticsearchRespositor搭配ElasticsearchTemplate使用)
TransportClient(7以后不支持)
RestHighLevelClient(推薦使用,版本推薦7)
2.maven倉庫
<properties>
? ? <!--不加請求會出現addParameters的報錯,-->
? ? <elasticsearch.version>7.3.2</elasticsearch.version>
</properties>
<dependency>
? ? <groupId>org.elasticsearch</groupId>
? ? <artifactId>elasticsearch</artifactId>
?? <version>7.3.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
? ? <artifactId>elasticsearch-rest-high-level-client</artifactId>
?? <version>7.3.2</version>
</dependency>
3.初始化客戶端
//初始化客戶端
RestHighLevelClient client =new RestHighLevelClient(
RestClient.builder(
new HttpHost("集群",9200,"http")
)
);
//創建查詢request
SearchRequest search =new SearchRequest();
//構建查詢條件
SearchSourceBuilder sourceBuilder =new SearchSourceBuilder();
//matchQuery模糊查詢
sourceBuilder.query(QueryBuilders.matchQuery("xx","yy"));
//queryStringQuery全文查詢
sourceBuilder.query(QueryBuilders.queryStringQuery("xx"));
//短語匹配 ,和match查詢類似,match_phrase查詢首先解析查詢字符串來產生一個詞條列表。
// 然后會搜索所有的詞條,但只保留包含了所有搜索詞條的文檔,并且詞條的位置要鄰接。
// 一個針對短語“中華共和國”的查詢不會匹配“中華人民共和國”,因為沒有含有鄰接在一起的“中華”和“共和國”詞條。
sourceBuilder.query(QueryBuilders.matchPhraseQuery("xx","yy"));
//multi_match多個字段匹配某字符串
sourceBuilder.query(QueryBuilders.multiMatchQuery("xx","yy","zz"));
//完全包含查詢,之前的查詢中,當我們輸入“我天”時,ES會把分詞后所有包含“我”和“天”的都查詢出來
//如果我們希望必須是包含了兩個字的才能被查詢出來,那么我們就需要設置一下Operator。and或者or
sourceBuilder.query(QueryBuilders.matchQuery("xx","yy").operator(Operator.AND));
//BoolQueryBuilder合并查詢,使用合并查詢可以把多個查詢條件合并到一起
BoolQueryBuilder boolQuery =new BoolQueryBuilder();
//范圍查詢
boolQuery.must(QueryBuilders.rangeQuery("xx")
.gt("zz")
.lt("mm"));
sourceBuilder.query(boolQuery);
//把查詢條件添加到查詢request中
search.source(sourceBuilder);
//同步調用
SearchResponse response = client.search(search, RequestOptions.DEFAULT);
//異步調用
client.searchAsync(search, RequestOptions.DEFAULT, new ActionListener() {
@Override
? ? public void onResponse(SearchResponse searchResponse) {
}
@Override
? ? public void onFailure(Exception e) {
}
});
3.集群
1.ES 選主流程
每個節點結算最小的ID,把它選舉為臨時Master,然后對該master進行投票;
每個節點收集票數,當票數大于指定的法定個數時,成為Master,然后對加入的節點進行集群信息廣播。
法定個數:有Master資格的節點數: (n/2 + 1 )
流程描述
服務啟動后,進行ping操作,ping所有的節點,然后得到一個fullPingResponses,本節點也加入該list;
創建兩個列表
活動的Master列表 activeMasters:遍歷剛剛的fullPingResponses然后將每個節點認為的Master存入該list,正常情況下是只有一個Master;
候選Master列表 candidateMasters:遍歷剛剛的fullPingResponses然后將具備Master節點資格的節點加入此list;
如果activeMasters不為空,則從activeMasters中選擇,否則從candidateMasters中選擇,從候選列表選擇時要先判斷候選者列表大小是否大于法定人數,否則失敗;
在從列表進行選擇時,直接對列表進行排序,然后選出最小ID的節點充當臨時節點;
開始進行投票,每個節點都向自己認為的Master進行joinRequest請求,此時會有兩種情況
本節點是Master時:此時該節點進行統計,統計發送過來的joinRequest個數,如果在指定的時間(默認30s,可配置)內達到法定人數,發布集群信息,并回復joinRequest請求,最后完成選舉,否則選舉失敗;
本節點不是Master時:拒絕其他節點joinRequest,向其認為的Master發送joinRequest請求,并等待,如果在指定的時間(1min,可配置)未收到回復或異常重試3次都失敗了則選舉失敗,重新嘗試;如果收到的回復中沒有Master信息或者Master信息不是之前選擇的臨時Master節點則選舉失敗。
2.垃圾回收
不要更改默認的垃圾回收器!
Elasticsearch 默認的垃圾回收器( GC )是 CMS。 這個垃圾回收器可以和應用并行處理,以便它可以最小化停頓。 然而,它有兩個 stop-the-world 階段,處理大內存也有點吃力。
盡管有這些缺點,它還是目前對于像 Elasticsearch 這樣低延遲需求軟件的最佳垃圾回收器。官方建議使用 CMS。
3.重要配置修改
#數據保存路徑,你可以通過逗號分隔指定多個目錄。
path.data: /path/to/data1,/path/to/data2?
#?日志路徑
# 插件路徑
path.plugins: /path/to/plugins
#最小主節點數
discovery.zen.minimum_master_nodes: 2
3.日志
Elasticsearch 會輸出很多日志,都放在?ES_HOME/logs?目錄下。默認的日志記錄等級是?INFO?。它提供了適度的信息,但是又設計好了不至于讓你的日志太過龐大。
你?可以?修改?logging.yml?文件然后重啟你的節點——但是這樣做即繁瑣還會導致不必要的宕機時間。作為替代,你可以通過?cluster-settings?API 更新日志記錄級別,就像我們前面剛學過的那樣。
要實現這個更新,選擇你感興趣的日志器,然后在前面補上?logger.?。對根日志器你可以用?logger._root?來表示。
讓我們調高節點發現的日志記錄級別:
PUT /_cluster/settings
{??
? "transient" :
? ? ? ?{??
? ? ? ? ?"logger.discovery" : "DEBUG"? ?
? ? ? ? ?}
}
還有另一個日志叫?慢日志?。這個日志的目的是捕獲那些超過指定時間閾值的查詢和索引請求。這個日志用來追蹤由用戶產生的很慢的請求很有用。
默認情況,慢日志是不開啟的。要開啟它,需要定義具體動作(query,fetch 還是 index),你期望的事件記錄等級(?WARN?、?DEBUG?等),以及時間閾值。
這是一個索引級別的設置,也就是說可以獨立應用給單個索引:
PUT /my_index/_settings{??
?"index.search.slowlog.threshold.query.warn" : "10s", ? ? "index.search.slowlog.threshold.fetch.debug": "500ms", ? ? "index.indexing.slowlog.threshold.index.info": "5s"?
你也可以在?elasticsearch.yml?文件里定義這些閾值。沒有閾值設置的索引會自動繼承在靜態配置文件里配置的參數。
一旦閾值設置過了,你可以和其他日志器一樣切換日志記錄等級:
PUT /_cluster/settings
{??
? ?"transient" :
? ? ? ?{? ? ??
? "logger.index.search.slowlog" : "DEBUG",
?? "logger.index.indexing.slowlog" : "WARN"
面試-------------------------
Elasticsearch 查詢數據的工作原理是什么?
ES 寫入數據的工作原理是什么啊?ES 查詢數據的工作原理是什么啊?底層的 Lucene 介紹一下唄?倒排索引了解嗎?
面試官心理分析
問這個,其實面試官就是要看看你了解不了解 es 的一些基本原理,因為用 es 無非就是寫入數據,搜索數據。你要是不明白你發起一個寫入和搜索請求的時候,es 在干什么,那你真的是......
對 es 基本就是個黑盒,你還能干啥?你唯一能干的就是用 es 的 api 讀寫數據了。要是出點什么問題,你啥都不知道,那還能指望你什么呢?
面試題剖析
es 寫數據過程
客戶端選擇一個 node 發送請求過去,這個 node 就是coordinating node(協調節點)。
coordinating node對 document 進行路由,將請求轉發給對應的 node(有 primary shard)。
實際的 node 上的primary shard處理請求,然后將數據同步到replica node。
coordinating node如果發現primary node和所有replica node都搞定之后,就返回響應結果給客戶端。
es-write
es 讀數據過程
可以通過doc id來查詢,會根據doc id進行 hash,判斷出來當時把doc id分配到了哪個 shard 上面去,從那個 shard 去查詢。
客戶端發送請求到任意一個 node,成為coordinate node。
coordinate node對doc id進行哈希路由,將請求轉發到對應的 node,此時會使用round-robin隨機輪詢算法,在primary shard以及其所有 replica 中隨機選擇一個,讓讀請求負載均衡。
接收請求的 node 返回 document 給coordinate node。
coordinate node返回 document 給客戶端。
es 搜索數據過程
es 最強大的是做全文檢索,就是比如你有三條數據:
java真好玩兒啊
java好難學啊
j2ee特別牛Copy?to?clipboardErrorCopied
你根據java關鍵詞來搜索,將包含java的document給搜索出來。es 就會給你返回:java真好玩兒啊,java好難學啊。
客戶端發送請求到一個coordinate node。
協調節點將搜索請求轉發到所有的 shard 對應的primary shard或replica shard,都可以。
query phase:每個 shard 將自己的搜索結果(其實就是一些doc id)返回給協調節點,由協調節點進行數據的合并、排序、分頁等操作,產出最終結果。
fetch phase:接著由協調節點根據doc id去各個節點上拉取實際的document數據,最終返回給客戶端。
寫請求是寫入 primary shard,然后同步給所有的 replica shard;讀請求可以從 primary shard 或 replica shard 讀取,采用的是隨機輪詢算法。
寫數據底層原理
es-write-detail
先寫入內存 buffer,在 buffer 里的時候數據是搜索不到的;同時將數據寫入 translog 日志文件。
如果 buffer 快滿了,或者到一定時間,就會將內存 buffer 數據refresh到一個新的segment file中,但是此時數據不是直接進入segment file磁盤文件,而是先進入os cache。這個過程就是refresh。
每隔 1 秒鐘,es 將 buffer 中的數據寫入一個新的segment file,每秒鐘會產生一個新的磁盤文件segment file,這個segment file中就存儲最近 1 秒內 buffer 中寫入的數據。
但是如果 buffer 里面此時沒有數據,那當然不會執行 refresh 操作,如果 buffer 里面有數據,默認 1 秒鐘執行一次 refresh 操作,刷入一個新的 segment file 中。
操作系統里面,磁盤文件其實都有一個東西,叫做os cache,即操作系統緩存,就是說數據寫入磁盤文件之前,會先進入os cache,先進入操作系統級別的一個內存緩存中去。只要buffer中的數據被 refresh 操作刷入os cache中,這個數據就可以被搜索到了。
為什么叫 es 是準實時的?NRT,全稱near real-time。默認是每隔 1 秒 refresh 一次的,所以 es 是準實時的,因為寫入的數據 1 秒之后才能被看到。可以通過 es 的restful api或者java api,手動執行一次 refresh 操作,就是手動將 buffer 中的數據刷入os cache中,讓數據立馬就可以被搜索到。只要數據被輸入os cache中,buffer 就會被清空了,因為不需要保留 buffer 了,數據在 translog 里面已經持久化到磁盤去一份了。
重復上面的步驟,新的數據不斷進入 buffer 和 translog,不斷將buffer數據寫入一個又一個新的segment file中去,每次refresh完 buffer 清空,translog 保留。隨著這個過程推進,translog 會變得越來越大。當 translog 達到一定長度的時候,就會觸發commit操作。
commit 操作發生第一步,就是將 buffer 中現有數據refresh到os cache中去,清空 buffer。然后,將一個commit point寫入磁盤文件,里面標識著這個commit point對應的所有segment file,同時強行將os cache中目前所有的數據都fsync到磁盤文件中去。最后清空現有 translog 日志文件,重啟一個 translog,此時 commit 操作完成。
這個 commit 操作叫做flush。默認 30 分鐘自動執行一次flush,但如果 translog 過大,也會觸發flush。flush 操作就對應著 commit 的全過程,我們可以通過 es api,手動執行 flush 操作,手動將 os cache 中的數據 fsync 強刷到磁盤上去。
translog 日志文件的作用是什么?你執行 commit 操作之前,數據要么是停留在 buffer 中,要么是停留在 os cache 中,無論是 buffer 還是 os cache 都是內存,一旦這臺機器死了,內存中的數據就全丟了。所以需要將數據對應的操作寫入一個專門的日志文件translog中,一旦此時機器宕機,再次重啟的時候,es 會自動讀取 translog 日志文件中的數據,恢復到內存 buffer 和 os cache 中去。
translog 其實也是先寫入 os cache 的,默認每隔 5 秒刷一次到磁盤中去,所以默認情況下,可能有 5 秒的數據會僅僅停留在 buffer 或者 translog 文件的 os cache 中,如果此時機器掛了,會丟失5 秒鐘的數據。但是這樣性能比較好,最多丟 5 秒的數據。也可以將 translog 設置成每次寫操作必須是直接fsync到磁盤,但是性能會差很多。
實際上你在這里,如果面試官沒有問你 es 丟數據的問題,你可以在這里給面試官炫一把,你說,其實 es 第一是準實時的,數據寫入 1 秒后可以搜索到;可能會丟失數據的。有 5 秒的數據,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盤上,此時如果宕機,會導致 5 秒的數據丟失。
總結一下,數據先寫入內存 buffer,然后每隔 1s,將數據 refresh 到 os cache,到了 os cache 數據就能被搜索到(所以我們才說 es 從寫入到能被搜索到,中間有 1s 的延遲)。每隔 5s,將數據寫入 translog 文件(這樣如果機器宕機,內存數據全沒,最多會有 5s 的數據丟失),translog 大到一定程度,或者默認每隔 30mins,會觸發 commit 操作,將緩沖區的數據都 flush 到 segment file 磁盤文件中。
數據寫入 segment file 之后,同時就建立好了倒排索引。
刪除/更新數據底層原理
如果是刪除操作,commit 的時候會生成一個.del文件,里面將某個 doc 標識為deleted狀態,那么搜索的時候根據.del文件就知道這個 doc 是否被刪除了。
如果是更新操作,就是將原來的 doc 標識為deleted狀態,然后新寫入一條數據。
buffer 每 refresh 一次,就會產生一個segment file,所以默認情況下是 1 秒鐘一個segment file,這樣下來segment file會越來越多,此時會定期執行 merge。每次 merge 的時候,會將多個segment file合并成一個,同時這里會將標識為deleted的 doc 給物理刪除掉,然后將新的segment file寫入磁盤,這里會寫一個commit point,標識所有新的segment file,然后打開segment file供搜索使用,同時刪除舊的segment file。
底層 lucene
簡單來說,lucene 就是一個 jar 包,里面包含了封裝好的各種建立倒排索引的算法代碼。我們用 Java 開發的時候,引入 lucene jar,然后基于 lucene 的 api 去開發就可以了。
通過 lucene,我們可以將已有的數據建立索引,lucene 會在本地磁盤上面,給我們組織索引的數據結構。
倒排索引
在搜索引擎中,每個文檔都有一個對應的文檔 ID,文檔內容被表示為一系列關鍵詞的集合。例如,文檔 1 經過分詞,提取了 20 個關鍵詞,每個關鍵詞都會記錄它在文檔中出現的次數和出現位置。
那么,倒排索引就是關鍵詞到文檔ID 的映射,每個關鍵詞都對應著一系列的文件,這些文件中都出現了關鍵詞。
舉個栗子。
有以下文檔:
DocIdDoc
1谷歌地圖之父跳槽 Facebook
2谷歌地圖之父加盟 Facebook
3谷歌地圖創始人拉斯離開谷歌加盟 Facebook
4谷歌地圖之父跳槽 Facebook 與 Wave 項目取消有關
5谷歌地圖之父拉斯加盟社交網站 Facebook
對文檔進行分詞之后,得到以下倒排索引。
WordIdWordDocIds
1谷歌1, 2, 3, 4, 5
2地圖1, 2, 3, 4, 5
3之父1, 2, 4, 5
4跳槽1, 4
5Facebook1, 2, 3, 4, 5
6加盟2, 3, 5
7創始人3
8拉斯3, 5
9離開3
10與4
......
另外,實用的倒排索引還可以記錄更多的信息,比如文檔頻率信息,表示在文檔集合中有多少個文檔包含某個單詞。
那么,有了倒排索引,搜索引擎可以很方便地響應用戶的查詢。比如用戶輸入查詢Facebook,搜索系統查找倒排索引,從中讀出包含這個單詞的文檔,這些文檔就是提供給用戶的搜索結果。
要注意倒排索引的兩個重要細節:
倒排索引中的所有詞項對應一個或多個文檔;
倒排索引中的詞項根據字典順序升序排列
上面只是一個簡單的例子,并沒有嚴格按照字典順序升序排列。