一、走進Elasticsearch
1.1 全文檢索
1.1.1 為什么要使用全文檢索
用戶訪問我們的首頁,一般都會直接搜索來尋找自己想要購買的商品。而商品的數(shù)量非常多,而且分類繁雜。如果能正確的顯示用戶想要的商品,并進行合理的過濾,盡快促成交易,是搜索系統(tǒng)要研究的核心。面對這樣復雜的搜索業(yè)務(wù)和數(shù)據(jù)量,使用傳統(tǒng)數(shù)據(jù)庫搜索就顯得力不從心,一般我們都會使用全文檢索技術(shù)。常見的全文檢索技術(shù)有Apache Lucene、Solr、Ferret、Elasticsearch等。
1.1.2 理解索引結(jié)構(gòu)
下圖是索引結(jié)構(gòu),下邊黑色部分是物理結(jié)構(gòu),上邊藍色部分是邏輯結(jié)構(gòu),邏輯結(jié)構(gòu)也是為了更好的去描述工作原理及去使用物理結(jié)構(gòu)中的索引文件。

邏輯結(jié)構(gòu)部分是一個倒排索引列表:
1、將要搜索的文檔內(nèi)容分詞,所有不重復的詞組成分詞列表。
2、將搜索的文檔最終以Document方式存儲起來。
3、每個詞和document都有關(guān)聯(lián)。
如下:

現(xiàn)在,如果我們想搜索quick brown,我們只需要查找包含每個詞條的文檔:

兩個文檔都匹配,但是第二個文檔比第一個匹配度更高。如果我們使用僅計算匹配詞條數(shù)量的簡單相似性算法,那么,我們可以說,對于我們查詢的相關(guān)性來講,第二個文檔比第一個文檔更佳。
1.1.3 倒排索引(Inverted Index)
該索引表中的每一項都包括一個屬性值和具有該屬性值的各記錄的地址。由于不是由記錄來確定屬性值,而是由屬性值來確定記錄的位置,因而稱為倒排索引(inverted index)。Elasticsearch能夠?qū)崿F(xiàn)快速、高效的搜索功能,正是基于倒排索引原理。
我們平時背詩一般是從前往后背,先記詩名、作者,然后背詩的內(nèi)容。在我們腦子里,大概是這樣的:

普通的索引,是以詩名作為key,詩的內(nèi)容作為value。如果有人讓你背靜夜思你馬上能反應(yīng)過來,因為你從索引直接找到了詩。但如果有人讓你說出帶”前“字的詩句,由于沒有索引,你只能遍歷腦海中所有詩詞,當你的腦海中詩詞量大的時候,就很難在短時間內(nèi)得到結(jié)果了。
如果采用倒排索引(又叫反向索引)的方式來存儲就能快速檢索出帶”前“字的詩句。

這就是倒排索引,以詩句內(nèi)容中的一些關(guān)鍵字作為索引,來找到詩句。但這種方式,一句詩就可以建立10個倒排索引,詩句字數(shù)越多索引量還要更多,存儲會呈現(xiàn)爆炸性地增長。我們可以做”壓縮性“存儲,既然我們可以通過詩名就想起一首詩,那反向索引就沒必要索引到詩句了,只要索引到詩名就行。

value不存詩句改存詩題,數(shù)據(jù)量就會減少很多。這里,詩題可以理解為數(shù)據(jù)正向索引。
1.1.4 搜索引擎原理
百度、谷歌等這些搜索引擎的原理和我們背詩是一樣的,最核心的都是建立倒排索引。搜索引擎三大過程:爬取內(nèi)容、進行分詞、建立反向索引。

其中標藍色字體的”的“、”而“由于經(jīng)過了停頓詞過濾,所以不會作為分詞。因為停頓詞就是沒有意義的詞,這些詞沒有必要建立索引。
1.2 Elasticsearch
1.2.1 Elasticsearch簡介
Elasticsearch是一個開源的分布式、RESTful 風格的搜索和數(shù)據(jù)分析引擎,它的底層是開源庫Apache Lucene。
Lucene 可以說是當下最先進、高性能、全功能的搜索引擎庫——無論是開源還是私有,但它也僅僅只是一個庫。為了充分發(fā)揮其功能,你需要使用 Java 并將 Lucene 直接集成到應(yīng)用程序中。 更糟糕的是,您可能需要獲得信息檢索學位才能了解其工作原理,因為Lucene 非常復雜。
為了解決Lucene使用時的繁復性,于是Elasticsearch便應(yīng)運而生。它使用 Java 編寫,內(nèi)部采用 Lucene 做索引與搜索,但是它的目標是使全文檢索變得更簡單,簡單來說,就是對Lucene 做了一層封裝,它提供了一套簡單一致的 RESTful API 來幫助我們實現(xiàn)存儲和檢索。
當然,Elasticsearch 不僅僅是 Lucene,并且也不僅僅只是一個全文搜索引擎。 它可以被下面這樣準確地形容:
- 一個分布式的實時文檔存儲,每個字段可以被索引與搜索;
- 一個分布式實時分析搜索引擎;
- 能勝任上百個服務(wù)節(jié)點的擴展,并支持 PB 級別的結(jié)構(gòu)化或者非結(jié)構(gòu)化數(shù)據(jù)。
由于Elasticsearch的功能強大和使用簡單,維基百科、衛(wèi)報、Stack Overflow、GitHub等都紛紛采用它來做搜索?,F(xiàn)在,Elasticsearch已成為全文搜索領(lǐng)域的主流軟件之一。
1.2.2 Elasticsearch工作原理
當Elasticsearch的節(jié)點啟動后,它會利用多播(multicast)(或者單播,如果用戶更改了配置)尋找集群中的其它節(jié)點,并與之建立連接。這個過程如下圖所示:

1.2.3 Elasticsearch核心概念
1、Cluster:集群
Elasticsearch可以作為一個獨立的單個搜索服務(wù)器,不過,為了處理大型數(shù)據(jù)集,實現(xiàn)容錯和高可用性,Elasticsearch可以運行在許多互相合作的服務(wù)器上。這些服務(wù)器的集合稱為集群。
2、Node:節(jié)點
形成集群的每個服務(wù)器稱為節(jié)點。
3、Shard:分片
當有大量的文檔時,由于內(nèi)存的限制、磁盤處理能力不足、無法足夠快的響應(yīng)客戶端的請求等,一個節(jié)點可能不夠。這種情況下,數(shù)據(jù)可以分為較小的分片。每個分片放到不同的服務(wù)器上。 當你查詢的索引分布在多個分片上時,Elasticsearch會把查詢發(fā)送給每個相關(guān)的分片,并將結(jié)果組合在一起,而應(yīng)用程序并不知道分片的存在。即:這個過程對用戶來說是透明的。
4、Replia:副本
為提高查詢吞吐量或?qū)崿F(xiàn)高可用性,可以使用分片副本。 副本是一個分片的精確復制,每個分片可以有零個或多個副本。ES中可以有許多相同的分片,其中之一被選擇更改索引操作,這種特殊的分片稱為主分片。 當主分片丟失時,如:該分片所在的數(shù)據(jù)不可用時,集群將副本提升為新的主分片。
5、全文檢索
全文檢索就是對一篇文章進行索引,可以根據(jù)關(guān)鍵字搜索,類似于mysql里的like語句。 全文索引就是把內(nèi)容根據(jù)詞的意義進行分詞,然后分別創(chuàng)建索引,例如”你們的激情是因為什么事情來的” 可能會被分詞成:“你們“,”激情“,“什么事情“,”來“ 等token,這樣當你搜索“你們” 或者 “激情” 都會把這句搜出來。
1.2.6 Elasticsearch數(shù)據(jù)架構(gòu)的主要概念(與關(guān)系數(shù)據(jù)庫Mysql對比)

1、關(guān)系型數(shù)據(jù)庫中的數(shù)據(jù)庫(DataBase),等價于ES中的索引(Index)
2、一個數(shù)據(jù)庫下面有N張表(Table),等價于1個索引Index下面有N多類型(Type),
3、一個數(shù)據(jù)庫表(Table)下的數(shù)據(jù)由多行(ROW)多列(column,屬性)組成,等價于1個Type由多個文檔(Document)和多Field組成。
4、在一個關(guān)系型數(shù)據(jù)庫里面,schema定義了表、每個表的字段,還有表和字段之間的關(guān)系。 與之對應(yīng)的,在ES中:Mapping定義索引下的Type的字段處理規(guī)則,即索引如何建立、索引類型、是否保存原始索引JSON文檔、是否壓縮原始JSON文檔、是否需要分詞處理、如何進行分詞處理等。
5、在數(shù)據(jù)庫中的增insert、刪delete、改update、查search操作等價于ES中的增PUT/POST、刪Delete、改_update、查GET
1.2.6 Elasticsearch特點和優(yōu)勢
1、分布式實時文件存儲,可將每一個字段存入索引,使其可以被檢索到。
2、實時分析的分布式搜索引擎。
分布式:索引分拆成多個分片,每個分片可有零個或多個副本。集群中的每個數(shù)據(jù)節(jié)點都可承載一個或多個分片,并且協(xié)調(diào)和處理各種操作; 負載再平衡和路由在大多數(shù)情況下自動完成。
3、可以擴展到上百臺服務(wù)器,處理PB級別的結(jié)構(gòu)化或非結(jié)構(gòu)化數(shù)據(jù)。也可以運行在單臺PC上(已測試)
4、支持插件機制,分詞插件(IK分詞器)、同步插件、Hadoop插件、可視化插件等(Kibana)。
1.3 Elasticsearch國內(nèi)外使用優(yōu)秀案例
1、 2013年初,GitHub拋棄了Solr,采取ElasticSearch 來做PB級的搜索。 “GitHub使用ElasticSearch搜索20TB的數(shù)據(jù),包括13億文件和1300億行代碼”。
2、維基百科:啟動以elasticsearch為基礎(chǔ)的核心搜索架構(gòu)。
3、SoundCloud:“SoundCloud使用ElasticSearch為1.8億用戶提供即時而精準的音樂搜索服務(wù)”。
4、百度:百度目前廣泛使用ElasticSearch作為文本數(shù)據(jù)分析,采集百度所有服務(wù)器上的各類指標數(shù)據(jù)及用戶自定義數(shù)據(jù),通過對各種數(shù)據(jù)進行多維分析展示,輔助定位分析實例異?;驑I(yè)務(wù)層面異常。目前覆蓋百度內(nèi)部20多個業(yè)務(wù)線(包括casio、云分析、網(wǎng)盟、預測、文庫、直達號、錢包、風控等),單集群最大100臺機器,200個ES節(jié)點,每天導入30TB+數(shù)據(jù)。
1.4 ELK
ELK是Elasticsearch、Logstash、Kibana三大開源框架首字母大寫簡稱。市面上也被成為Elastic Stack。Logstash是ELK的中央數(shù)據(jù)流引擎,用于從不同目標(文件/數(shù)據(jù)存儲/MQ)收集的不同格式數(shù)據(jù),經(jīng)過過濾后支持輸出到不同目的地(文件/MQ/redis/Elasticsearch/kafka等)。Kibana可以將Elasticsearch的數(shù)據(jù)通過友好的頁面展示出來,提供實時分析的功能。
市面上很多開發(fā)只要提到ELK能夠一致說出它是一個日志分析架構(gòu)技術(shù)棧總稱,但實際上ELK不僅僅適用于日志分析,它還可以支持其它任何數(shù)據(jù)分析和收集的場景,日志分析和收集只是更具有代表性。并非唯一性。
二、Elasticsearch映射與數(shù)據(jù)類型
2.1 動態(tài)映射和靜態(tài)映射
映射(Mapping)相當于數(shù)據(jù)表的表結(jié)構(gòu)。ElasticSearch中的映射用來定義一個文檔,可以定義所包含的字段以及字段的類型、分詞器及屬性等等。
映射可以分為動態(tài)映射和靜態(tài)映射。
動態(tài)映射(dynamic mapping),在關(guān)系數(shù)據(jù)庫中,需要事先創(chuàng)建數(shù)據(jù)庫,然后在該數(shù)據(jù)庫實例下創(chuàng)建數(shù)據(jù)表,然后才能在該數(shù)據(jù)表中插入數(shù)據(jù)。而ElasticSearch中不需要事先定義映射(Mapping),文檔寫入ElasticSearch時,會根據(jù)文檔字段自動識別類型,這種機制稱之為動態(tài)映射。
靜態(tài)映射,在ElasticSearch中也可以事先定義好映射,包含文檔的各個字段及其類型等,這種方式稱之為靜態(tài)映射。
2.2 靜態(tài)映射數(shù)據(jù)類型
2.2.1 字符串類型

2.2.2 整數(shù)類型

2.2.3 浮點類型

2.2.4 date類型
日期類型表示格式可以是以下幾種
1、日期格式的字符串,比如“2020-01-12”或“2020-01-12 12:10:30”
2、long類型的毫秒數(shù)(milliseconds-since-the-epoch,epoch就是指UNIX誕生的UTC時間1970年1月1日0時0分0秒)
3、integer的秒數(shù)(seconds-since-the-epoch)
2.2.5 boolean類型
邏輯類型(布爾類型)可以接受true/false
2.2.6 binary類型
二進制字段是指用base64來表示索引中存儲的二進制數(shù)據(jù),可用來存儲二進制形式的數(shù)據(jù),例如圖像。默認情況下,該類型的字段只存儲不索引。二進制類型只支持index_name屬性。
2.2.7 array類型
在ElasticSearch中,沒有專門的數(shù)組(Array)數(shù)據(jù)類型,但是,在默認情況下,任意一個字段都可以包含0或多個值,這個意味著每個字段默認都是數(shù)組類型,只不過,數(shù)組類型的各個元素值的數(shù)據(jù)類型必須相同。在ElasticSearch中,數(shù)組是開箱即用的(out of box),不需要進行任何配置,就可以直接使用。
在同一個數(shù)組中,數(shù)組元素的數(shù)據(jù)類型是相同的,ElasticSearch不支持元素為多個數(shù)據(jù)類型:[10,"some string"]。
2.2.8 object類型
JSON天生具有層級關(guān)系,文檔會包含嵌套的對象。
三、安裝并運行Elasticsearch
在下載之前你應(yīng)該確保你的 Java 版本保持在 1.8 及以上,這是 Elasticsearch 的硬性要求,可以自行打開命令行輸入 java -version 來查看 Java 的版本
安裝完 Java,就可以跟著官方文檔安裝 Elasticsearch,直接下載壓縮包比較簡單。我的開發(fā)環(huán)境是Mac OS,因此我選擇的是Mac版本。

下載壓縮包后對其進行解壓,cd進入解壓elasticsearch的bin目錄
cd /Users/AC/soft/elasticsearch-7.6.2/bin
運行elasticsearch文件啟動elasticsearch
./elasticsearch
此時,Elasticsearch運行在本地的9200端口,在瀏覽器中輸入地址http://localhost:9200/ 如果看到以下信息就說明你的電腦已成功安裝Elasticsearch

默認情況下,Elasticsearch 只允許本機訪問,如果需要遠程訪問,可以修改 Elastic 安裝目錄的config/elasticsearch.yml文件,去掉network.host的注釋,將它的值改成0.0.0.0,然后重新啟動 Elasticsearch。
network.host: 0.0.0.0
上面代碼中,設(shè)成0.0.0.0讓任何人都可以訪問。線上服務(wù)不要這樣設(shè)置,要設(shè)成具體的 IP。
四、Elasticsearch可視化操作平臺Kibana
Kibana 是一個開源的分析和可視化平臺,旨在與 Elasticsearch 合作。Kibana 提供搜索、查看和與存儲在 Elasticsearch 索引中的數(shù)據(jù)進行交互的功能。開發(fā)者或運維人員可以輕松地執(zhí)行高級數(shù)據(jù)分析,并在各種圖表、表格和地圖中可視化數(shù)據(jù)。
你可以從 Elasticsearch 的官網(wǎng)獲取最新版本的Kibana。解壓文檔后,cd進kibana的bin目錄
cd /Users/AC/soft/kibana-7.6.2/bin
運行kibana文件啟動kibana
./kibana
kibana啟動后運行在5601端口上,我們可以在瀏覽器中輸入http://localhost:5601 地址來訪問kibana。
注意:啟動kibana前必須先啟動Elasticsearch,否則kibana會啟動不成功。

開發(fā)中我們一般用得比較多的是Dev Tools工具

五、Elasticsearch中文分詞器-IK分詞器
5.1 中文分詞
首先我們通過Postman發(fā)送GET請求查詢分詞效果
GET http://localhost:9200/_analyze
{
"text":"我愛你中國"
}
得到如下結(jié)果,可以發(fā)現(xiàn)ES的默認分詞器無法識別中文:我、我愛你、中國這樣的詞匯,而是簡單的將每個字拆完分為一個詞,這顯然不符合我們的使用要求,所以我們需要安裝中文分詞器來解決這個問題。
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "愛",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "你",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
},
{
"token": "中",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 3
},
{
"token": "國",
"start_offset": 4,
"end_offset": 5,
"type": "<IDEOGRAPHIC>",
"position": 4
}
]
}
或用kibana請求得到效果(用kibana的話就不用再寫IP地址和端口了)

IK分詞器是一款國人開發(fā)的相對簡單的中文分詞器。首先我們訪問 https://github.com/medcl/elasticsearch-analysis-ik/releases 下載與ES對應(yīng)版本的中文分詞器。將解壓后的后的文件夾放入ES根目錄下的plugins/ik目錄下(ik目錄要手動創(chuàng)建),重啟ES即可使用。
IK提供了兩個分詞算法ik_smart和ik_max_word。其中ik_smart為最少切分;ik_max_word為最細粒度劃分。
- ik_max_word:會將文本做最細粒度的拆分,例如「我是程序員」會被拆分為「我、是、程序員、程序、員」。
- ik_smart:會將文本做最少切分,例如「我是程序員」會被拆分為「我、是、程序員」


5.2 自定義詞庫
每年都會涌現(xiàn)一些特殊的流行詞,如網(wǎng)紅,藍瘦香菇,喊麥,鬼畜,一般不會在ik的原生詞典里。如[藍瘦香菇]會被拆分成[藍、瘦、香菇]三個詞,而無法識別到[藍瘦香菇]也是一個詞。

這時就需要自己補充自己的最新的詞語到ik的詞庫里面去。首先進入ES根目錄中的plugins文件夾下的ik文件夾,進入config目錄,創(chuàng)建custom.dic文件,寫入藍瘦香菇。同時打開IKAnalyzer.cfg文件,將新建的custom.dic配置其中,重啟ES。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴展配置</comment>
<!--用戶可以在這里配置自己的擴展字典 -->
<entry key="ext_dict">custom.dic</entry>
<!--用戶可以在這里配置自己的擴展停止詞字典-->
<entry key="ext_stopwords"></entry>
<!--用戶可以在這里配置遠程擴展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用戶可以在這里配置遠程擴展停止詞字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
再次查詢發(fā)現(xiàn)ES的分詞器可以識別到[藍瘦香菇]詞匯

六、索引操作
6.1 創(chuàng)建索引與映射字段
語法
PUT /索引庫名
{
"mappings":{
"類型名稱":{
"properties":{
"字段名":{
"type":"類型",
"index":true,
"store":true,
"analyzer":"分詞器"
}
}
}
}
}
- 類型名稱:就是type的概念,類似于數(shù)據(jù)庫中的不同表。
- 字段名:類似于數(shù)據(jù)庫中的字段名稱。
- type:類似于數(shù)據(jù)庫中字段的類型,可以是text、long、short、data、object等。
- index:是否索引,默認為true。如果你需要根據(jù)該字段進行查詢或排序,則需要將該字段index設(shè)置為true,否則設(shè)置為false(如圖片)。
- store:是否單獨存儲,默認為false,一般內(nèi)容比較多的字段設(shè)置成true,可以提升查詢性能。
- analyzer:分詞器,如ik_smart
示例
PUT /sku
{
"mappings": {
"doc":{
"properties":{
"name":{
"type":"text",
"analyzer":"ik_smart"
},
"price":{
"type":"integer"
},
"image":{
"type":"text"
},
"createTime":{
"type":"date"
},
"spuId":{
"type":"text"
},
"categoryName":{
"type":"keyword"
},
"brandName":{
"type":"keyword"
},
"spec":{
"type":"object"
},
"selNum":{
"type":"integer"
},
"commentNum":{
"type":"integer"
}
}
}
}
}
如果在6.x上執(zhí)行,則會正常執(zhí)行。在elasticsearch7.x上執(zhí)行會失敗,提示信息如下:
{
"error" : {
"root_cause" : [
{
"type" : "mapper_parsing_exception",
"reason" : "Root mapping definition has unsupported parameters: [doc : {properties={commentNum={type=integer}, image={type=text}, brandName={type=keyword}, selNum={type=integer}, createTime={type=date}, price={type=integer}, name={analyzer=ik_smart, type=text}, spuId={type=text}, categoryName={type=keyword}, spec={type=object}}}]"
}
],
"type" : "mapper_parsing_exception",
"reason" : "Failed to parse mapping [_doc]: Root mapping definition has unsupported parameters: [doc : {properties={commentNum={type=integer}, image={type=text}, brandName={type=keyword}, selNum={type=integer}, createTime={type=date}, price={type=integer}, name={analyzer=ik_smart, type=text}, spuId={type=text}, categoryName={type=keyword}, spec={type=object}}}]",
"caused_by" : {
"type" : "mapper_parsing_exception",
"reason" : "Root mapping definition has unsupported parameters: [doc : {properties={commentNum={type=integer}, image={type=text}, brandName={type=keyword}, selNum={type=integer}, createTime={type=date}, price={type=integer}, name={analyzer=ik_smart, type=text}, spuId={type=text}, categoryName={type=keyword}, spec={type=object}}}]"
}
},
"status" : 400
}
出現(xiàn)這個的原因是,elasticsearch7默認不再支持指定索引類型,默認索引類型是_doc。所以在Elasticsearch7中應(yīng)該這么創(chuàng)建索引
PUT /sku
{
"mappings": {
"properties":{
"name":{
"type":"text",
"analyzer":"ik_smart"
},
"price":{
"type":"integer"
},
"image":{
"type":"text"
},
"createTime":{
"type":"date"
},
"spuId":{
"type":"text"
},
"categoryName":{
"type":"keyword"
},
"brandName":{
"type":"keyword"
},
"spec":{
"type":"object"
},
"selNum":{
"type":"integer"
},
"commentNum":{
"type":"integer"
}
}
}
}
執(zhí)行成功,返回結(jié)果為
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "sku"
}
查看索引字段類型
//查看sku文檔
GET sku/_mapping
//查看group文檔
GET group/_mapping

6.2 文檔增加與修改
6.2.1 增加文檔自動生成ID
通過POST請求,可以向一個已經(jīng)存在的索引庫中添加數(shù)據(jù)。
語法:
POST 索引庫名/類型名
{
"key":"value"
}
示例
POST sku/_doc
{
"name":"小米手機",
"price":200000,
"spuId":101,
"createTime":"2020-05-09",
"brandName":"小米",
"categoryName":"手機",
"saleNum":10012,
"commentNum":323,
"spec":{
"網(wǎng)絡(luò)制式":"移動4g",
"屏幕尺寸":"4.5"
}
}
執(zhí)行失敗,提示
{
"error" : {
"root_cause" : [
{
"type" : "cluster_block_exception",
"reason" : "index [sku] blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"
}
],
"type" : "cluster_block_exception",
"reason" : "index [sku] blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"
},
"status" : 403
}
解決辦法
PUT _settings
{
"index": {
"blocks": {
"read_only_allow_delete": "false"
}
}
}
參考:index [XXX] blocked by: [FORBIDDEN/12/index read-only / allow delete (api)]問題解決
再次執(zhí)行,執(zhí)行成功且返回信息如下
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "Ompe7HEBQIJxGG8U13yY",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 2
}
6.2.2 新增文檔指定ID
如果我們想要自己新增的時候指定ID,可以這么做:
語法
PUT /索引庫名/類型/ID值
{
...
}
示例
PUT sku/_doc/1
{
"name":"小米電視",
"price":100000,
"spuId":10110,
"createTime":"2020-05-09",
"brandName":"小米",
"categoryName":"電視",
"saleNum":10012,
"commentNum":323,
"spec":{
"網(wǎng)絡(luò)制式":"移動4g",
"屏幕尺寸":"39"
}
}
可以通過查詢命令查看剛才新增的數(shù)據(jù)
GET sku/_search
6.2.3 修改索引文檔
我們可以繼續(xù)通過 PUT /索引庫名/類型/ID值 的方式來更改剛才插入的數(shù)據(jù)
PUT sku/_doc/1
{
"name":"華為電視",
"price":100000,
"spuId":10110,
"createTime":"2020-05-09",
"brandName":"華為",
"categoryName":"電視",
"saleNum":10012,
"commentNum":323,
"spec":{
"網(wǎng)絡(luò)制式":"移動4g",
"屏幕尺寸":"39"
}
}
6.2.4 通過ID刪除索引文檔
DELETE /sku/_doc/z8qEEHIBZBLFtWo4JEtR
6.3 索引查詢
基本語法
GET /索引庫名/_search
{
"query":{
"查詢類型":{
"查詢條件":"查詢條件值"
}
}
}
這里的query代表一個查詢對象,里面可以有不同的查詢屬性
- 查詢類型:例如match_all,match,term,range等等。
- 查詢條件:查詢條件會根據(jù)類型的不同,寫法也有差異。
6.3.1 查詢所有數(shù)據(jù)(match_all)
示例:
GET /sku/_search
{
"query": {
"match_all": {}
}
}
query:代表查詢對象
match_all:代表查詢所有
上面可以簡寫成:
GET sku/_search
6.3.2 匹配查詢(match)
示例:查詢名稱包含手機的記錄
GET /sku/_search
{
"query": {
"match": {
"name": "手機"
}
}
}
結(jié)果結(jié)果如下:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 0.44183272,
"hits" : [
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "Ompe7HEBQIJxGG8U13yY",
"_score" : 0.44183272,
"_source" : {
"name" : "小米手機",
"price" : 200000,
"spuId" : 101,
"createTime" : "2020-05-09",
"brandName" : "小米",
"categoryName" : "手機",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "移動4g",
"屏幕尺寸" : "4.5"
}
}
},
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.44183272,
"_source" : {
"name" : "蘋果手機",
"price" : 100000,
"spuId" : 10112,
"createTime" : "2020-05-01",
"brandName" : "蘋果",
"categoryName" : "手機",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "全網(wǎng)",
"屏幕尺寸" : "56"
}
}
},
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "4",
"_score" : 0.44183272,
"_source" : {
"name" : "vivo手機",
"price" : 200000,
"spuId" : 10118,
"createTime" : "2020-05-01",
"brandName" : "vivo",
"categoryName" : "手機",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "全網(wǎng)",
"屏幕尺寸" : "56"
}
}
},
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "gAWw_nEBqh6AXa2lyxIz",
"_score" : 0.44183272,
"_source" : {
"name" : "三星手機",
"price" : 200000,
"spuId" : 104,
"createTime" : "2020-05-09",
"brandName" : "三星",
"categoryName" : "手機",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "移動4g",
"屏幕尺寸" : "4.5"
}
}
}
]
}
}
如果我們查詢“小米電視”會有幾條記錄被查詢出來呢?你可以能說會有一條,但我們測試一下會看到結(jié)果為:小米電視、小米手機、三星電視三條結(jié)果,這是為什么呢?這是因為在查詢時,會先搜索關(guān)鍵字進行分詞,對分詞后的字符串進行查詢(分成小米、電視兩個詞),只要是包含這些字符串的都是要被查詢出來的,多個詞之間是or的關(guān)系。
但注意,查詢結(jié)果的匹配分值_score 是不一樣的,分值高的排在前面。
搜索:小米電視
GET /sku/_search
{
"query": {
"match": {
"name": "小米電視"
}
}
}
查詢結(jié)果:
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 2.059239,
"hits" : [
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "2",
"_score" : 2.059239,
"_source" : {
"name" : "小米電視",
"price" : 100000,
"spuId" : 10111,
"createTime" : "2020-05-01",
"brandName" : "小米",
"categoryName" : "電視",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "全網(wǎng)",
"屏幕尺寸" : "56"
}
}
},
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "Ompe7HEBQIJxGG8U13yY",
"_score" : 1.0296195,
"_source" : {
"name" : "小米手機",
"price" : 200000,
"spuId" : 101,
"createTime" : "2020-05-09",
"brandName" : "小米",
"categoryName" : "手機",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "移動4g",
"屏幕尺寸" : "4.5"
}
}
},
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0296195,
"_source" : {
"name" : "三星電視",
"price" : 100000,
"spuId" : 10111,
"createTime" : "2020-05-01",
"brandName" : "三星",
"categoryName" : "電視",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "全網(wǎng)",
"屏幕尺寸" : "56"
}
}
}
]
}
}
如果我們想要進行精確查詢,想要的是查詢“小米電視”這一條記錄,怎么辦呢?我們可以這樣寫:
GET /sku/_search
{
"query": {
"match": {
"name": {
"query": "小米電視",
"operator": "and"
}
}
}
}
查詢結(jié)果只有小米電視一條記錄了:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 2.059239,
"hits" : [
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "2",
"_score" : 2.059239,
"_source" : {
"name" : "小米電視",
"price" : 100000,
"spuId" : 10111,
"createTime" : "2020-05-01",
"brandName" : "小米",
"categoryName" : "電視",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "全網(wǎng)",
"屏幕尺寸" : "56"
}
}
}
]
}
}
operator 指定為and,不指定時默認為or。
Java關(guān)鍵代碼
SearchRequest request = new SearchRequest(index);
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery(pageSearchVO.getField(), keyword);
//精確查詢(關(guān)鍵字不分詞查詢)
queryBuilder.operator(Operator.AND);
builder.query(queryBuilder);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
控制精度
在 所有 與 任意 間二選一有點過于非黑即白。如果用戶給定 5 個查詢詞項,想查找只包含其中 4 個的文檔,該如何處理?將 operator 操作符參數(shù)設(shè)置成 and 只會將此文檔排除。
有時候這正是我們期望的,但在全文搜索的大多數(shù)應(yīng)用場景下,我們既想包含那些可能相關(guān)的文檔,同時又排除那些不太相關(guān)的。換句話說,我們想要處于中間某種結(jié)果。
match 查詢支持 minimum_should_match 最小匹配參數(shù),這讓我們可以指定必須匹配的詞項數(shù)用來表示一個文檔是否相關(guān)。我們可以將其設(shè)置為某個具體數(shù)字,更常用的做法是將其設(shè)置為一個百分數(shù),因為我們無法控制用戶搜索時輸入的單詞數(shù)量:
GET /my_index/my_type/_search
{
"query": {
"match": {
"title": {
"query": "quick brown dog",
"minimum_should_match": "75%"
}
}
}
}
6.3.3 多字段查詢(multi_match)
multi_match與match類似,不同的是它可以在多個字段中查詢
GET /sku/_search
{
"query": {
"multi_match": {
"query": "小米",
"fields": ["name","brandName","categoryName"]
}
}
}
查詢結(jié)果
{
"took" : 14,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0296195,
"hits" : [
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "Ompe7HEBQIJxGG8U13yY",
"_score" : 1.0296195,
"_source" : {
"name" : "小米手機",
"price" : 200000,
"spuId" : 101,
"createTime" : "2020-05-09",
"brandName" : "小米",
"categoryName" : "手機",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "移動4g",
"屏幕尺寸" : "4.5"
}
}
},
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0296195,
"_source" : {
"name" : "小米電視",
"price" : 100000,
"spuId" : 10111,
"createTime" : "2020-05-01",
"brandName" : "小米",
"categoryName" : "電視",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "全網(wǎng)",
"屏幕尺寸" : "56"
}
}
}
]
}
}
查詢字段的模糊匹配
字段名稱可以用模糊匹配的方式給出:任何與模糊模式正則匹配的字段都會被包括在搜索條件中,例如可以使用以下方式同時匹配 book_title 、 chapter_title 和 section_title (書名、章名、節(jié)名)這三個字段:
{
"multi_match": {
"query": "Quick brown fox",
"fields": "*_title"
}
}
提升單個字段的權(quán)重
可以使用 ^ 字符語法為單個字段提升權(quán)重,在字段名稱的末尾添加 ^boost ,其中 boost 是一個浮點數(shù):
{
"multi_match": {
"query": "Quick brown fox",
"fields": [ "*_title", "chapter_title^2" ]
}
}
chapter_title 這個字段的 boost 值為 2 ,而其他兩個字段 book_title 和 section_title 字段的默認 boost 值為 1 。
6.3.4 詞條查詢(term)
term查詢被用于精確匹配,這些精確值可能是數(shù)字、時間、布爾或者那些未分詞的字符串。
GET /sku/_search
{
"query": {
"term": {
"price": 200000
}
}
}
6.3.5 多詞條查詢(terms)
terms查詢和term查詢一樣,但它允許你指定多值進行匹配。如果這個詞段包含了指定中的任何一個值,那么這個文檔滿足條件(類似于mysql中的in)。
GET /sku/_search
{
"query": {
"terms": {
"price": [200000,100000]
}
}
}
6.3.6 布爾組合(bool)
bool把各種其他查詢通過must(與)、must_not(非)、should(或)的方式進行組合。
示例:查詢名稱包含手機的,并且品牌為小米的記錄
GET /sku/_search
{
"query": {
"bool": {
"must": [
{"match": {
"name": "手機"
}},
{
"term": {
"brandName": {
"value": "小米"
}
}
}
]
}
}
}
示例:查詢名稱包含手機的,或者品牌為小米的記錄
GET /sku/_search
{
"query": {
"bool": {
"should": [
{"match": {
"name": "手機"
}},
{
"term": {
"brandName": {
"value": "小米"
}
}
}
]
}
}
}
6.3.7 過濾查詢
過濾是針對搜索的結(jié)果進行過濾,過濾器主要判斷的是文檔是否匹配,不去計算和判斷文檔的匹配度得分,所以過濾器性能比查詢要高,且方便緩存,推薦盡量使用過濾器去實現(xiàn)查詢或者過濾器和查詢共同使用。
示例:過濾品牌為小米的記錄
GET /sku/_search
{
"query": {
"bool": {
"filter": [
{"match":{
"brandName":"小米"
}}
]
}
}
}
查詢結(jié)果(注意_score為0)
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.0,
"hits" : [
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "Ompe7HEBQIJxGG8U13yY",
"_score" : 0.0,
"_source" : {
"name" : "小米手機",
"price" : 200000,
"spuId" : 101,
"createTime" : "2020-05-09",
"brandName" : "小米",
"categoryName" : "手機",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "移動4g",
"屏幕尺寸" : "4.5"
}
}
},
{
"_index" : "sku",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.0,
"_source" : {
"name" : "小米電視",
"price" : 100000,
"spuId" : 10111,
"createTime" : "2020-05-01",
"brandName" : "小米",
"categoryName" : "電視",
"saleNum" : 10012,
"commentNum" : 323,
"spec" : {
"網(wǎng)絡(luò)制式" : "全網(wǎng)",
"屏幕尺寸" : "56"
}
}
}
]
}
}
6.3.8 分組查詢
示例:按分組名稱聚合查詢,統(tǒng)計每個分組的數(shù)量。類似mysql中的group by
GET /sku/_search
{
"size": 0,
"aggs": {
"sku_category": {
"terms": {
"field": "categoryName"
}
}
}
}
size為0 不會將數(shù)據(jù)查詢出來,目的是讓查詢更快。查詢結(jié)果如下:
GET /sku/_search
{
"size": 0,
"aggs": {
"sku_category": {
"terms": {
"field": "categoryName"
}
}
}
}
我們可以同時查詢多個分組,如下:
GET /sku/_search
{
"size": 0,
"aggs": {
"sku_category": {
"terms": {
"field": "categoryName"
}
},
"sku_brand": {
"terms": {
"field": "brandName"
}
}
}
}
6.3.9 范圍
在 SQL 中,范圍查詢可以表示為:
SELECT document
FROM products
WHERE price BETWEEN 20 AND 40
Elasticsearch 有 range 查詢,不出所料地,可以用它來查找處于某個范圍內(nèi)的文檔:
"range" : {
"price" : {
"gte" : 20,
"lte" : 40
}
}
range 查詢可同時提供包含(inclusive)和不包含(exclusive)這兩種范圍表達式,可供組合的選項如下:
- gt: > 大于(greater than)
- lt: < 小于(less than)
- gte: >= 大于或等于(greater than or equal to)
- lte: <= 小于或等于(less than or equal to)
下面是一個范圍查詢的例子
GET /my_store/products/_search
{
"query" : {
"constant_score" : {
"filter" : {
"range" : {
"price" : {
"gte" : 20,
"lt" : 40
}
}
}
}
}
}
如果想要范圍無界(比方說 >20 ),只須省略其中一邊的限制:
"range" : {
"price" : {
"gt" : 20
}
}
日期范圍
range 查詢同樣可以應(yīng)用在日期字段上:
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-07 00:00:00"
}
}
當使用它處理日期字段時, range 查詢支持對 日期計算(date math) 進行操作,比方說,如果我們想查找時間戳在過去一小時內(nèi)的所有文檔:
"range" : {
"timestamp" : {
"gt" : "now-1h"
}
}
日期計算還可以被應(yīng)用到某個具體的時間,并非只能是一個像 now 這樣的占位符。只要在某個日期后加上一個雙管符號 (||) 并緊跟一個日期數(shù)學表達式就能做到:
"range" : {
"timestamp" : {
"gt" : "2014-01-01 00:00:00",
"lt" : "2014-01-01 00:00:00||+1M"
}
}
早于 2014 年 1 月 1 日加 1 月(2014 年 2 月 1 日 零時)
6.3.10 處理Null值
參考 處理Null值
存在查詢 exists
POST /my_index/posts/_bulk
{ "index": { "_id": "1" }}
{ "tags" : ["search"] }
{ "index": { "_id": "2" }}
{ "tags" : ["search", "open_source"] }
{ "index": { "_id": "3" }}
{ "other_field" : "some data" }
{ "index": { "_id": "4" }}
{ "tags" : null }
{ "index": { "_id": "5" }}
{ "tags" : ["search", null] }
'
1、tags 字段有 1 個值。
2、tags 字段有 2 個值。
3、tags 字段缺失。
4、tags 字段被置為 null 。
5、tags 字段有 1 個值和 1 個 null 。
我們的目標是找到那些被設(shè)置過標簽字段的文檔,并不關(guān)心標簽的具體內(nèi)容。只要它存在于文檔中即可,用 SQL 的話就是用 IS NOT NULL 非空進行查詢:
SELECT tags
FROM posts
WHERE tags IS NOT NULL
在 Elasticsearch 中,使用 exists 查詢的方式如下:
GET /my_index/posts/_search
{
"query" : {
"constant_score" : {
"filter" : {
"exists" : { "field" : "tags" }
}
}
}
}
這個查詢返回 3 個文檔:
"hits" : [
{
"_id" : "1",
"_score" : 1.0,
"_source" : { "tags" : ["search"] }
},
{
"_id" : "5",
"_score" : 1.0,
"_source" : { "tags" : ["search", null] }
},
{
"_id" : "2",
"_score" : 1.0,
"_source" : { "tags" : ["search", "open source"] }
}
]
盡管文檔 5 有 null 值,但它仍會被命中返回。字段之所以存在,是因為標簽有實際值( search )可以被索引,所以 null 對過濾不會產(chǎn)生任何影響。
顯而易見,只要 tags 字段存在項(term)的文檔都會命中并作為結(jié)果返回,只有 3 和 4 兩個文檔被排除。
缺失查詢 missing
這個 missing 查詢本質(zhì)上與 exists 恰好相反:它返回某個特定 無 值字段的文檔,與以下 SQL 表達的意思類似:
SELECT tags
FROM posts
WHERE tags IS NULL
我們將前面例子中 exists 查詢換成 missing 查詢:
GET /my_index/posts/_search
{
"query" : {
"constant_score" : {
"filter": {
"missing" : { "field" : "tags" }
}
}
}
}
按照期望的那樣,我們得到 3 和 4 兩個文檔(這兩個文檔的 tags 字段沒有實際值):
"hits" : [
{
"_id" : "3",
"_score" : 1.0,
"_source" : { "other_field" : "some data" }
},
{
"_id" : "4",
"_score" : 1.0,
"_source" : { "tags" : null }
}
]
6.3.11 最佳字段 dis_max
參考 最佳字段
假設(shè)有個網(wǎng)站允許用戶搜索博客的內(nèi)容,以下面兩篇博客內(nèi)容文檔為例:
PUT /my_index/my_type/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
PUT /my_index/my_type/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
用戶輸入詞組 “Brown fox” 然后點擊搜索按鈕。事先,我們并不知道用戶的搜索項是會在 title 還是在 body 字段中被找到,但是,用戶很有可能是想搜索相關(guān)的詞組。用肉眼判斷,文檔 2 的匹配度更高,因為它同時包括要查找的兩個詞:
現(xiàn)在運行以下 bool 查詢:
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
但是我們發(fā)現(xiàn)查詢的結(jié)果是文檔 1 的評分更高:
{
"hits": [
{
"_id": "1",
"_score": 0.14809652,
"_source": {
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
},
{
"_id": "2",
"_score": 0.09256032,
"_source": {
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
}
]
}
為了理解導致這樣的原因,需要回想一下 bool 是如何計算評分的:
1、它會執(zhí)行 should 語句中的兩個查詢。
2、加和兩個查詢的評分。
3、乘以匹配語句的總數(shù)。
4、除以所有語句總數(shù)(這里為:2)。
文檔 1 的兩個字段都包含 brown 這個詞,所以兩個 match 語句都能成功匹配并且有一個評分。文檔 2 的 body 字段同時包含 brown 和 fox 這兩個詞,但 title 字段沒有包含任何詞。這樣, body 查詢結(jié)果中的高分,加上 title 查詢中的 0 分,然后乘以二分之一,就得到比文檔 1 更低的整體評分。
在本例中, title 和 body 字段是相互競爭的關(guān)系,所以就需要找到單個 最佳匹配 的字段。
如果不是簡單將每個字段的評分結(jié)果加在一起,而是將 最佳匹配 字段的評分作為查詢的整體評分,結(jié)果會怎樣?這樣返回的結(jié)果可能是: 同時 包含 brown 和 fox 的單個字段比反復出現(xiàn)相同詞語的多個不同字段有更高的相關(guān)度。
dis_max 查詢
不使用 bool 查詢,可以使用 dis_max 即分離 最大化查詢(Disjunction Max Query) 。分離(Disjunction)的意思是 或(or) ,這與可以把結(jié)合(conjunction)理解成 與(and) 相對應(yīng)。分離最大化查詢(Disjunction Max Query)指的是: 將任何與任一查詢匹配的文檔作為結(jié)果返回,但只將最佳匹配的評分作為查詢的評分結(jié)果返回 :
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
得到我們想要的結(jié)果為:
{
"hits": [
{
"_id": "2",
"_score": 0.21509302,
"_source": {
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
},
{
"_id": "1",
"_score": 0.12713557,
"_source": {
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
}
]
}
最佳字段調(diào)優(yōu)
當用戶搜索 “quick pets” 時會發(fā)生什么呢?在前面的例子中,兩個文檔都包含詞 quick ,但是只有文檔 2 包含詞 pets ,兩個文檔中都不具有同時包含 兩個詞 的 相同字段 。如下,一個簡單的 dis_max 查詢會采用單個最佳匹配字段,而忽略其他的匹配:
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
]
}
}
}
{
"hits": [
{
"_id": "1",
"_score": 0.12713557,
"_source": {
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
},
{
"_id": "2",
"_score": 0.12713557,
"_source": {
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
}
]
}
注意兩個評分是完全相同的。
我們可能期望同時匹配 title 和 body 字段的文檔比只與一個字段匹配的文檔的相關(guān)度更高,但事實并非如此,因為 dis_max 查詢只會簡單地使用 單個 最佳匹配語句的評分 _score 作為整體評分。
tie_breaker參數(shù)
可以通過指定 tie_breaker 這個參數(shù)將其他匹配語句的評分也考慮其中:
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.3
}
}
}
結(jié)果:文檔 2 的相關(guān)度比文檔 1 略高。
tie_breaker 參數(shù)提供了一種 dis_max 和 bool 之間的折中選擇,它的評分方式如下:
- 獲得最佳匹配語句的評分 _score 。
- 將其他匹配語句的評分結(jié)果與 tie_breaker 相乘。
- 對以上評分求和并規(guī)范化。
tie_breaker 可以是 0 到 1 之間的浮點數(shù),其中 0 代表使用 dis_max 最佳匹配語句的普通邏輯, 1 表示所有匹配語句同等重要。最佳的精確值需要根據(jù)數(shù)據(jù)與查詢調(diào)試得出,但是合理值應(yīng)該與零接近(處于 0.1 - 0.4 之間),這樣就不會顛覆 dis_max 最佳匹配性質(zhì)的根本。
七、JavaRest高級客戶端
7.1 JavaRest高級客戶端簡介
Elasticsearch 存在三種Java客戶端
1、Transport Client
2、Java Low Level Rest Client (低級rest客戶端)
3、Java High Level Rest Client (高級rest客戶端)
這三者的區(qū)別是:
1、Transport Client 沒有使用RESTful風格的接口,而是二進制的方式傳輸數(shù)據(jù)。
2、Elasticsearch 官方推出了Java Low Level Rest Client,它支持RESTful。但是缺點是Transport Client的使用者把代碼遷移到Java Low Level Rest Client的工作量比較大。
3、Elasticsearch 官方推出Java High Level Rest Client ,它是基于Java Low Level Rest Client的封裝,并且API接收參數(shù)和返回值和Transport Client是一樣的,使得代碼遷移變得容易并且支持了RESTful的風格,兼容了這兩種客戶端的優(yōu)點。強烈建議ES 5 及其以后的版本使用Java High Level Rest Client。
7.2 正式使用Java High Level Rest Client
準備工作,新建工程,引入依賴
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.2</version>
</dependency>
7.3 新增和修改數(shù)據(jù)
7.3.1 插入單條數(shù)據(jù)
HttpHost:url地址封裝
RestClientBuilder:rest客戶端構(gòu)建器
RestHighLevelClient:rest高級客戶端
IndexRequest:新增或修改請求
IndexResponse:新增或修改的響應(yīng)結(jié)果
import org.apache.http.HttpHost;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author Alan Chen
* @description 新增/修改 數(shù)據(jù)
* @date 2020-05-14
*/
public class Client {
public static void main(String[] args) throws IOException {
// 1、連接rest接口
HttpHost http = new HttpHost("127.0.0.1",9200,"http");
RestClientBuilder restClientBuilder = RestClient.builder(http);
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder);
// 2、封裝請求對象
//如果id不存在則是新增,如果存在則是修改
IndexRequest indexRequest = new IndexRequest("sku").id("1000");
Map skuMap = new HashMap();
skuMap.put("name","華為P30 Pro 新增");
skuMap.put("brandName","華為");
skuMap.put("categoryName","手機");
skuMap.put("price",1010222);
skuMap.put("createTime","2019-05-01");
skuMap.put("saleNum",101022);
skuMap.put("commentNum",1010223);
Map spec = new HashMap();
spec.put("網(wǎng)絡(luò)制式","移動4G");
spec.put("屏幕尺寸","5");
skuMap.put("spec",spec);
indexRequest.source(skuMap);
// 3、獲取執(zhí)行結(jié)果
IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
int status = indexResponse.status().getStatus();
System.out.println(status);
restHighLevelClient.close();
}
}
如果id不存在則是新增,如果存在則是修改。
新增時控制臺打印的status 為201,修改status 為200,在Kibana查詢我們剛才插入的數(shù)據(jù),顯示結(jié)果如下:

7.3.2 批處理請求
BulkRequest:批量請求(用于增刪改操作)
BulkResponse:批量響應(yīng)(用于增刪改操作)
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author Alan Chen
* @description 批處理請求
* @date 2020-05-14
*/
public class Client2 {
public static void main(String[] args) throws IOException {
// 1、連接rest接口
HttpHost http = new HttpHost("127.0.0.1",9200,"http");
RestClientBuilder restClientBuilder = RestClient.builder(http);
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder);
// 2、封裝請求對象
BulkRequest bulkRequest = new BulkRequest();
IndexRequest indexRequest = new IndexRequest("sku").id("1001");
Map skuMap = new HashMap();
skuMap.put("name","華為Mete20 Pro");
skuMap.put("brandName","華為");
skuMap.put("categoryName","手機");
skuMap.put("price",1010222);
skuMap.put("createTime","2019-05-01");
skuMap.put("saleNum",101022);
skuMap.put("commentNum",1010223);
Map spec = new HashMap();
spec.put("網(wǎng)絡(luò)制式","移動4G");
spec.put("屏幕尺寸","5");
skuMap.put("spec",spec);
indexRequest.source(skuMap);
bulkRequest.add(indexRequest);
// 3、獲取執(zhí)行結(jié)果
// IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
int status = bulkResponse.status().getStatus();
System.out.println(status);
restHighLevelClient.close();
}
}
7.4 匹配查詢
SearchRequest:查詢請求對象
SearchResponse:查詢響應(yīng)對象
SearchSourceBuilder:查詢源構(gòu)造器
MatchQueryBuilder:匹配查詢構(gòu)建器
示例:查詢商品名稱包含手機的記錄
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
/**
* @author Alan Chen
* @description 匹配查詢
* @date 2020-05-14
*/
public class Client3 {
public static void main(String[] args) throws IOException {
// 1、連接rest接口
HttpHost http = new HttpHost("127.0.0.1",9200,"http");
RestClientBuilder restClientBuilder = RestClient.builder(http);
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder);
// 2、封裝請求對象
/**
* GET /sku/_search
* {
* "query": {
* "match": {
* "name": "手機"
* }
* }
* }
*/
SearchRequest searchRequest = new SearchRequest("sku");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name","手機");
searchSourceBuilder.query(matchQueryBuilder);
searchRequest.source(searchSourceBuilder);
// 3、獲取執(zhí)行結(jié)果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits().value;
System.out.println("記錄數(shù):"+totalHits);
SearchHit[] hits = searchHits.getHits();
for(SearchHit hit : hits){
String sourceAsString = hit.getSourceAsString();
System.out.println(sourceAsString);
}
restHighLevelClient.close();
}
}
查詢結(jié)果如下:

7.5 布爾與詞條查詢
BoolQueryBuilder:布爾查詢構(gòu)建器
TermQueryBuilder:詞條查詢構(gòu)建器
QueryBuilders:查詢構(gòu)建器工廠
示例:查詢名稱包含手機,并且品牌為小米的記錄
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
/**
* @author Alan Chen
* @description 布爾與詞條查詢
* @date 2020-05-14
*/
public class Client4 {
public static void main(String[] args) throws IOException {
// 1、連接rest接口
HttpHost http = new HttpHost("127.0.0.1",9200,"http");
RestClientBuilder restClientBuilder = RestClient.builder(http);
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder);
// 2、封裝請求對象
/**
* GET /sku/_search
* {
* "query": {
* "bool": {
* "must": [
* {"match": {
* "name": "手機"
* }},
* {
* "term": {
* "brandName": {
* "value": "小米"
* }
* }
* }
* ]
* }
* }
* }
*/
SearchRequest searchRequest = new SearchRequest("sku");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name","手機");
boolQueryBuilder.must(matchQueryBuilder);
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName","小米");
boolQueryBuilder.must(termQueryBuilder);
searchSourceBuilder.query(boolQueryBuilder);
searchRequest.source(searchSourceBuilder);
// 3、獲取執(zhí)行結(jié)果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits().value;
System.out.println("記錄數(shù):"+totalHits);
SearchHit[] hits = searchHits.getHits();
for(SearchHit hit : hits){
String sourceAsString = hit.getSourceAsString();
System.out.println(sourceAsString);
}
restHighLevelClient.close();
}
}
查詢結(jié)果:

7.6 過濾查詢
示例:篩選品牌為小米的記錄
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
/**
* @author Alan Chen
* @description 過濾查詢
* @date 2020-05-14
*/
public class Client5 {
public static void main(String[] args) throws IOException {
// 1、連接rest接口
HttpHost http = new HttpHost("127.0.0.1",9200,"http");
RestClientBuilder restClientBuilder = RestClient.builder(http);
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder);
// 2、封裝請求對象
/**
* GET /sku/_search
* {
* "query": {
* "bool": {
* "filter": [
* {"match":{
* "brandName":"小米"
* }}
* ]
* }
* }
* }
*/
SearchRequest searchRequest = new SearchRequest("sku");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brandName","小米");
boolQueryBuilder.filter(termQueryBuilder);
searchSourceBuilder.query(boolQueryBuilder);
searchRequest.source(searchSourceBuilder);
// 3、獲取執(zhí)行結(jié)果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits().value;
System.out.println("記錄數(shù):"+totalHits);
SearchHit[] hits = searchHits.getHits();
for(SearchHit hit : hits){
String sourceAsString = hit.getSourceAsString();
System.out.println(sourceAsString);
}
restHighLevelClient.close();
}
}
查詢結(jié)果:

7.7 分組(聚合)查詢
AggregationBuilders:聚合構(gòu)建器工廠
TermsAggregationBuilder:詞條聚合構(gòu)建器
Aggregations:分組結(jié)果封裝
Terms.Bucket:桶
示例:按商品分類分組查詢,求出每個分類的文檔數(shù)
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* @author Alan Chen
* @description 分組(聚合)查詢
* @date 2020-05-14
*/
public class Client6 {
public static void main(String[] args) throws IOException {
// 1、連接rest接口
HttpHost http = new HttpHost("127.0.0.1",9200,"http");
RestClientBuilder restClientBuilder = RestClient.builder(http);
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder);
// 2、封裝請求對象
/**
* GET /sku/_search
* {
* "size": 0,
* "aggs": {
* "sku_category": {
* "terms": {
* "field": "categoryName"
* }
* }
* }
* }
*/
SearchRequest searchRequest = new SearchRequest("sku");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("sku_category").field("categoryName");
searchSourceBuilder.aggregation(termsAggregationBuilder);
searchSourceBuilder.size(0);
searchRequest.source(searchSourceBuilder);
// 3、獲取執(zhí)行結(jié)果
SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
Aggregations aggregations = searchResponse.getAggregations();
Map<String, Aggregation> aggregationMap = aggregations.getAsMap();
Terms terms = (Terms) aggregationMap.get("sku_category");
List<? extends Terms.Bucket> buckets = terms.getBuckets();
for(Terms.Bucket bucket : buckets){
System.out.println(bucket.getKeyAsString()+":"+bucket.getDocCount());
}
restHighLevelClient.close();
}
}
查詢結(jié)果:

八、ES文檔 & 驅(qū)動
https://www.elastic.co/cn/downloads/past-releases/jdbc-client-7-6-2
https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html