elasticsearch最擅長的是
搜索
和
數(shù)據(jù)分析
。
數(shù)據(jù)搜索
DSL實現(xiàn)
查詢文檔
常見的查詢類型包括:
- 查詢所有:查詢出所有數(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
全文檢索查詢:
使用場景
全文檢索查詢的基本流程如下:
- 對用戶搜索的內(nèi)容做分詞,得到詞條
- 根據(jù)詞條去倒排索引庫中匹配,得到文檔id
- 根據(jù)文檔id找到文檔,返回給用戶
比較常用的場景包括:
- 商城的輸入框搜索
- 百度輸入框搜索
可以看到,兩種查詢結(jié)果是一樣的,為什么?
因為我們將brand、name、business值都利用copy_to復(fù)制到了all字段中。因此你根據(jù)三個字段搜索,和根據(jù)all字段搜索效果當(dāng)然一樣了。
但是,搜索字段越多,對查詢性能影響越大,因此建議采用copy_to,然后單字段查詢的方式。
精準(zhǔn)查詢:
精確查詢一般是查找keyword、數(shù)值、日期、boolean等類型字段。所以不會對搜索條件分詞。常見的有:
- term:根據(jù)詞條精確值查詢
- range:根據(jù)值的范圍查詢
范圍查詢,一般應(yīng)用在對數(shù)值類型做范圍過濾的時候。比如做價格范圍過濾。
精確查詢常見的有哪些?
- term查詢:根據(jù)詞條精確匹配,一般搜索keyword類型、數(shù)值類型、布爾類型、日期類型字段
- range查詢:根據(jù)數(shù)值范圍查詢,可以是數(shù)值、日期的范圍
地理坐標(biāo)查詢:
附近查詢,也叫做距離查詢(geo_distance):查詢到指定中心點小于某個距離值的所有文檔
復(fù)合查詢:
復(fù)合(compound)查詢:復(fù)合查詢可以將其它簡單查詢組合起來,實現(xiàn)更復(fù)雜的搜索邏輯。常見的有兩種:
-
fuction score:算分函數(shù)查詢,可以控制文檔相關(guān)性算分,控制文檔排名
-
bool query:布爾查詢,利用邏輯關(guān)系組合多個其它的查詢,實現(xiàn)復(fù)雜搜索
function score的運行流程如下: -
1)根據(jù)原始條件查詢搜索文檔,并且計算相關(guān)性算分,稱為原始算分(query score)
-
2)根據(jù)過濾條件,過濾文檔
-
3)符合過濾條件的文檔,基于算分函數(shù)運算,得到函數(shù)算分(function score)
-
4)將原始算分(query score)和函數(shù)算分(function score)基于運算模式做運算,得到最終結(jié)果,作為相關(guān)性算分。
function score query定義的三要素是什么?
- 過濾條件:哪些文檔要加分
- 算分函數(shù):如何計算function score
- 加權(quán)方式:function score 與 query score如何運算
布爾查詢:
布爾查詢是一個或多個查詢子句的組合,每一個子句就是一個子查詢。子查詢的組合方式有:
- must:必須匹配每個子查詢,類似“與”
- should:選擇性匹配子查詢,類似“或”
- must_not:必須不匹配,不參與算分,類似“非”
- filter:必須匹配,不參與算分
比如在搜索酒店時,除了關(guān)鍵字搜索外,我們還可能根據(jù)品牌、價格、城市等字段做過濾:
每一個不同的字段,其查詢的條件、方式都不一樣,必須是多個不同的查詢,而要組合這些查詢,就必須用bool查詢了。
需要注意的是,搜索時,參與打分的字段越多,查詢的性能也越差。因此這種多條件查詢時,建議這樣做:
- 搜索框的關(guān)鍵字搜索,是全文檢索查詢,使用must查詢,參與算分
- 其它過濾條件,采用filter查詢。不參與算分
示例:
需求:搜索名字包含“如家”,價格不高于400,在坐標(biāo)31.21,121.5周圍10km范圍內(nèi)的酒店。
分析:
- 名稱搜索,屬于全文檢索查詢,應(yīng)該參與算分。放到must中
- 價格不高于400,用range查詢,屬于過濾條件,不參與算分。放到must_not中
- 周圍10km范圍內(nèi),用geo_distance查詢,屬于過濾條件,不參與算分。放到filter中
搜索結(jié)果處理
排序:
普通字段排序:
地理坐標(biāo)排序:
分頁:
- from:從第幾個文檔開始
- size:總共查詢幾個文檔
分頁查詢的常見實現(xiàn)方案以及優(yōu)缺點:
-
from + size
:- 優(yōu)點:支持隨機翻頁
- 缺點:深度分頁問題,默認查詢上限(from + size)是10000
- 場景:百度、京東、谷歌、淘寶這樣的隨機翻頁搜索
-
after search
:- 優(yōu)點:沒有查詢上限(單次查詢的size不超過10000)
- 缺點:只能向后逐頁查詢,不支持隨機翻頁
- 場景:沒有隨機翻頁需求的搜索,例如手機向下滾動翻頁
高亮:
高亮顯示的實現(xiàn)分為兩步:
-
1)給文檔中的所有關(guān)鍵字都添加一個標(biāo)簽,例如
<em>
標(biāo)簽 -
2)頁面給
<em>
標(biāo)簽編寫CSS樣式
注意: -
高亮是對關(guān)鍵字高亮,因此搜索條件必須帶有關(guān)鍵字,而不能是范圍這樣的查詢。
-
默認情況下,高亮的字段,必須與搜索指定的字段一致,否則無法高亮
-
如果要對非搜索字段高亮,則需要添加一個屬性:required_field_match=false
總結(jié):
查詢的DSL是一個大的JSON對象,包含下列屬性:
- query:查詢條件
- from和size:分頁條件
- sort:排序條件
- highlight:高亮條件
RestClient實現(xiàn)
簡單實現(xiàn):
@Test
void testMatchAll() throws IOException {
// 1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準(zhǔn)備DSL
request.source()
.query(QueryBuilders.matchAllQuery());
// 3.發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應(yīng)
handleResponse(response);
}
private void handleResponse(SearchResponse response) {
// 4.解析響應(yīng)
SearchHits searchHits = response.getHits();
// 4.1.獲取總條數(shù)
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "條數(shù)據(jù)");
// 4.2.文檔數(shù)組
SearchHit[] hits = searchHits.getHits();
// 4.3.遍歷
for (SearchHit hit : hits) {
// 獲取文檔source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println("hotelDoc = " + hotelDoc);
}
}
查詢的基本步驟是:
-
創(chuàng)建SearchRequest對象
-
準(zhǔn)備Request.source(),也就是DSL。
① QueryBuilders來構(gòu)建查詢條件
② 傳入Request.source() 的 query() 方法
-
發(fā)送請求,得到結(jié)果
-
解析結(jié)果(參考JSON結(jié)果,從外到內(nèi),逐層解析)
精確查詢:
布爾查詢:
排序、分頁:
搜索結(jié)果的排序和分頁是與query同級的參數(shù),因此同樣是使用request.source()來設(shè)置。
高亮:
高亮的結(jié)果與查詢的文檔結(jié)果默認是分離的,并不在一起。
因此解析高亮的代碼需要額外處理:
旅游案例
我們實現(xiàn)四部分功能:
- 酒店搜索和分頁
- 酒店結(jié)果過濾
- 我周邊的酒店
- 酒店競價排名
酒店搜索和分頁
前端請求的json結(jié)構(gòu)如下:
定義一個實體類:
返回值
分頁查詢,需要返回分頁結(jié)果PageResult,包含兩個屬性:
-
total
:總條數(shù) -
List<HotelDoc>
:當(dāng)前頁的數(shù)據(jù)
定義controller:
實現(xiàn)業(yè)務(wù)搜索:
我們在controller調(diào)用了IHotelService,并沒有實現(xiàn)該方法,因此下面我們就在IHotelService中定義方法,并且去實現(xiàn)業(yè)務(wù)邏輯。
實現(xiàn)搜索業(yè)務(wù),肯定離不開RestHighLevelClient,我們需要把它注冊到Spring中作為一個Bean。在cn.itcast.hotel
中的HotelDemoApplication
中聲明這個Bean
在cn.itcast.hotel.service.impl
中的HotelService
中實現(xiàn)search方法:
@Override
public PageResult search(RequestParams params) {
try {
// 1.準(zhǔn)備Request
SearchRequest request = new SearchRequest("hotel");
// 2.準(zhǔn)備DSL
// 2.1.query
String key = params.getKey();
if (key == null || "".equals(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
// 2.2.分頁
int page = params.getPage();
int size = params.getSize();
request.source().from((page - 1) * size).size(size);
// 3.發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應(yīng)
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 結(jié)果解析
private PageResult handleResponse(SearchResponse response) {
// 4.解析響應(yīng)
SearchHits searchHits = response.getHits();
// 4.1.獲取總條數(shù)
long total = searchHits.getTotalHits().value;
// 4.2.文檔數(shù)組
SearchHit[] hits = searchHits.getHits();
// 4.3.遍歷
List<HotelDoc> hotels = new ArrayList<>();
for (SearchHit hit : hits) {
// 獲取文檔source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
// 放入集合
hotels.add(hotelDoc);
}
// 4.4.封裝返回
return new PageResult(total, hotels);
}
酒店結(jié)果過濾
需求:添加品牌、城市、星級、價格等過濾功能
修改實體類:
修改搜索業(yè)務(wù):
在HotelService的search方法中,只有一個地方需要修改:requet.source().query( … )其中的查詢條件。
在之前的業(yè)務(wù)中,只有match查詢,根據(jù)關(guān)鍵字搜索,現(xiàn)在要添加條件過濾,包括:
- 品牌過濾:是keyword類型,用term查詢
- 星級過濾:是keyword類型,用term查詢
- 價格過濾:是數(shù)值類型,用range查詢
- 城市過濾:是keyword類型,用term查詢
多個查詢條件組合,肯定是boolean查詢來組合:
- 關(guān)鍵字搜索放到must中,參與算分
- 其它過濾條件放到filter中,不參與算分
buildBasicQuery的代碼如下:
private void buildBasicQuery(RequestParams params, SearchRequest request) {
// 1.構(gòu)建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2.關(guān)鍵字搜索
String key = params.getKey();
if (key == null || "".equals(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
// 3.城市條件
if (params.getCity() != null && !params.getCity().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
// 4.品牌條件
if (params.getBrand() != null && !params.getBrand().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
// 5.星級條件
if (params.getStarName() != null && !params.getStarName().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
// 6.價格
if (params.getMinPrice() != null && params.getMaxPrice() != null) {
boolQuery.filter(QueryBuilders
.rangeQuery("price")
.gte(params.getMinPrice())
.lte(params.getMaxPrice())
);
}
// 7.放入source
request.source().query(boolQuery);
}
我周邊的酒店
需求:我附近的酒店
我們要做的事情就是基于這個location坐標(biāo),然后按照距離對周圍酒店排序。實現(xiàn)思路如下:
- 修改RequestParams參數(shù),接收location字段
- 修改search方法業(yè)務(wù)邏輯,如果location有值,添加根據(jù)geo_distance排序的功能
修改實體類:
添加距離排序:
發(fā)現(xiàn)確實可以實現(xiàn)對我附近酒店的排序,不過并沒有看到酒店到底距離我多遠,這該怎么辦?
因此,我們在結(jié)果解析階段,除了解析source部分以外,還要得到sort部分,也就是排序的距離,然后放到響應(yīng)結(jié)果中。
我們要做兩件事:
- 修改HotelDoc,添加排序距離字段,用于頁面顯示
- 修改HotelService類中的handleResponse方法,添加對sort值的獲取
酒店競價排名
需求:讓指定的酒店在搜索結(jié)果中排名置頂
這里的需求是:讓指定酒店排名靠前。因此我們需要給這些酒店添加一個標(biāo)記,這樣在過濾條件中就可以根據(jù)這個標(biāo)記來判斷,是否要提高算分。
比如,我們給酒店添加一個字段:isAD,Boolean類型:
- true:是廣告
- false:不是廣告
這樣function_score包含3個要素就很好確定了:
- 過濾條件:判斷isAD 是否為true
- 算分函數(shù):我們可以用最簡單暴力的weight,固定加權(quán)值
- 加權(quán)方式:可以用默認的相乘,大大提高算分
因此,業(yè)務(wù)的實現(xiàn)步驟包括:文章來源:http://www.zghlxwxcb.cn/news/detail-688681.html
- 給HotelDoc類添加isAD字段,Boolean類型
- 挑選幾個你喜歡的酒店,給它的文檔數(shù)據(jù)添加isAD字段,值為true
- 修改search方法,添加function score功能,給isAD值為true的酒店增加權(quán)重
文章來源地址http://www.zghlxwxcb.cn/news/detail-688681.html
private void buildBasicQuery(RequestParams params, SearchRequest request) {
// 1.構(gòu)建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 關(guān)鍵字搜索
String key = params.getKey();
if (key == null || "".equals(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
// 城市條件
if (params.getCity() != null && !params.getCity().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
}
// 品牌條件
if (params.getBrand() != null && !params.getBrand().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
}
// 星級條件
if (params.getStarName() != null && !params.getStarName().equals("")) {
boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
}
// 價格
if (params.getMinPrice() != null && params.getMaxPrice() != null) {
boolQuery.filter(QueryBuilders
.rangeQuery("price")
.gte(params.getMinPrice())
.lte(params.getMaxPrice())
);
}
// 2.算分控制
FunctionScoreQueryBuilder functionScoreQuery =
QueryBuilders.functionScoreQuery(
// 原始查詢,相關(guān)性算分的查詢
boolQuery,// 原始查詢
// function score的數(shù)組
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
// 其中的一個function score 元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 過濾條件
QueryBuilders.termQuery("isAD", true),
// 算分函數(shù)
ScoreFunctionBuilders.weightFactorFunction(10)
)
});
request.source().query(functionScoreQuery);
}
到了這里,關(guān)于【分布式搜索引擎es】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!