1里烦、分頁查詢
1.1凿蒜、 正常分頁查詢代碼如下
假設(shè)現(xiàn)在你要查詢第100頁的10條數(shù)據(jù),但是對于es來說胁黑,from=1000000废封,size=100,這時(shí) es需要從各個(gè)分片上查詢出來10000100條數(shù)據(jù)丧蘸,然后匯總計(jì)算后從其中取出100條漂洋。如果有5個(gè)分片則需要查詢出來5*10000100條數(shù)據(jù),如果現(xiàn)在有一個(gè)100個(gè)查詢請求呢力喷,50億左右的數(shù)據(jù)刽漂,一條數(shù)據(jù)2KB,就需要9000G左右的內(nèi)存弟孟,什么樣的機(jī)器能夠支持這么龐大的查詢爽冕,所以如果你在使用es的分頁查詢過程中,剛開始翻頁可能速度比較快披蕉,可能到第一百頁查詢就需要4-5s颈畸,翻到1000頁以后乌奇,直接報(bào)錯(cuò)了。
NativeSearchQueryBuilder query = new NativeSearchQueryBuilder();
if(!StringUtils.isEmpty(ulqBean.getStartTime()) && !StringUtils.isEmpty(ulqBean.getEndTime())) {
query.withQuery(QueryBuilders.rangeQuery("logTime").from(ulqBean.getStartTime()).to(ulqBean.getEndTime()));
}
if(!StringUtils.isEmpty(ulqBean.getSearch())) {
BoolQueryBuilder shouldQuery = QueryBuilders.boolQuery()
.should(QueryBuilders.wildcardQuery("content", "*" + ulqBean.getSearch() + "*"))
.should(QueryBuilders.wildcardQuery("code", "*" + ulqBean.getSearch() + "*"))
.should(QueryBuilders.wildcardQuery("name", "*" + ulqBean.getSearch() + "*"));
query.withQuery(shouldQuery);
}
query.withSort(new FieldSortBuilder("logTime").order(SortOrder.DESC));
if(ulqBean.getPageNo() != null && ulqBean.getPageSize() != null) {
//es結(jié)果從第0頁開始算
query.withPageable(new PageRequest(ulqBean.getPageNo() - 1, ulqBean.getPageSize()));
}
NativeSearchQuery build = query.build();
org.springframework.data.domain.Page<ConductAudits> conductAuditsPage = template.queryForPage(build, ConductAudits.class);
ulqBean.getPagination().setTotal((int) conductAuditsPage.getTotalElements());
ulqBean.getPagination().setList(conductAuditsPage.getContent());
1.2眯娱、 錯(cuò)誤信息
[root@localhost elasticsearch-2.4.6]# curl -XGET 'http://11.12.84.126:9200/_audit_0102/_log_0102/_search?size=2&from=10000&pretty=true'
{
"error" : {
"root_cause" : [ {
"type" : "query_phase_execution_exception",
"reason" : "Result window is too large, from + size must be less than or equal to: [10000] but was [10002]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level parameter."
} ],
"type" : "search_phase_execution_exception",
"reason" : "all shards failed",
"phase" : "query",
"grouped" : true,
"failed_shards" : [ {
"shard" : 0,
"index" : "_audit_0102",
"node" : "f_CQitYESZedx8ZbyZ6bHA",
"reason" : {
"type" : "query_phase_execution_exception",
"reason" : "Result window is too large, from + size must be less than or equal to: [10000] but was [10002]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level parameter."
}
} ]
},
"status" : 500
}
1.3礁苗、 修改問題
如果你的數(shù)據(jù)大小在你的控制范圍內(nèi),想要進(jìn)一步深度分頁徙缴,你可以通過如下命令修改窗口大小:
"index": {
"max_result_window": 100000
}
}'
2试伙、深度查詢問題
但是這只是允許你更進(jìn)一步深度分頁,卻沒有從根本上解決深度分頁的問題于样,而且隨著頁碼的增加疏叨,系統(tǒng)資源占用成指數(shù)級上升,很容易就會出現(xiàn)OOM穿剖。
這時(shí)如果你的產(chǎn)品經(jīng)理要求你按照常規(guī)的做法去分頁蚤蔓,你可以很明確的告訴他,你的系統(tǒng)不支持這么深度的分頁糊余,翻的越深秀又,性能也就越差。
不過這種深度分頁場景在現(xiàn)實(shí)中確實(shí)存在贬芥,有些場景下吐辙,我們可以說服產(chǎn)品經(jīng)理很少有人會翻看很久之前的歷史數(shù)據(jù),但是有些場景下可能一天都產(chǎn)生幾百萬蘸劈。這個(gè)時(shí)候我們可以根據(jù)具體場景具體分析昏苏。
3、 利用scroll遍歷數(shù)據(jù)
scroll查詢原理是在第一次查詢的時(shí)候一次性生成一個(gè)快照威沫,根據(jù)上一次的查詢的id來進(jìn)行下一次的查詢捷雕,這個(gè)就類似于關(guān)系型數(shù)據(jù)庫的游標(biāo),然后每次滑動都是根據(jù)產(chǎn)生的游標(biāo)id進(jìn)行下一次查詢壹甥,這種性能比上面說的分頁性能要高出很多救巷,基本都是毫秒級的。 注意:scroll不支持跳頁查詢句柠。 使用場景:對實(shí)時(shí)性要求不高的查詢浦译,例如微博或者頭條滾動查詢。 具體java代碼實(shí)現(xiàn)
3.1溯职、設(shè)置查詢條件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
QueryBuilder builder = QueryBuilders.queryStringQuery("123456").field("code");
boolQueryBuilder.must(QueryBuilders.termQuery("logType", "10"))
.must(builder);
3.2精盅、 第一次查詢
第一次查詢,跟平時(shí)的search查詢一樣需要設(shè)置index和type以及查詢條件谜酒。
如果把查詢類型設(shè)置成SCAN叹俏,那么不能獲取結(jié)果并且不支持排序,只能獲得scrollId僻族,如果使用默認(rèn)設(shè)置或者不設(shè)置粘驰,那么第一次在獲取id的同時(shí)也可以獲取到查詢結(jié)果屡谐。
這個(gè)size大小的意思不是總分頁的大小,實(shí)際數(shù)量應(yīng)該是:所以實(shí)際返回的數(shù)量是:分片的數(shù)量*size
滾動時(shí)間設(shè)置是指在這個(gè)查詢搜索結(jié)果的緩存時(shí)間蝌数,時(shí)間不能太久愕掏,畢竟內(nèi)存空間是有限的。
SearchResponse response1 = client.prepareSearch("_audit_0221").setTypes("_log_0221")
.setQuery(boolQueryBuilder)
.setSearchType(.setSearchType(SearchType.DEFAULT))
.setSize(10).setScroll(TimeValue.timeValueMinutes(5))
.addSort("logTime", SortOrder.DESC)
.execute().actionGet();//第一次查詢
for (SearchHit searchHit : response1.getHits().hits()) {
biz handle....;
}
3.3顶伞、 第二次查詢饵撑,循環(huán)獲取查詢結(jié)果
while (response1.getHits().hits().length>0) {
for (SearchHit searchHit : response1.getHits().hits()) {
System.out.println(searchHit.getSource().toString());
}
response1 = client.prepareSearchScroll(response1.getScrollId()).setScroll(TimeValue.timeValueMinutes(5))
.execute().actionGet();
}
如果是一次性的搜索,可以清除查詢結(jié)果唆貌,畢竟可以減少對內(nèi)存的消耗滑潘。
ClearScrollRequest request = new ClearScrollRequest();
request.addScrollId(scrollId);
client.clearScroll(request);
4、 利用scroll-scan遍歷數(shù)據(jù)
使用場景:我有500w用戶锨咙,需要遍歷所有用戶發(fā)送數(shù)據(jù)语卤,并且對順序沒有要求,這個(gè)時(shí)候我們可以使用scroll-scan蓖租。
具體使用方式:
4.1粱侣、 查詢
SearchResponse response = client.prepareSearch("_audit_0221").setTypes("_log_0221")
.setQuery(boolQueryBuilder)
.setSearchType(SearchType.SCAN)
.setSize(5).setScroll(TimeValue.timeValueMinutes(5))
.addSort("logTime", SortOrder.DESC)
.execute().actionGet();
4.2羊壹、 獲取結(jié)果
SearchResponse response1 = client.prepareSearchScroll(scrollId).setScroll(TimeValue.timeValueMinutes(5))
.execute().actionGet();
while (response1.getHits().hits().length>0) {
for (SearchHit searchHit : response1.getHits().hits()) {
System.out.println(searchHit.getSource().toString());
}
response1 = client.prepareSearchScroll(response1.getScrollId()).setScroll(TimeValue.timeValueMinutes(5))
.execute().actionGet();
}
5蓖宦、 也可以使用如下spring提供的ElasticsearchTemplate分頁的查詢方式
QueryBuilder builder = QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("code", "123456"));
SearchQuery searchQuery = new NativeSearchQueryBuilder().withIndices("_audit_0221")
.withTypes("_log_0221").withQuery(builder).withPageable(new PageRequest(0, 2)).build();
String srollId = template.scan(searchQuery, 100000, false);
while (true) {
Page<ConductAudits> scroll = template.scroll(srollId, 1000, ConductAudits.class);
if(scroll.getContent().size()==0) {
break;
}
List<ConductAudits> content = scroll.getContent();
for (ConductAudits c: content
) {
System.out.println(JSON.toJSONString(c));
}
// System.out.println(JSON.toJSONString(scroll.getContent()+"\r\n"));
for (ConductAudits conductAudits : scroll.getContent()) {
System.out.println(JSON.toJSONString(conductAudits+"\r\n"));
}
}
6、 scroll和scroll-scan區(qū)別
scroll支持排序油猫,scroll-scan不支持排序稠茂,是按照索引順序返回,可以提高查詢效率情妖。
scroll-scan第一次查詢只支持返回id睬关,沒有結(jié)果。
7毡证、 總結(jié):
es的分頁查詢不支持深度分頁电爹,如果偏要使用要結(jié)合具體業(yè)務(wù)場景進(jìn)行使用。不能當(dāng)成關(guān)系型數(shù)據(jù)庫中的分頁進(jìn)行使用料睛。
要想提高產(chǎn)品體驗(yàn)和查詢效率不能過于依賴技術(shù)丐箩,要結(jié)合需求進(jìn)行分析以提高體驗(yàn),因?yàn)楹芏嗨阉黝惍a(chǎn)品都不支持深度分頁恤煞。
如果在不涉及排序的情況下盡量使用scroll-scan屎勘,它是按照索引順序返回,提高效率居扒。
PS:elasticSearch各個(gè)版本可能都稍有區(qū)別概漱,但是原理相同。本文的很多代碼都是基于es 2.4.6
開源中國社區(qū)喜喂,每日推送最新優(yōu)質(zhì)的技術(shù)類文章瓤摧,涵蓋外文翻譯竿裂,軟件更新,技術(shù)博客等優(yōu)質(zhì)內(nèi)容姻灶。關(guān)注開源社區(qū)簡書號铛绰,每日獲取最新技術(shù)資訊,點(diǎn)擊下鏈接閱讀原文章产喉∥骊↓↓↓
每日一博 | 如何跳過es分頁這個(gè)坑?