目錄
- from+size分頁
1.1 from+size的命令行實現(xiàn)
1.2 from+size的RestHighLevelClient實現(xiàn) - scroll 分頁
2.1 scroll分頁的命令行實現(xiàn)
2.2 scroll的RestHighLevelClient實現(xiàn) - search_after分頁
3.1 search_after的命令行實現(xiàn)
3.2 search_after的的RestHighLevelClient實現(xiàn) - 總結(jié)
正文
ES作為數(shù)據(jù)源的分頁查詢。 數(shù)據(jù)量如果過大叉橱,使用淺分頁可能會引發(fā)性能問題玩裙,可以考慮search_after深分頁蜒谤,當然是要根據(jù)具體業(yè)務(wù)場景進行分析氮帐。
分頁一般有三種方式:
- es默認采用的是from+size形式昆庇,在深度分頁的情況下榄棵,這種效率是非常低的嘲更,但是可以隨機跳轉(zhuǎn)頁面筐钟;
- scroll search 方式(滾動搜索),官方的建議并不是用于實時的請求赋朦,因為每一個 scroll_id 不僅會占用大量的資源(特別是排序的請求)篓冲,而且是生成的歷史快照,對于數(shù)據(jù)的變更不會反映到快照上北发。這種方式往往用于非實時處理大量數(shù)據(jù)的情況纹因,比如要進行數(shù)據(jù)遷移或者索引變更之類的喷屋。
- search_after方式(滾動搜索)琳拨,可以在實時的情況下處理深度分頁,在Es5.x版本后提供的功能屯曹,search_after缺點是不能夠隨機跳轉(zhuǎn)分頁狱庇,只能是一頁一頁的向后翻,并且需要至少指定一個唯一不重復(fù)字段來排序恶耽。
1. from+size分頁
Es封裝RestHighLevelClient和BulkProcessor的工具類
1.1 from+size的命令行實現(xiàn)
GET /test_demo/_search
{
"query":{
"match_all": {}
},
"from":5000,
"size":10
}
上面意味著es需要在各個分片上匹配排序并得到5010條數(shù)據(jù)密任,協(xié)調(diào)節(jié)點拿到這些數(shù)據(jù)再進行排序,然后結(jié)果集中取最后10條數(shù)據(jù)返回偷俭。
上述語句性能低的原因:我們只需要10條數(shù)據(jù)浪讳,而es每個分片都需要執(zhí)行from+size條數(shù)據(jù)然后處理后返回。
es為了性能涌萤,會限制我們分頁的深度淹遵,es目前支持最大的max_result_window = 10000,也就是from+size的大小不能超過10000负溪。
1.2 from+size的RestHighLevelClient實現(xiàn)
/**
* 淺分頁查詢
*
* @throws IOException
*/
public static void testPage() throws IOException {
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//排序條件
searchSourceBuilder.sort("id", SortOrder.ASC);
searchSourceBuilder.sort("publishTime", SortOrder.DESC);
//分頁查詢
searchSourceBuilder.from(2);
searchSourceBuilder.size(2);
EsUtil.remoteSearch(searchRequest, searchSourceBuilder);
}
2. scroll 分頁
在es中我們分頁要請求大數(shù)據(jù)集或者一次請求要獲取大的數(shù)據(jù)集透揣,scroll[skr??l]
都是一種非常好的解決方案。
scroll也是滾動搜索川抡。會在第一次搜索的時候辐真,保存一個當時的快照。之后只會基于該舊的視圖快照提供數(shù)據(jù)搜索崖堤。在這個期間發(fā)生變動侍咱,是不會讓用戶看到的。
官方的建議并不是用于實時的請求密幔,因為每一個 scroll_id 不僅會占用大量的資源(特別是排序的請求)楔脯,而且是生成的歷史快照,對于數(shù)據(jù)的變更不會反映到快照上老玛。這種方式往往用于非實時處理大量數(shù)據(jù)的情況淤年,比如要進行數(shù)據(jù)遷移或者索引變更之類的钧敞。
2.1 scroll分頁的命令行實現(xiàn)
- 第一步:進行搜索,生成scrollId麸粮。
GET test_demo/_search?scroll=1m
{
"size":2,
"query":{
"terms":{
"tag":[
"疫情"
],
"boost":"1.0"
}
}
}
- 后續(xù)在使用第一步生成的scrollId進行滾動查詢溉苛。
POST /_search/scroll
{
"scroll": "5m",
"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAylJEWQ3Z4cHRTRzVTeXVqRG9YY1R5Q3hYdw=="
}
2.2 scroll的RestHighLevelClient實現(xiàn)
public static void testScroll() throws IOException {
SearchRequest searchRequest = new SearchRequest();
//失效時間為1min
Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1));
//封存快照
searchRequest.scroll(scroll);
/**
* 查詢條件
*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("tag", "疫情");
searchSourceBuilder.query(termQueryBuilder);
/**
* 分頁參數(shù)
*/
searchSourceBuilder.size(2);
searchRequest.indices("test_demo");
//放入文檔中
searchRequest.source(searchSourceBuilder);
log.info("dsl:" + searchSourceBuilder.toString());
//遠程查詢
SearchResponse searchResponse = EsUtil.getRestHighLevelClient().search(searchRequest, RequestOptions.DEFAULT);
//元素數(shù)量
Iterator<SearchHit> it1 = searchResponse.getHits().iterator();
while (it1.hasNext()) {
SearchHit next = it1.next();
log.info("輸出數(shù)據(jù):" + next.getSourceAsString());
}
log.info("=======================||");
//計算總數(shù)量
long totalCount = searchResponse.getHits().getTotalHits().value;
//得到總頁數(shù)
int page = (int) Math.ceil((float) totalCount / 2);
//多次遍歷分頁,獲取結(jié)果
String scrollId = searchResponse.getScrollId();
for (int i = 1; i <= page; i++) {
//獲取到該id
SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
searchScrollRequest.scroll(scroll);
SearchResponse response = EsUtil.getRestHighLevelClient().scroll(searchScrollRequest, RequestOptions.DEFAULT);
//打印數(shù)據(jù)
SearchHits hits = response.getHits();
scrollId = response.getScrollId();
Iterator<SearchHit> iterator = hits.iterator();
while (iterator.hasNext()) {
SearchHit next = iterator.next();
log.info("輸出數(shù)據(jù):" + next.getSourceAsString());
}
log.info("=======================");
}
}
3. search_after分頁
可以在實時的情況下處理深度分頁弄诲,在Es5.x版本后提供的功能愚战,search_after缺點是不能夠隨機跳轉(zhuǎn)分頁,只能是一頁一頁的向后翻齐遵,并且需要至少指定一個唯一不重復(fù)字段來排序寂玲。
3.1 search_after的命令行實現(xiàn)
注意:search_after必須指定一個唯一不重復(fù)的字段來排序,此處我們指定了兩個字段進行排序梗摇,
該例子:id是唯一不重復(fù)字段拓哟,publishTime可能會重復(fù)。
GET test_demo/_search
{
"query": {
"match_all": {
}
},
"size": 2,
"sort":[
{"id":"asc"},
{"publishTime":"desc"}
]
}
注意伶授,結(jié)果返回的sort字段断序,存儲的是最后一組的排序字段的值,而search_after在下次搜索時糜烹,需要攜帶該數(shù)據(jù)违诗。
最終完成了滾動分頁操作。
3.2 search_after的的RestHighLevelClient實現(xiàn)
public static void testSearchAfter() throws IOException {
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//排序條件
searchSourceBuilder.sort("id", SortOrder.ASC);
searchSourceBuilder.sort("publishTime", SortOrder.DESC);
//分頁查詢
searchSourceBuilder.size(2);
List<SearchHit> remoteSearch = EsUtil.remoteSearch(searchRequest, searchSourceBuilder);
//查詢最后一筆數(shù)據(jù)
SearchHit result = remoteSearch.get(remoteSearch.size() - 1);
//序列化為對象
//分頁查詢下一頁數(shù)據(jù)
log.info("=====================下一頁============================");
SearchRequest searchRequest2 = new SearchRequest();
SearchSourceBuilder searchSourceBuilder2 = new SearchSourceBuilder();
//排序條件
searchSourceBuilder2.sort("id", SortOrder.ASC);
searchSourceBuilder2.sort("publishTime", SortOrder.DESC);
//存儲上一次分頁的sort信息
searchSourceBuilder2.searchAfter(result.getSortValues());
searchSourceBuilder2.size(2);
EsUtil.remoteSearch(searchRequest2, searchSourceBuilder2);
}