目錄
一、高級篇
二、面試篇
==============實(shí)用篇==============
day05-Elasticsearch01
1.初識elasticsearch
1.4.安裝es、kibana
1.4.1.部署單點(diǎn)es
1.4.2.部署kibana
1.4.3.安裝IK分詞器
1.4.4.總結(jié)
2.索引庫操作
2.1.mapping映射屬性
2.2.索引庫的CRUD
2.2.1.創(chuàng)建索引庫和映射
2.2.2.查詢索引庫
2.2.3.修改索引庫
2.2.4.刪除索引庫
2.2.5.總結(jié)
3.文檔操作
3.1.新增文檔
3.2.查詢文檔
3.3.刪除文檔
3.4.修改文檔
3.4.1.全量修改
3.4.2.局部修改
3.5.總結(jié)
4.RestAPI
4.0.導(dǎo)入Demo工程
4.0.1.導(dǎo)入數(shù)據(jù)
4.0.2.導(dǎo)入項(xiàng)目
4.0.3.mapping映射分析
4.0.4.初始化RestClient
4.1.創(chuàng)建索引庫
4.1.1.代碼解讀
4.1.2.完整示例
4.2.刪除索引庫
4.3.判斷索引庫是否存在
4.4.總結(jié)
5.RestClient操作文檔
5.1.新增文檔
5.1.1.索引庫實(shí)體類
5.1.2.語法說明
5.1.3.完整代碼
5.2.查詢文檔
5.2.1.語法說明
5.2.2.完整代碼
5.3.刪除文檔
5.4.修改文檔
5.4.1.語法說明
5.4.2.完整代碼
5.5.批量導(dǎo)入文檔
5.5.1.語法說明
5.5.2.完整代碼
5.6.小結(jié)
day06-Elasticsearch02
1.DSL查詢文檔
1.1.DSL查詢分類
1.2.全文檢索查詢
1.2.1.使用場景
1.2.2.基本語法
1.2.3.示例
1.2.4.總結(jié)
1.3.精準(zhǔn)查詢
1.3.1.term查詢
1.3.2.range查詢
1.3.3.總結(jié)
1.4.地理坐標(biāo)查詢
1.4.1.矩形范圍查詢
1.4.2.附近查詢
1.5.復(fù)合查詢
1.5.1.相關(guān)性算分
1.5.2.算分函數(shù)查詢
1.5.3.布爾查詢
2.3.高亮
2.3.1.高亮原理
2.3.2.實(shí)現(xiàn)高亮
2.4.總結(jié)
3.RestClient查詢文檔
3.1.快速入門
3.1.1.發(fā)起查詢請求
3.1.2.解析響應(yīng)
一、高級篇
二、面試篇
==============實(shí)用篇==============
day05-Elasticsearch01
1.初識elasticsearch
1.4.安裝es、kibana
1.4.1.部署單點(diǎn)es
1.1.創(chuàng)建網(wǎng)絡(luò)
因?yàn)槲覀冞€需要部署kibana容器,因此需要讓es和kibana容器互聯(lián)。這里先創(chuàng)建一個(gè)網(wǎng)絡(luò):
docker network create es-net
1.2.加載鏡像
這里我們采用elasticsearch的7.12.1版本的鏡像,這個(gè)鏡像體積非常大,接近1G。不建議大家自己pull。
課前資料提供了鏡像的tar包:
大家將其上傳到虛擬機(jī)中,然后運(yùn)行命令加載即可:
# 導(dǎo)入數(shù)據(jù)
docker load -i es.tar
同理還有kibana
的tar包也需要這樣做。
1.3.運(yùn)行
運(yùn)行docker命令,部署單點(diǎn)es:
docker run -d \ #后臺運(yùn)行的意思 --name es \ #起個(gè)名字 ? -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ #環(huán)境變量:運(yùn)行內(nèi)存 ? -e "discovery.type=single-node" \ #環(huán)境變量:單點(diǎn)模式 ? -v es-data:/usr/share/elasticsearch/data \ #數(shù)據(jù)卷掛載:數(shù)據(jù)保存目錄 ? -v es-plugins:/usr/share/elasticsearch/plugins \ #數(shù)據(jù)卷掛載:數(shù)據(jù)插件目錄 ? --privileged \ ? --network es-net \ #加入到這個(gè)網(wǎng)絡(luò) ? -p 9200:9200 \ #暴露的端口 ? -p 9300:9300 \ #節(jié)點(diǎn)互聯(lián)的端口 elasticsearch:7.12.1 #容器名字
命令解釋:
-
-e "cluster.name=es-docker-cluster"
:設(shè)置集群名稱 -
-e "http.host=0.0.0.0"
:監(jiān)聽的地址,可以外網(wǎng)訪問 -
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:內(nèi)存大小 -
-e "discovery.type=single-node"
:非集群模式 -
-v es-data:/usr/share/elasticsearch/data
:掛載邏輯卷,綁定es的數(shù)據(jù)目錄 -
-v es-logs:/usr/share/elasticsearch/logs
:掛載邏輯卷,綁定es的日志目錄 -
-v es-plugins:/usr/share/elasticsearch/plugins
:掛載邏輯卷,綁定es的插件目錄 -
--privileged
:授予邏輯卷訪問權(quán) -
--network es-net
:加入一個(gè)名為es-net的網(wǎng)絡(luò)中 -
-p 9200:9200
:端口映射配置
在瀏覽器中輸入:http://192.168.150.101:9200 即可看到elasticsearch的響應(yīng)結(jié)果:
1.4.2.部署kibana
kibana可以給我們提供一個(gè)elasticsearch的可視化界面,便于我們學(xué)習(xí)。
2.1.部署
運(yùn)行docker命令,部署kibana
docker run -d \
????????--name kibana \
????????-e ELASTICSEARCH_HOSTS=http://es:9200 \
????????--network=es-net \
????????-p 5601:5601 ?\
kibana:7.12.1? ? ? ? #kibana的版本一定要和es的版本保持一致
-
--network es-net
:加入一個(gè)名為es-net的網(wǎng)絡(luò)中,與elasticsearch在同一個(gè)網(wǎng)絡(luò)中 -
-e ELASTICSEARCH_HOSTS=http://es:9200"
:設(shè)置elasticsearch的地址,因?yàn)閗ibana已經(jīng)與elasticsearch在一個(gè)網(wǎng)絡(luò),因此可以用容器名直接訪問elasticsearch -
-p 5601:5601
:端口映射配置
kibana啟動(dòng)一般比較慢,需要多等待一會,可以通過命令:
docker logs -f kibana
?查看運(yùn)行日志,當(dāng)查看到下面的日志,說明成功:
此時(shí),在瀏覽器輸入地址訪問:http://192.168.150.101:5601,即可看到結(jié)果
2.2.DevTools?
kibana中提供了一個(gè)DevTools界面:
這個(gè)界面中可以編寫DSL來操作elasticsearch。并且對DSL語句有自動(dòng)補(bǔ)全功能。
1.4.3.安裝IK分詞器
3.1.在線安裝ik插件(較慢)
# 進(jìn)入容器內(nèi)部
docker exec -it elasticsearch /bin/bash# 在線下載并安裝
./bin/elasticsearch-plugin ?install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip#退出
exit
#重啟容器
docker restart elasticsearch
3.2.離線安裝ik插件(推薦)
1)查看數(shù)據(jù)卷目錄
安裝插件需要知道elasticsearch的plugins目錄位置,而我們用了數(shù)據(jù)卷掛載,因此需要查看elasticsearch的數(shù)據(jù)卷目錄,通過下面命令查看:
docker volume inspect es-plugins
顯示結(jié)果:
[
? ? {
? ? ? ? "CreatedAt": "2022-05-06T10:06:34+08:00",
? ? ? ? "Driver": "local",
? ? ? ? "Labels": null,
? ? ? ? "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
? ? ? ? "Name": "es-plugins",
? ? ? ? "Options": null,
? ? ? ? "Scope": "local"
? ? }
]
說明plugins目錄被掛載到了:/var/lib/docker/volumes/es-plugins/_data
這個(gè)目錄中。 ?
2)解壓縮分詞器安裝包
下面我們需要把課前資料中的ik分詞器解壓縮,重命名為ik
3)上傳到es容器的插件數(shù)據(jù)卷中
也就是/var/lib/docker/volumes/es-plugins/_data
:
4)重啟容器
?# 4、重啟容器
docker restart es
?# 查看es日志
docker logs -f es
5)測試:
IK分詞器包含兩種模式:
-
ik_smart
:最少切分 -
ik_max_word
:最細(xì)切分
GET /_analyze
{
? "analyzer": "ik_max_word",
? "text": "黑馬程序員學(xué)習(xí)java太棒了"
}
?結(jié)果:
{
"tokens" : [
{
"token" : "黑馬",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "程序員",
"start_offset" : 2,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "程序",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 2
},
{
"token" : "員",
"start_offset" : 4,
"end_offset" : 5,
"type" : "CN_CHAR",
"position" : 3
},
{
"token" : "學(xué)習(xí)",
"start_offset" : 5,
"end_offset" : 7,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "java",
"start_offset" : 7,
"end_offset" : 11,
"type" : "ENGLISH",
"position" : 5
},
{
"token" : "太棒了",
"start_offset" : 11,
"end_offset" : 14,
"type" : "CN_WORD",
"position" : 6
},
{
"token" : "太棒",
"start_offset" : 11,
"end_offset" : 13,
"type" : "CN_WORD",
"position" : 7
},
{
"token" : "了",
"start_offset" : 13,
"end_offset" : 14,
"type" : "CN_CHAR",
"position" : 8
}
]
}
3.3 擴(kuò)展詞詞典
隨著互聯(lián)網(wǎng)的發(fā)展,“造詞運(yùn)動(dòng)”也越發(fā)的頻繁。出現(xiàn)了很多新的詞語,在原有的詞匯列表中并不存在。比如:“奧力給”,“傳智播客” 等。
所以我們的詞匯也需要不斷的更新,IK分詞器提供了擴(kuò)展詞匯的功能。
1)打開IK分詞器config目錄:
2)在IKAnalyzer.cfg.xml配置文件內(nèi)容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴(kuò)展配置</comment>
<!--用戶可以在這里配置自己的擴(kuò)展字典 *** 添加擴(kuò)展詞典-->
<entry key="ext_dict">ext.dic</entry>
</properties>
?3)新建一個(gè) ext.dic,可以參考config目錄下復(fù)制一個(gè)配置文件進(jìn)行修改
傳智播客
奧力給
4)重啟elasticsearch
docker restart es
# 查看 日志
docker logs -f elasticsearch
日志中已經(jīng)成功加載ext.dic配置文件
5)測試效果:
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "傳智播客Java就業(yè)超過90%,奧力給!"
}
3.4 停用詞詞典
在互聯(lián)網(wǎng)項(xiàng)目中,在網(wǎng)絡(luò)間傳輸?shù)乃俣群芸?,所以很多語言是不允許在網(wǎng)絡(luò)上傳遞的,如:關(guān)于宗教、政治等敏感詞語,那么我們在搜索時(shí)也應(yīng)該忽略當(dāng)前詞匯
。
IK分詞器也提供了強(qiáng)大的停用詞功能,讓我們在索引時(shí)就直接忽略當(dāng)前的停用詞匯表中的內(nèi)容。
1)IKAnalyzer.cfg.xml配置文件內(nèi)容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴(kuò)展配置</comment>
<!--用戶可以在這里配置自己的擴(kuò)展字典-->
<entry key="ext_dict">ext.dic</entry>
<!--用戶可以在這里配置自己的擴(kuò)展停止詞字典 *** 添加停用詞詞典-->
<entry key="ext_stopwords">stopword.dic</entry>
</properties>
3)在 stopword.dic 添加停用詞
奧里給
4)重啟elasticsearch
# 重啟服務(wù)
docker restart elasticsearch
docker restart kibana
# 查看 日志
docker logs -f elasticsearch
日志中已經(jīng)成功加載stopword.dic配置文件
5)測試效果:
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "冬泳怪鴿子,奧力給!"
}
1.4.4.總結(jié)
分詞器的作用是什么?
-
創(chuàng)建倒排索引時(shí)對文檔分詞
-
用戶搜索時(shí),對輸入的內(nèi)容分詞
IK分詞器有幾種模式?
-
ik_smart:智能切分,粗粒度
-
ik_max_word:最細(xì)切分,細(xì)粒度
IK分詞器如何拓展詞條?如何停用詞條?
-
利用config目錄的IkAnalyzer.cfg.xml文件添加拓展詞典和停用詞典
-
在詞典中添加拓展詞條或者停用詞條
2.索引庫操作
索引庫就類似數(shù)據(jù)庫表,mapping映射就類似表的結(jié)構(gòu)。
我們要向es中存儲數(shù)據(jù),必須先創(chuàng)建“庫”和“表”。
2.1.mapping映射屬性
mapping是對索引庫中文檔的約束,常見的mapping屬性包括:
-
type:字段數(shù)據(jù)類型,常見的簡單類型有:
-
字符串:text(可分詞的文本)、keyword(精確值,例如:品牌、國家、ip地址)
-
數(shù)值:long、integer、short、byte、double、float、
-
布爾:boolean
-
日期:date
-
對象:object
-
-
index:是否創(chuàng)建索引,默認(rèn)為true
-
analyzer:使用哪種分詞器
-
properties:該字段的子字段
例如下面的json文檔:
{
????"age":?21,
????"weight":?52.1,
????"isMarried":?false,
????"info":?"黑馬程序員Java講師",
"email":?"zy@itcast.cn",
"score":?[99.1, 99.5, 98.9],
????"name":?{
????????"firstName":?"云",
????????"lastName":?"趙"
????}
}
對應(yīng)的每個(gè)字段映射(mapping):
-
age:類型為 integer;參與搜索,因此需要index為true;無需分詞器
-
weight:類型為float;參與搜索,因此需要index為true;無需分詞器
-
isMarried:類型為boolean;參與搜索,因此需要index為true;無需分詞器
-
info:類型為字符串,需要分詞,因此是text;參與搜索,因此需要index為true;分詞器可以用ik_smart
-
email:類型為字符串,但是不需要分詞,因此是keyword;不參與搜索,因此需要index為false;無需分詞器
-
score:雖然是數(shù)組,但是我們只看元素的類型,類型為float;參與搜索,因此需要index為true;無需分詞器
-
name:類型為object,需要定義多個(gè)子屬性
-
name.firstName;類型為字符串,但是不需要分詞,因此是keyword;參與搜索,因此需要index為true;無需分詞器
-
name.lastName;類型為字符串,但是不需要分詞,因此是keyword;參與搜索,因此需要index為true;無需分詞器
-
2.2.索引庫的CRUD
這里我們統(tǒng)一使用Kibana編寫DSL的方式來演示。
2.2.1.創(chuàng)建索引庫和映射
基本語法:
-
請求方式:PUT
-
請求路徑:/索引庫名,可以自定義
-
請求參數(shù):mapping映射
格式:
PUT?/索引庫名稱
{
??"mappings":?{
????"properties":?{
??????"字段名":{
????????"type":?"text",
????????"analyzer":?"ik_smart"
??????},
??????"字段名2":{
????????"type":?"keyword",
????????"index":?"false"
??????},
??????"字段名3":{
????????"properties":?{
??????????"子字段":?{
????????????"type":?"keyword"
??????????}
????????}
??????},
// ...略
????}
??}
}
示例:
PUT?/heima
{
??"mappings":?{
????"properties":?{
??????"info":{
????????"type":?"text",
????????"analyzer":?"ik_smart"
??????},
??????"email":{
????????"type":?"keyword",
????????"index":?"falsae"
??????},
??????"name":{
????????"properties":?{
??????????"firstName":?{
????????????"type":?"keyword"
??????????}
????????}
??????},
// ... 略
????}
??}
}
2.2.2.查詢索引庫
基本語法:
-
請求方式:GET
-
請求路徑:/索引庫名
-
請求參數(shù):無
格式:
GET /索引庫名
?
2.2.3.修改索引庫
倒排索引結(jié)構(gòu)雖然不復(fù)雜,但是一旦數(shù)據(jù)結(jié)構(gòu)改變(比如改變了分詞器),就需要重新創(chuàng)建倒排索引,這簡直是災(zāi)難。因此索引庫一旦創(chuàng)建,無法修改mapping。
雖然無法修改mapping中已有的字段,但是卻允許添加新的字段到mapping中,因?yàn)椴粫Φ古潘饕a(chǎn)生影響。
語法說明:
PUT?/索引庫名/_mapping
{
??"properties":?{
????"新字段名":{
??????"type":?"integer"
????}
??}
}
2.2.4.刪除索引庫
語法:
-
請求方式:DELETE
-
請求路徑:/索引庫名
-
請求參數(shù):無
格式:
DELETE /索引庫名
在kibana中測試: ?
2.2.5.總結(jié)
索引庫操作有哪些?
-
創(chuàng)建索引庫:PUT /索引庫名
-
查詢索引庫:GET /索引庫名
-
刪除索引庫:DELETE /索引庫名
-
添加字段:PUT /索引庫名/_mapping
3.文檔操作
3.1.新增文檔
語法:
POST?/索引庫名/_doc/文檔id
{
????"字段1":?"值1",
????"字段2":?"值2",
????"字段3":?{
????????"子屬性1":?"值3",
????????"子屬性2":?"值4"
????},
? ? // ...
}
示例:
POST?/heima/_doc/1
{
????"info":?"黑馬程序員Java講師",
????"email":?"zy@itcast.cn",
????"name":?{
????????"firstName":?"云",
????????"lastName":?"趙"
????}
}
響應(yīng):
3.2.查詢文檔
根據(jù)rest風(fēng)格,新增是post,查詢應(yīng)該是get,不過查詢一般都需要條件,這里我們把文檔id帶上。
語法:
GET /{索引庫名稱}/_doc/{id}
通過kibana查看數(shù)據(jù):
?GET /heima/_doc/1
查看結(jié)果: ?
3.3.刪除文檔
刪除使用DELETE請求,同樣,需要根據(jù)id進(jìn)行刪除:
語法:
DELETE /{索引庫名}/_doc/id值
示例:
# 根據(jù)id刪除數(shù)據(jù)
DELETE /heima/_doc/1
結(jié)果:
3.4.修改文檔
修改有兩種方式:
-
全量修改:直接覆蓋原來的文檔
-
增量修改:修改文檔中的部分字段
3.4.1.全量修改
全量修改是覆蓋原來的文檔,其本質(zhì)是:
-
根據(jù)指定的id刪除文檔
-
新增一個(gè)相同id的文檔
注意:如果根據(jù)id刪除時(shí),id不存在,第二步的新增也會執(zhí)行,也就從修改變成了新增操作了。
語法:
PUT?/{索引庫名}/_doc/文檔id
{
????"字段1":?"值1",
????"字段2":?"值2",
// ... 略
}
3.4.2.局部修改
示例:
POST?/heima/_update/1
{
??"doc":?{
????"email":?"ZhaoYun@itcast.cn"
??}
}
3.5.總結(jié)
文檔操作有哪些?
-
創(chuàng)建文檔:POST /{索引庫名}/_doc/文檔id { json文檔 }
-
查詢文檔:GET /{索引庫名}/_doc/文檔id
-
刪除文檔:DELETE /{索引庫名}/_doc/文檔id
-
修改文檔:
-
全量修改:PUT /{索引庫名}/_doc/文檔id { json文檔 }
-
增量修改:POST /{索引庫名}/_update/文檔id { "doc": {字段}}
-
4.RestAPI
ES官方提供了各種不同語言的客戶端,用來操作ES。這些客戶端的本質(zhì)就是組裝DSL語句,通過http請求發(fā)送給ES。官方文檔地址:Elasticsearch Clients | Elastic
其中的Java Rest Client又包括兩種:
-
Java Low Level Rest Client
-
Java High Level Rest Client
我們學(xué)習(xí)的是Java HighLevel Rest Client客戶端API
4.0.導(dǎo)入Demo工程
4.0.1.導(dǎo)入數(shù)據(jù)
首先導(dǎo)入課前資料提供的數(shù)據(jù)庫數(shù)據(jù):
?數(shù)據(jù)結(jié)構(gòu)如下:
CREATE?TABLE?`tb_hotel`?(
??`id`?bigint(20)?NOT?NULL?COMMENT?'酒店id',
??`name`?varchar(255)?NOT?NULL?COMMENT?'酒店名稱;例:7天酒店',
??`address`?varchar(255)?NOT?NULL?COMMENT?'酒店地址;例:航頭路',
??`price`?int(10)?NOT?NULL?COMMENT?'酒店價(jià)格;例:329',
??`score`?int(2)?NOT?NULL?COMMENT?'酒店評分;例:45,就是4.5分',
??`brand`?varchar(32)?NOT?NULL?COMMENT?'酒店品牌;例:如家',
??`city`?varchar(32)?NOT?NULL?COMMENT?'所在城市;例:上海',
??`star_name`?varchar(16)?DEFAULT?NULL?COMMENT?'酒店星級,從低到高分別是:1星到5星,1鉆到5鉆',
??`business`?varchar(255)?DEFAULT?NULL?COMMENT?'商圈;例:虹橋',
??`latitude`?varchar(32)?NOT?NULL?COMMENT?'緯度;例:31.2497',
??`longitude`?varchar(32)?NOT?NULL?COMMENT?'經(jīng)度;例:120.3925',
??`pic`?varchar(255)?DEFAULT?NULL?COMMENT?'酒店圖片;例:/img/1.jpg',
??PRIMARY?KEY?(`id`)
)?ENGINE=InnoDB?DEFAULT?CHARSET=utf8mb4;
4.0.2.導(dǎo)入項(xiàng)目
然后導(dǎo)入課前資料提供的項(xiàng)目:
?項(xiàng)目結(jié)構(gòu)如圖:
4.0.3.mapping映射分析
創(chuàng)建索引庫,最關(guān)鍵的是mapping映射,而mapping映射要考慮的信息包括:
-
字段名
-
字段數(shù)據(jù)類型
-
是否參與搜索
-
是否需要分詞
-
如果分詞,分詞器是什么?
其中:
-
字段名、字段數(shù)據(jù)類型,可以參考數(shù)據(jù)表結(jié)構(gòu)的名稱和類型
-
是否參與搜索要分析業(yè)務(wù)來判斷,例如圖片地址,就無需參與搜索
-
是否分詞呢要看內(nèi)容,內(nèi)容如果是一個(gè)整體就無需分詞,反之則要分詞
-
分詞器,我們可以統(tǒng)一使用ik_max_word
來看下酒店數(shù)據(jù)的索引庫結(jié)構(gòu):
PUT /hotel
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
幾個(gè)特殊字段說明:
-
location:地理坐標(biāo),里面包含精度、緯度
-
all:一個(gè)組合字段,其目的是將多字段的值 利用copy_to合并,提供給用戶搜索
地理坐標(biāo)說明:
?copy_to說明:
4.0.4.初始化RestClient
在elasticsearch提供的API中,與elasticsearch一切交互都封裝在一個(gè)名為RestHighLevelClient的類中,必須先完成這個(gè)對象的初始化,建立與elasticsearch的連接。
分為三步:
1)引入es的RestHighLevelClient依賴:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2)因?yàn)镾pringBoot默認(rèn)的ES版本是7.6.2,所以我們需要覆蓋默認(rèn)的ES版本:
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
3)初始化RestHighLevelClient:
初始化的代碼如下:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
這里為了單元測試方便,我們創(chuàng)建一個(gè)測試類HotelIndexTest,然后將初始化的代碼編寫在@BeforeEach方法中:
package cn.itcast.hotel;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class HotelIndexTest {
private RestHighLevelClient client;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}
4.1.創(chuàng)建索引庫
4.1.1.代碼解讀
創(chuàng)建索引庫的API如下:
代碼分為三步:
-
1)創(chuàng)建Request對象。因?yàn)槭莿?chuàng)建索引庫的操作,因此Request是CreateIndexRequest。
-
2)添加請求參數(shù),其實(shí)就是DSL的JSON參數(shù)部分。因?yàn)閖son字符串很長,這里是定義了靜態(tài)字符串常量MAPPING_TEMPLATE,讓代碼看起來更加優(yōu)雅。
-
3)發(fā)送請求,client.indices()方法的返回值是IndicesClient類型,封裝了所有與索引庫操作有關(guān)的方法。
4.1.2.完整示例
在hotel-demo的cn.itcast.hotel.constants包下,創(chuàng)建一個(gè)類,定義mapping映射的JSON字符串常量:
package cn.itcast.hotel.constants;
public class HotelConstants {
public static final String MAPPING_TEMPLATE = "{\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"id\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"address\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"price\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"score\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"brand\":{\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"city\":{\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"starName\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"business\":{\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"location\":{\n" +
" \"type\": \"geo_point\"\n" +
" },\n" +
" \"pic\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"all\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}
在hotel-demo中的HotelIndexTest測試類中,編寫單元測試,實(shí)現(xiàn)創(chuàng)建索引:
@Test
void createHotelIndex() throws IOException {
// 1.創(chuàng)建Request對象
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.準(zhǔn)備請求的參數(shù):DSL語句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.發(fā)送請求
client.indices().create(request, RequestOptions.DEFAULT);
}
4.2.刪除索引庫
刪除索引庫的DSL語句非常簡單:
DELETE /hotel
與創(chuàng)建索引庫相比:
-
請求方式從PUT變?yōu)镈ELTE
-
請求路徑不變
-
無請求參數(shù)
所以代碼的差異,注意體現(xiàn)在Request對象上。依然是三步走:
-
1)創(chuàng)建Request對象。這次是DeleteIndexRequest對象
-
2)準(zhǔn)備參數(shù)。這里是無參
-
3)發(fā)送請求。改用delete方法
在hotel-demo中的HotelIndexTest測試類中,編寫單元測試,實(shí)現(xiàn)刪除索引:
@Test
void testDeleteHotelIndex() throws IOException {
// 1.創(chuàng)建Request對象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.發(fā)送請求
client.indices().delete(request, RequestOptions.DEFAULT);
}
4.3.判斷索引庫是否存在
判斷索引庫是否存在,本質(zhì)就是查詢,對應(yīng)的DSL是:
GET /hotel
因此與刪除的Java代碼流程是類似的。依然是三步走:
-
1)創(chuàng)建Request對象。這次是GetIndexRequest對象
-
2)準(zhǔn)備參數(shù)。這里是無參
-
3)發(fā)送請求。改用exists方法
@Test
void testExistsHotelIndex() throws IOException {
// 1.創(chuàng)建Request對象
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.發(fā)送請求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.輸出
System.err.println(exists ? "索引庫已經(jīng)存在!" : "索引庫不存在!");
}
4.4.總結(jié)
JavaRestClient操作elasticsearch的流程基本類似。核心是client.indices()方法來獲取索引庫的操作對象。
索引庫操作的基本步驟:
-
初始化RestHighLevelClient
-
創(chuàng)建XxxIndexRequest。XXX是Create、Get、Delete
-
準(zhǔn)備DSL( Create時(shí)需要,其它是無參)
-
發(fā)送請求。調(diào)用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete
5.RestClient操作文檔
為了與索引庫操作分離,我們再次參加一個(gè)測試類,做兩件事情:
-
初始化RestHighLevelClient
-
我們的酒店數(shù)據(jù)在數(shù)據(jù)庫,需要利用IHotelService去查詢,所以注入這個(gè)接口
package cn.itcast.hotel;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.service.IHotelService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.List;
@SpringBootTest
public class HotelDocumentTest {
@Autowired
private IHotelService hotelService;
private RestHighLevelClient client;
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.150.101:9200")
));
}
@AfterEach
void tearDown() throws IOException {
this.client.close();
}
}
5.1.新增文檔
我們要將數(shù)據(jù)庫的酒店數(shù)據(jù)查詢出來,寫入elasticsearch中。
5.1.1.索引庫實(shí)體類
數(shù)據(jù)庫查詢后的結(jié)果是一個(gè)Hotel類型的對象。結(jié)構(gòu)如下:
@Data
@TableName("tb_hotel")
public class Hotel {
@TableId(type = IdType.INPUT)
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String longitude;
private String latitude;
private String pic;
}
與我們的索引庫結(jié)構(gòu)存在差異:
-
longitude和latitude需要合并為location
因此,我們需要定義一個(gè)新的類型,與索引庫結(jié)構(gòu)吻合:
package cn.itcast.hotel.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
this.pic = hotel.getPic();
}
}
5.1.2.語法說明
新增文檔的DSL語句如下:
POST /{索引庫名}/_doc/1
{
"name": "Jack",
"age": 21
}
對應(yīng)的java代碼如圖:
可以看到與創(chuàng)建索引庫類似,同樣是三步走:
-
1)創(chuàng)建Request對象
-
2)準(zhǔn)備請求參數(shù),也就是DSL中的JSON文檔
-
3)發(fā)送請求
變化的地方在于,這里直接使用client.xxx()的API,不再需要client.indices()了。
5.1.3.完整代碼
我們導(dǎo)入酒店數(shù)據(jù),基本流程一致,但是需要考慮幾點(diǎn)變化:
-
酒店數(shù)據(jù)來自于數(shù)據(jù)庫,我們需要先查詢出來,得到hotel對象
-
hotel對象需要轉(zhuǎn)為HotelDoc對象
-
HotelDoc需要序列化為json格式
因此,代碼整體步驟如下:
-
1)根據(jù)id查詢酒店數(shù)據(jù)Hotel
-
2)將Hotel封裝為HotelDoc
-
3)將HotelDoc序列化為JSON
-
4)創(chuàng)建IndexRequest,指定索引庫名和id
-
5)準(zhǔn)備請求參數(shù),也就是JSON文檔
-
6)發(fā)送請求
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testAddDocument() throws IOException {
// 1.根據(jù)id查詢酒店數(shù)據(jù)
Hotel hotel = hotelService.getById(61083L);
// 2.轉(zhuǎn)換為文檔類型
HotelDoc hotelDoc = new HotelDoc(hotel);
// 3.將HotelDoc轉(zhuǎn)json
String json = JSON.toJSONString(hotelDoc);
// 1.準(zhǔn)備Request對象
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
// 2.準(zhǔn)備Json文檔
request.source(json, XContentType.JSON);
// 3.發(fā)送請求
client.index(request, RequestOptions.DEFAULT);
}
5.2.查詢文檔
5.2.1.語法說明
查詢的DSL語句如下:
GET /hotel/_doc/{id}
非常簡單,因此代碼大概分兩步:
-
準(zhǔn)備Request對象
-
發(fā)送請求
不過查詢的目的是得到結(jié)果,解析為HotelDoc,因此難點(diǎn)是結(jié)果的解析。完整代碼如下:
?
可以看到,結(jié)果是一個(gè)JSON,其中文檔放在一個(gè)_source
屬性中,因此解析就是拿到_source
,反序列化為Java對象即可。
與之前類似,也是三步走:
-
1)準(zhǔn)備Request對象。這次是查詢,所以是GetRequest
-
2)發(fā)送請求,得到結(jié)果。因?yàn)槭遣樵?,這里調(diào)用client.get()方法
-
3)解析結(jié)果,就是對JSON做反序列化
5.2.2.完整代碼
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testGetDocumentById() throws IOException {
// 1.準(zhǔn)備Request
GetRequest request = new GetRequest("hotel", "61082");
// 2.發(fā)送請求,得到響應(yīng)
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.解析響應(yīng)結(jié)果
String json = response.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
5.3.刪除文檔
刪除的DSL為是這樣的:
DELETE /hotel/_doc/{id}
與查詢相比,僅僅是請求方式從DELETE變成GET,可以想象Java代碼應(yīng)該依然是三步走:
-
1)準(zhǔn)備Request對象,因?yàn)槭莿h除,這次是DeleteRequest對象。要指定索引庫名和id
-
2)準(zhǔn)備參數(shù),無參
-
3)發(fā)送請求。因?yàn)槭莿h除,所以是client.delete()方法
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testDeleteDocument() throws IOException {
// 1.準(zhǔn)備Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.發(fā)送請求
client.delete(request, RequestOptions.DEFAULT);
}
5.4.修改文檔
5.4.1.語法說明
修改我們講過兩種方式:
-
全量修改:本質(zhì)是先根據(jù)id刪除,再新增
-
增量修改:修改文檔中的指定字段值
在RestClient的API中,全量修改與新增的API完全一致,判斷依據(jù)是ID:
-
如果新增時(shí),ID已經(jīng)存在,則修改
-
如果新增時(shí),ID不存在,則新增
這里不再贅述,我們主要關(guān)注增量修改。
代碼示例如圖:
與之前類似,也是三步走:
-
1)準(zhǔn)備Request對象。這次是修改,所以是UpdateRequest
-
2)準(zhǔn)備參數(shù)。也就是JSON文檔,里面包含要修改的字段
-
3)更新文檔。這里調(diào)用client.update()方法
5.4.2.完整代碼
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testUpdateDocument() throws IOException {
// 1.準(zhǔn)備Request
UpdateRequest request = new UpdateRequest("hotel", "61083");
// 2.準(zhǔn)備請求參數(shù)
request.doc(
"price", "952",
"starName", "四鉆"
);
// 3.發(fā)送請求
client.update(request, RequestOptions.DEFAULT);
}
5.5.批量導(dǎo)入文檔
案例需求:利用BulkRequest批量將數(shù)據(jù)庫數(shù)據(jù)導(dǎo)入到索引庫中。
步驟如下:
-
利用mybatis-plus查詢酒店數(shù)據(jù)
-
將查詢到的酒店數(shù)據(jù)(Hotel)轉(zhuǎn)換為文檔類型數(shù)據(jù)(HotelDoc)
-
利用JavaRestClient中的BulkRequest批處理,實(shí)現(xiàn)批量新增文檔
5.5.1.語法說明
批量處理BulkRequest,其本質(zhì)就是將多個(gè)普通的CRUD請求組合在一起發(fā)送。
其中提供了一個(gè)add方法,用來添加其他請求:
可以看到,能添加的請求包括:
-
IndexRequest,也就是新增
-
UpdateRequest,也就是修改
-
DeleteRequest,也就是刪除
因此Bulk中添加了多個(gè)IndexRequest,就是批量新增功能了。示例:
其實(shí)還是三步走:
-
1)創(chuàng)建Request對象。這里是BulkRequest
-
2)準(zhǔn)備參數(shù)。批處理的參數(shù),就是其它Request對象,這里就是多個(gè)IndexRequest
-
3)發(fā)起請求。這里是批處理,調(diào)用的方法為client.bulk()方法
我們在導(dǎo)入酒店數(shù)據(jù)時(shí),將上述代碼改造成for循環(huán)處理即可。
5.5.2.完整代碼
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testBulkRequest() throws IOException {
// 批量查詢酒店數(shù)據(jù)
List<Hotel> hotels = hotelService.list();
// 1.創(chuàng)建Request
BulkRequest request = new BulkRequest();
// 2.準(zhǔn)備參數(shù),添加多個(gè)新增的Request
for (Hotel hotel : hotels) {
// 2.1.轉(zhuǎn)換為文檔類型HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
// 2.2.創(chuàng)建新增文檔的Request對象
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
// 3.發(fā)送請求
client.bulk(request, RequestOptions.DEFAULT);
}
5.6.小結(jié)
文檔操作的基本步驟:
-
初始化RestHighLevelClient
-
創(chuàng)建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
-
準(zhǔn)備參數(shù)(Index、Update、Bulk時(shí)需要)
-
發(fā)送請求。調(diào)用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
-
解析結(jié)果(Get時(shí)需要)
day06-Elasticsearch02
1.DSL查詢文檔
elasticsearch的查詢依然是基于JSON風(fēng)格的DSL來實(shí)現(xiàn)的。
1.1.DSL查詢分類
Elasticsearch提供了基于JSON的DSL(Domain Specific Language)來定義查詢。常見的查詢類型包括:
-
查詢所有:查詢出所有數(shù)據(jù),一般測試用。例如:match_all
-
全文檢索(full text)查詢:利用分詞器對用戶輸入內(nèi)容分詞,然后去倒排索引庫中匹配。例如:
-
match_query
-
multi_match_query
-
-
精確查詢:根據(jù)精確詞條值查找數(shù)據(jù),一般是查找keyword、數(shù)值、日期、boolean等類型字段。例如:
-
ids
-
range
-
term
-
-
地理(geo)查詢:根據(jù)經(jīng)緯度查詢。例如:
-
geo_distance
-
geo_bounding_box
-
-
復(fù)合(compound)查詢:復(fù)合查詢可以將上述各種查詢條件組合起來,合并查詢條件。例如:
-
bool
-
function_score
-
查詢的語法基本一致:
GET?/indexName/_search
{
??"query":?{
????"查詢類型":?{
??????"查詢條件":?"條件值"
????}
??}
}
我們以查詢所有為例,其中:
-
查詢類型為match_all
-
沒有查詢條件
// 查詢所有
GET?/indexName/_search
{
??"query":?{
????"match_all":?{
}
??}
}
其它查詢無非就是查詢類型、查詢條件的變化。
1.2.全文檢索查詢
1.2.1.使用場景
全文檢索查詢的基本流程如下:
-
對用戶搜索的內(nèi)容做分詞,得到詞條
-
根據(jù)詞條去倒排索引庫中匹配,得到文檔id
-
根據(jù)文檔id找到文檔,返回給用戶
比較常用的場景包括:
-
商城的輸入框搜索
-
百度輸入框搜索
例如京東:
?因?yàn)槭悄弥~條去匹配,因此參與搜索的字段也必須是可分詞的text類型的字段
1.2.2.基本語法
常見的全文檢索查詢包括:
-
match查詢:單字段查詢
-
multi_match查詢:多字段查詢,任意一個(gè)字段符合條件就算符合查詢條件
match查詢語法如下:
GET?/indexName/_search
{
??"query":?{
????"match":?{
??????"FIELD":?"TEXT"
????}
??}
}
mulit_match語法如下:
GET?/indexName/_search
{
??"query":?{
????"multi_match":?{
??????"query":?"TEXT",
??????"fields":?["FIELD1",?" FIELD12"]
????}
??}
}
1.2.3.示例
match查詢示例:
?multi_match查詢示例:
可以看到,兩種查詢結(jié)果是一樣的,為什么?
因?yàn)槲覀儗rand、name、business值都利用copy_to復(fù)制到了all字段中。因此你根據(jù)三個(gè)字段搜索,和根據(jù)all字段搜索效果當(dāng)然一樣了。
但是,搜索字段越多,對查詢性能影響越大,因此建議采用copy_to,然后單字段查詢的方式。
1.2.4.總結(jié)
match和multi_match的區(qū)別是什么?
-
match:根據(jù)一個(gè)字段查詢
-
multi_match:根據(jù)多個(gè)字段查詢,參與查詢字段越多,查詢性能越差
1.3.精準(zhǔn)查詢
精確查詢一般是查找keyword、數(shù)值、日期、boolean等類型字段。所以不會對搜索條件分詞。常見的有:
-
term:根據(jù)詞條精確值查詢
-
range:根據(jù)值的范圍查詢
1.3.1.term查詢
因?yàn)榫_查詢的字段搜是不分詞的字段,因此查詢的條件也必須是不分詞的詞條。查詢時(shí),用戶輸入的內(nèi)容跟自動(dòng)值完全匹配時(shí)才認(rèn)為符合條件。如果用戶輸入的內(nèi)容過多,反而搜索不到數(shù)據(jù)。
語法說明:
//?term查詢
GET?/indexName/_search
{
??"query":?{
????"term":?{
??????"FIELD":?{
????????"value":?"VALUE"
??????}
????}
??}
}
示例:
當(dāng)我搜索的是精確詞條時(shí),能正確查詢出結(jié)果:
?但是,當(dāng)我搜索的內(nèi)容不是詞條,而是多個(gè)詞語形成的短語時(shí),反而搜索不到:
1.3.2.range查詢
范圍查詢,一般應(yīng)用在對數(shù)值類型做范圍過濾的時(shí)候。比如做價(jià)格范圍過濾。
基本語法:
//?range查詢
GET?/indexName/_search
{
??"query":?{
????"range":?{
??????"FIELD":?{
????????"gte":?10, // 這里的gte代表大于等于,gt則代表大于
????????"lte":?20 // lte代表小于等于,lt則代表小于
??????}
????}
??}
}
示例:
1.3.3.總結(jié)
精確查詢常見的有哪些?
-
term查詢:根據(jù)詞條精確匹配,一般搜索keyword類型、數(shù)值類型、布爾類型、日期類型字段
-
range查詢:根據(jù)數(shù)值范圍查詢,可以是數(shù)值、日期的范圍
1.4.地理坐標(biāo)查詢
所謂的地理坐標(biāo)查詢,其實(shí)就是根據(jù)經(jīng)緯度查詢,官方文檔:Geo queries | Elasticsearch Guide [8.6] | Elastic
常見的使用場景包括:
-
攜程:搜索我附近的酒店
-
滴滴:搜索我附近的出租車
-
微信:搜索我附近的人
附近的酒店:
?附近的車:
1.4.1.矩形范圍查詢
矩形范圍查詢,也就是geo_bounding_box查詢,查詢坐標(biāo)落在某個(gè)矩形范圍的所有文檔:
查詢時(shí),需要指定矩形的左上、右下兩個(gè)點(diǎn)的坐標(biāo),然后畫出一個(gè)矩形,落在該矩形內(nèi)的都是符合條件的點(diǎn)。
語法如下:
//?geo_bounding_box查詢
GET?/indexName/_search
{
??"query":?{
????"geo_bounding_box":?{
??????"FIELD":?{
????????"top_left":?{ // 左上點(diǎn)
??????????"lat":?31.1,
??????????"lon":?121.5
????????},
????????"bottom_right":?{ // 右下點(diǎn)
??????????"lat":?30.9,
??????????"lon":?121.7
????????}
??????}
????}
??}
}
這種并不符合“附近的人”這樣的需求,所以我們就不做了。
1.4.2.附近查詢
附近查詢,也叫做距離查詢(geo_distance):查詢到指定中心點(diǎn)小于某個(gè)距離值的所有文檔。
換句話來說,在地圖上找一個(gè)點(diǎn)作為圓心,以指定距離為半徑,畫一個(gè)圓,落在圓內(nèi)的坐標(biāo)都算符合條件:
?語法說明:
//?geo_distance 查詢
GET?/indexName/_search
{
??"query":?{
????"geo_distance":?{
??????"distance":?"15km", // 半徑
??????"FIELD":?"31.21,121.5" // 圓心
????}
??}
}
示例:
我們先搜索陸家嘴附近15km的酒店:
發(fā)現(xiàn)共有47家酒店。
然后把半徑縮短到3公里:
可以發(fā)現(xiàn),搜索到的酒店數(shù)量減少到了5家。
1.5.復(fù)合查詢
復(fù)合(compound)查詢:復(fù)合查詢可以將其它簡單查詢組合起來,實(shí)現(xiàn)更復(fù)雜的搜索邏輯。常見的有兩種:
-
fuction score:算分函數(shù)查詢,可以控制文檔相關(guān)性算分,控制文檔排名
-
bool query:布爾查詢,利用邏輯關(guān)系組合多個(gè)其它的查詢,實(shí)現(xiàn)復(fù)雜搜索
1.5.1.相關(guān)性算分
當(dāng)我們利用match查詢時(shí),文檔結(jié)果會根據(jù)與搜索詞條的關(guān)聯(lián)度打分(_score),返回結(jié)果時(shí)按照分值降序排列。
例如,我們搜索 "虹橋如家",結(jié)果如下:
[
??{
????"_score"?:?17.850193,
????"_source"?:?{
??????"name"?:?"虹橋如家酒店真不錯(cuò)",
????}
??},
??{
????"_score"?:?12.259849,
????"_source"?:?{
??????"name"?:?"外灘如家酒店真不錯(cuò)",
????}
??},
??{
????"_score"?:?11.91091,
????"_source"?:?{
??????"name"?:?"迪士尼如家酒店真不錯(cuò)",
????}
??}
]
在elasticsearch中,早期使用的打分算法是TF-IDF算法,公式如下:
?在后來的5.1版本升級中,elasticsearch將算法改進(jìn)為BM25算法,公式如下:
TF-IDF算法有一各缺陷,就是詞條頻率越高,文檔得分也會越高,單個(gè)詞條對文檔影響較大。而BM25則會讓單個(gè)詞條的算分有一個(gè)上限,曲線更加平滑:
小結(jié):elasticsearch會根據(jù)詞條和文檔的相關(guān)度做打分,算法由兩種:
-
TF-IDF算法
-
BM25算法,elasticsearch5.1版本后采用的算法
1.5.2.算分函數(shù)查詢
根據(jù)相關(guān)度打分是比較合理的需求,但合理的不一定是產(chǎn)品經(jīng)理需要的。
以百度為例,你搜索的結(jié)果中,并不是相關(guān)度越高排名越靠前,而是誰掏的錢多排名就越靠前。如圖:
要想認(rèn)為控制相關(guān)性算分,就需要利用elasticsearch中的function score 查詢了。
1)語法說明
function score 查詢中包含四部分內(nèi)容:
-
原始查詢條件:query部分,基于這個(gè)條件搜索文檔,并且基于BM25算法給文檔打分,原始算分(query score)
-
過濾條件:filter部分,符合該條件的文檔才會重新算分
-
算分函數(shù):符合filter條件的文檔要根據(jù)這個(gè)函數(shù)做運(yùn)算,得到的函數(shù)算分(function score),有四種函數(shù)
-
weight:函數(shù)結(jié)果是常量
-
field_value_factor:以文檔中的某個(gè)字段值作為函數(shù)結(jié)果
-
random_score:以隨機(jī)數(shù)作為函數(shù)結(jié)果
-
script_score:自定義算分函數(shù)算法
-
-
運(yùn)算模式:算分函數(shù)的結(jié)果、原始查詢的相關(guān)性算分,兩者之間的運(yùn)算方式,包括:
-
multiply:相乘
-
replace:用function score替換query score
-
其它,例如:sum、avg、max、min
-
function score的運(yùn)行流程如下:
-
1)根據(jù)原始條件查詢搜索文檔,并且計(jì)算相關(guān)性算分,稱為原始算分(query score)
-
2)根據(jù)過濾條件,過濾文檔
-
3)符合過濾條件的文檔,基于算分函數(shù)運(yùn)算,得到函數(shù)算分(function score)
-
4)將原始算分(query score)和函數(shù)算分(function score)基于運(yùn)算模式做運(yùn)算,得到最終結(jié)果,作為相關(guān)性算分。
因此,其中的關(guān)鍵點(diǎn)是:
-
過濾條件:決定哪些文檔的算分被修改
-
算分函數(shù):決定函數(shù)算分的算法
-
運(yùn)算模式:決定最終算分結(jié)果
2)示例
需求:給“如家”這個(gè)品牌的酒店排名靠前一些
翻譯一下這個(gè)需求,轉(zhuǎn)換為之前說的四個(gè)要點(diǎn):
-
原始條件:不確定,可以任意變化
-
過濾條件:brand = "如家"
-
算分函數(shù):可以簡單粗暴,直接給固定的算分結(jié)果,weight
-
運(yùn)算模式:比如求和
因此最終的DSL語句如下:
GET?/hotel/_search
{
??"query":?{
????"function_score":?{
??????"query":?{ .... }, // 原始查詢,可以是任意條件
??????"functions":?[?//?算分函數(shù)
????????{
??????????"filter":?{?//?滿足的條件,品牌必須是如家
????????????"term":?{
??????????????"brand":?"如家"
????????????}
??????????},
??????????"weight":?2?//?算分權(quán)重為2
????????}
??????],
"boost_mode": "sum" // 加權(quán)模式,求和
????}
??}
}
測試,在未添加算分函數(shù)時(shí),如家得分如下:
?添加了算分函數(shù)后,如家得分就提升了:
3)小結(jié)
function score query定義的三要素是什么?
-
過濾條件:哪些文檔要加分
-
算分函數(shù):如何計(jì)算function score
-
加權(quán)方式:function score 與 query score如何運(yùn)算
1.5.3.布爾查詢
布爾查詢是一個(gè)或多個(gè)查詢子句的組合,每一個(gè)子句就是一個(gè)子查詢。子查詢的組合方式有:
-
must:必須匹配每個(gè)子查詢,類似“與”
-
should:選擇性匹配子查詢,類似“或”
-
must_not:必須不匹配,不參與算分,類似“非”
-
filter:必須匹配,不參與算分
比如在搜索酒店時(shí),除了關(guān)鍵字搜索外,我們還可能根據(jù)品牌、價(jià)格、城市等字段做過濾:
每一個(gè)不同的字段,其查詢的條件、方式都不一樣,必須是多個(gè)不同的查詢,而要組合這些查詢,就必須用bool查詢了。
需要注意的是,搜索時(shí),參與打分的字段越多,查詢的性能也越差。因此這種多條件查詢時(shí),建議這樣做:
-
搜索框的關(guān)鍵字搜索,是全文檢索查詢,使用must查詢,參與算分
-
其它過濾條件,采用filter查詢。不參與算分
1)語法示例:
GET?/hotel/_search
{
??"query":?{
????"bool":?{
??????"must":?[
????????{"term":?{"city":?"上海"?}}
??????],
??????"should":?[
????????{"term":?{"brand":?"皇冠假日"?}},
{"term":?{"brand":?"華美達(dá)"?}}
??????],
??????"must_not":?[
????????{?"range":?{?"price":?{?"lte":?500?}?}}
??????],
??????"filter":?[
????????{?"range":?{"score":?{?"gte":?45?}?}}
??????]
????}
??}
}
2)示例
需求:搜索名字包含“如家”,價(jià)格不高于400,在坐標(biāo)31.21,121.5周圍10km范圍內(nèi)的酒店。
分析:
-
名稱搜索,屬于全文檢索查詢,應(yīng)該參與算分。放到must中
-
價(jià)格不高于400,用range查詢,屬于過濾條件,不參與算分。放到must_not中
-
周圍10km范圍內(nèi),用geo_distance查詢,屬于過濾條件,不參與算分。放到filter中
3)小結(jié)
bool查詢有幾種邏輯關(guān)系?
-
must:必須匹配的條件,可以理解為“與”
-
should:選擇性匹配的條件,可以理解為“或”
-
must_not:必須不匹配的條件,不參與打分
-
filter:必須匹配的條件,不參與打分
2.3.高亮
2.3.1.高亮原理
什么是高亮顯示呢?
我們在百度,京東搜索時(shí),關(guān)鍵字會變成紅色,比較醒目,這叫高亮顯示:
高亮顯示的實(shí)現(xiàn)分為兩步:
-
1)給文檔中的所有關(guān)鍵字都添加一個(gè)標(biāo)簽,例如
<em>
標(biāo)簽 -
2)頁面給
<em>
標(biāo)簽編寫CSS樣式
2.3.2.實(shí)現(xiàn)高亮
高亮的語法:
GET?/hotel/_search
{
??"query":?{
????"match":?{
??????"FIELD":?"TEXT" // 查詢條件,高亮一定要使用全文檢索查詢
????}
??},
??"highlight":?{
????"fields":?{?//?指定要高亮的字段
??????"FIELD":?{
????????"pre_tags":?"<em>",??//?用來標(biāo)記高亮字段的前置標(biāo)簽
????????"post_tags":?"</em>"?//?用來標(biāo)記高亮字段的后置標(biāo)簽
??????}
????}
??}
}
注意:
-
高亮是對關(guān)鍵字高亮,因此搜索條件必須帶有關(guān)鍵字,而不能是范圍這樣的查詢。
-
默認(rèn)情況下,高亮的字段,必須與搜索指定的字段一致,否則無法高亮
-
如果要對非搜索字段高亮,則需要添加一個(gè)屬性:required_field_match=false
2.4.總結(jié)
查詢的DSL是一個(gè)大的JSON對象,包含下列屬性:
-
query:查詢條件
-
from和size:分頁條件
-
sort:排序條件
-
highlight:高亮條件
示例:
?文章來源地址http://www.zghlxwxcb.cn/news/detail-433882.html
3.RestClient查詢文檔
文檔的查詢同樣適用昨天學(xué)習(xí)的 RestHighLevelClient對象,基本步驟包括:
-
1)準(zhǔn)備Request對象
-
2)準(zhǔn)備請求參數(shù)
-
3)發(fā)起請求
-
4)解析響應(yīng)
3.1.快速入門
我們以match_all查詢?yōu)槔?/p>
3.1.1.發(fā)起查詢請求
代碼解讀:
-
第一步,創(chuàng)建
SearchRequest
對象,指定索引庫名 -
第二步,利用
request.source()
構(gòu)建DSL,DSL中可以包含查詢、分頁、排序、高亮等-
query()
:代表查詢條件,利用QueryBuilders.matchAllQuery()
構(gòu)建一個(gè)match_all查詢的DSL
-
-
第三步,利用client.search()發(fā)送請求,得到響應(yīng)
這里關(guān)鍵的API有兩個(gè),一個(gè)是request.source()
,其中包含了查詢、排序、分頁、高亮等所有功能:
?
?另一個(gè)是QueryBuilders
,其中包含match、term、function_score、bool等各種查詢:
?
3.1.2.解析響應(yīng)
響應(yīng)結(jié)果的解析:
?文章來源:http://www.zghlxwxcb.cn/news/detail-433882.html
?
到了這里,關(guān)于微服務(wù)學(xué)習(xí):SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!