分頁
elasticsearch支持對搜索結果排序,默認是根據相關度算分(_score)來排序。可以排序字段類型有:keyword類型、數值類型、地理坐標類型、日期類型等。
一旦自己指定排序,則_score排序就會失效。
基本語法
//基本語法
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"FIELD": "desc" // 排序字段、排序方式ASC、DESC
}
]
}
//地理坐標排序
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance" : {
"FIELD" : "緯度,經度", // 文檔中geo_point類型的字段名、目標坐標點
"order" : "asc", // 排序方式
"unit" : "km" // 排序的距離單位
}
}
]
}
舉例1
//對酒店數據按照用戶評價降序排序,評價相同的按照價格升序排序
GET hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"score": {
"order": "desc"
},
"price": {
"order": "asc"
}
}
]
}
舉例2
//實現對酒店數據按照到你的位置坐標的距離升序排序
GET hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": "31.220872,121.467579",
"order": "desc",
"unit": "km"
}
}
]
}
分頁
elasticsearch 默認情況下只返回top10的數據。而如果要查詢更多數據就需要修改分頁參數了。elasticsearch中通過修改from、size參數來控制要返回的分頁結果:
- from:從第幾個文檔開始
- size:總共查詢幾個文檔
類似於mysql中的limit ?, ?
基本語法
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 0, // 分頁開始的位置,默認為0
"size": 10, // 期望獲取的文檔總數
"sort": [
{"price": "asc"}
]
}
深度分頁問題
ES是分布式的,所以會面臨深度分頁問題。例如按price排序后,獲取from = 990,size =10的數據:
-
首先在每個數據分片上都排序並查詢前1000條文檔。
-
然后將所有節點的結果聚合,在內存中重新排序選出前1000條文檔
-
最后從這1000條中,選取從990開始的10條文檔
如果搜索頁數過深,或者結果集(from + size)越大,對內存和CPU的消耗也越高。因此ES設定結果集查詢的上限是10000
深度分頁解決方案
針對深度分頁,ES提供了兩種解決方案,官方文檔:
-
search after:分頁時需要排序,原理是從上一次的排序值開始,查詢下一頁數據。官方推薦使用的方式。
-
scroll:原理將排序數據形成快照,保存在內存。官方已經不推薦使用。
小結
分頁查詢的常見實現方案以及優缺點:
-
from + size:- 優點:支持隨機翻頁
- 缺點:深度分頁問題,默認查詢上限(from + size)是10000
- 場景:百度、京東、谷歌、淘寶這樣的隨機翻頁搜索
-
after search:- 優點:沒有查詢上限(單次查詢的size不超過10000)
- 缺點:只能向后逐頁查詢,不支持隨機翻頁
- 場景:沒有隨機翻頁需求的搜索,例如手機向下滾動翻頁
-
scroll:- 優點:沒有查詢上限(單次查詢的size不超過10000)
- 缺點:會有額外內存消耗,並且搜索結果是非實時的
- 場景:海量數據的獲取和遷移。從ES7.1開始不推薦,建議用 after search方案。
高亮
高亮:就是在搜索結果中把搜索關鍵字突出顯示。
原理是這樣的:
-
將搜索結果中的關鍵字用標簽標記出來
-
在頁面中給標簽添加css樣式
基本語法
GET /hotel/_search
{
"query": {
"match": {
"FIELD": "TEXT" // 查詢條件,高亮一定要使用全文檢索查詢
}
},
"highlight": {
"fields": { // 指定要高亮的字段
"FIELD": {
"pre_tags": "<em>", // 用來標記高亮字段的前置標簽
"post_tags": "</em>" // 用來標記高亮字段的后置標簽
}
}
}
}
注意:
- 高亮是對關鍵字高亮,因此搜索條件必須帶有關鍵字,而不能是范圍這樣的查詢。
- 默認情況下,高亮的字段,必須與搜索指定的字段一致,否則無法高亮
- 如果要對非搜索字段高亮,則需要添加一個屬性:required_field_match=false
案例
//給所有如家高亮
GET hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": "<em>",//前綴
"post_tags": "</em>",//后綴
"require_field_match": "false"//是否與查詢字段統一
}
}
}
}
RestClient查詢文檔
快速入門查詢所有(Match_all)
/**
* 這個方法是match_all的方法,並將返回的結果封裝成java對象
* @throws IOException
*/
@Test
void matchAll() throws IOException {
//1.創建Request對象
SearchRequest request = new SearchRequest("hotel");
//2.准備請求參數:DSL語句
request.source().query(QueryBuilders.matchAllQuery());
//3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
System.out.println(response);
}
返回的結果是一個SearchResponse,這是一個json風格的對象。
現在需要解析SearchResponse,對解析的方法進行抽取
/**
* 這個方法用來將SearchResponse進行解析
* @param response
* @return:泛型為HotelDoc的List集合
*/
private static List<HotelDoc> extracted(SearchResponse response) {
List<HotelDoc> list = new ArrayList<HotelDoc>();
SearchHits searchHits = response.getHits();
//4.1獲取總條數
long total = searchHits.getTotalHits().value;
System.out.println("總搜索到"+total+"條記錄");
//4.2獲取文檔數組
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
//4.3反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
list.add(hotelDoc);
}
return list;
}
代碼解讀:
-
第一步,創建
SearchRequest對象,指定索引庫名 -
第二步,利用
request.source()構建DSL,DSL中可以包含查詢、分頁、排序、高亮等query():代表查詢條件,利用QueryBuilders.matchAllQuery()構建一個match_all查詢的DSL
-
第三步,利用client.search()發送請求,得到響應
這里關鍵的API有兩個,一個是request.source(),其中包含了查詢、排序、分頁、高亮等所有功能:

另一個是QueryBuilders,其中包含match、term、function_score、bool等各種查詢:

全文檢索查詢Match
/**
* 這個方法是match的方法,並將返回的結果封裝成java對象
* @throws IOException
*/
@Test
void match() throws IOException {
//1.創建Request對象
SearchRequest request = new SearchRequest("hotel");
//2.准備請求參數:DSL語句
request.source().query(QueryBuilders.matchQuery("name","如家"));
//3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
}
全文搜索multi_match
/**
* 這個方法是multi_match的方法,並將返回的結果封裝成java對象
* @throws IOException
*/
@Test
void multiMatch() throws IOException {
//1.創建Request對象
SearchRequest request = new SearchRequest("hotel");
//2.准備請求參數:DSL語句
request.source().query(QueryBuilders.multiMatchQuery("如家","name","bussiness"));
//3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
for(HotelDoc hotelDoc :extracted(response)){
System.out.println(hotelDoc);
}
}
復合查詢
/**
* 這個方法是bool的方法,並將返回的結果封裝成java對象
* @throws IOException
*/
@Test
void bool() throws IOException {
//1.創建Request對象
SearchRequest request = new SearchRequest("hotel");
//2.1准備boolQuery
BoolQueryBuilder bool = QueryBuilders.boolQuery();
bool
.must(QueryBuilders.termQuery("city","上海"))
.filter(QueryBuilders.rangeQuery("price").gte(100).lte(500))
.must(QueryBuilders.termQuery("starName","四鑽"));
//2.准備請求參數
request.source().query(bool);
//3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
for(HotelDoc hotelDoc :extracted(response)){
System.out.println(hotelDoc);
}
}
分頁、排序
/**
* 這個方法是match_all的方法,並將返回的結果封裝成java對象
* 並實現了分頁和排序
* @throws IOException
*/
@Test
void matchAll() throws IOException {
int page=1,size=5;
//1.創建Request對象
SearchRequest request = new SearchRequest("hotel");
//2.准備請求參數:DSL語句
request.source().query(QueryBuilders.matchAllQuery());
//3.1分頁
request.source().from((page-1)*size).size(size);
//3.2排序
request.source().sort("price",SortOrder.DESC);
//3.發送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
extracted(response);
}
高亮
//4.2獲取文檔數組
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
//4.3反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//4.4處理高亮
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField highlightField = highlightFields.get("name");
//4.4.1如果存在高亮則。。。
if(CollectionUtils.isEmpty(highlightFields)){
HighlightField highlightField = highlightFields.get("name");
if(highlightField!=null){
String name = highlightField.getFragments()[0].string();
hotelDoc.setName(name);
}
}
System.out.println(hotelDoc);
list.add(hotelDoc);
}
-
所有搜索DSL的構建,記住一個API:SearchRequest的source()方法。
-
高亮結果解析是參考JSON結果,逐層解析
