每日一博 | 如何跳過es分頁這個(gè)坑?

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è)坑?

關(guān)注開源中國簡書號曾沈,獲取最新技術(shù)資訊这嚣!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市塞俱,隨后出現(xiàn)的幾起案子姐帚,更是在濱河造成了極大的恐慌,老刑警劉巖障涯,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罐旗,死亡現(xiàn)場離奇詭異,居然都是意外死亡唯蝶,警方通過查閱死者的電腦和手機(jī)九秀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粘我,“玉大人鼓蜒,你說我怎么就攤上這事≌髯郑” “怎么了都弹?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長匙姜。 經(jīng)常有香客問我畅厢,道長,這世上最難降的妖魔是什么氮昧? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任框杜,我火速辦了婚禮,結(jié)果婚禮上郭计,老公的妹妹穿的比我還像新娘霸琴。我一直安慰自己,他們只是感情好昭伸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布梧乘。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪选调。 梳的紋絲不亂的頭發(fā)上夹供,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音仁堪,去河邊找鬼哮洽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弦聂,可吹牛的內(nèi)容都是我干的鸟辅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼莺葫,長吁一口氣:“原來是場噩夢啊……” “哼匪凉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捺檬,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤再层,失蹤者是張志新(化名)和其女友劉穎扣讼,沒想到半個(gè)月后沿腰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锦溪,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡竿秆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乍构。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片物喷。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡起宽,死狀恐怖职车,靈堂內(nèi)的尸體忽然破棺而出瘫俊,到底是詐尸還是另有隱情鹊杖,我是刑警寧澤悴灵,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站骂蓖,受9級特大地震影響积瞒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜登下,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一茫孔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧被芳,春花似錦缰贝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春赞弥,著一層夾襖步出監(jiān)牢的瞬間毅整,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工绽左, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悼嫉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓拼窥,卻偏偏與公主長得像戏蔑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子鲁纠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容