Elasticsearch——分頁查詢From&Size VS scroll

Elasticsearch中數據都存儲在分片中,當執行搜索時每一個分片獨立搜索後,數據再通過整合返回。那麼,若是要實現分頁查詢該怎麼辦呢?
更多內容參考Elasticsearch資料彙總php

按照通常的查詢流程來講,若是我想查詢前10條數據:html

  • 1 客戶端請求發給某個節點
  • 2 節點轉發給個個分片,查詢每一個分片上的前10條
  • 3 結果返回給節點,整合數據,提取前10條
  • 4 返回給請求客戶端

那麼當我想要查詢第10條到第20條的數據該怎麼辦呢?這個時候就用到分頁查詢了。java

from-size"淺"分頁

"淺"分頁的概念是小博主本身定義的,能夠理解爲簡單意義上的分頁。它的原理很簡單,就是查詢前20條數據,而後截斷前10條,只返回10-20的數據。這樣其實白白浪費了前10條的查詢。curl

查詢的方法如:elasticsearch

{
    "from" : 0, "size" : 10,
    "query" : {
        "term" : { "user" : "kimchy" }
    }
}

其中,from定義了目標數據的偏移值,size定義當前返回的事件數目。
默認from爲0,size爲10,即全部的查詢默認僅僅返回前10條數據。maven

作過測試,越日後的分頁,執行的效率越低。
經過下圖能夠看出,刨去一些異常的數據,整體上仍是會隨着from的增長,消耗時間也會增長。並且數據量越大,效果越明顯!ide

也就是說,分頁的偏移值越大,執行分頁查詢時間就會越長!

scroll「深」分頁

相對於from和size的分頁來講,使用scroll能夠模擬一個傳統數據的遊標,記錄當前讀取的文檔信息位置。這個分頁的用法,不是爲了實時查詢數據,而是爲了一次性查詢大量的數據(甚至是所有的數據)。工具

由於這個scroll至關於維護了一份當前索引段的快照信息,這個快照信息是你執行這個scroll查詢時的快照。在這個查詢後的任何新索引進來的數據,都不會在這個快照中查詢到。可是它相對於from和size,不是查詢全部數據而後剔除不要的部分,而是記錄一個讀取的位置,保證下一次快速繼續讀取。性能

API使用方法如:測試

curl -XGET 'localhost:9200/twitter/tweet/_search?scroll=1m' -d '
{
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
'

會自動返回一個_scroll_id,經過這個id能夠繼續查詢(實際上這個ID會很長哦!):

curl -XGET  'localhost:9200/_search/scroll?scroll=1m&scroll_id=c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1'

注意,我在使用1.4版本的ES時,只支持把參數放在URL路徑裏面,不支持在JSON body中使用。

有個頗有意思的事情,細心的會發現,這個ID實際上是經過base64編碼的:

cXVlcnlUaGVuRmV0Y2g7MTY7MjI3NTp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyNzQ6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjgwOnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI4MTp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyODM6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjgyOnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI4Njp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyODc6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjg5OnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI4NDp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyODU6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjg4OnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI3Njp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyNzc6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjc4OnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI3OTp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzA7

若是使用解碼工具能夠看到:

queryThenFetch;16;2275:vtXKJ8lnQImdiwcDtPT-kA;2274:vtXKJ8lnQImdiwcDtPT-kA;2280:vtXKJ8lnQImdiwcDtPT-kA;2281:vtXKJ8lnQImdiwcDtPT-kA;2283:vtXKJ8lnQImdiwcDtPT-kA;2282:vtXKJ8lnQImdiwcDtPT-kA;2286:vtXKJ8lnQImdiwcDtPT-kA;2287:vtXKJ8lnQImdiwcDtPT-kA;2289:vtXKJ8lnQImdiwcDtPT-kA;2284:vtXKJ8lnQImdiwcDtPT-kA;2285:vtXKJ8lnQImdiwcDtPT-kA;2288:vtXKJ8lnQImdiwcDtPT-kA;2276:vtXKJ8lnQImdiwcDtPT-kA;2277:vtXKJ8lnQImdiwcDtPT-kA;2278:vtXKJ8lnQImdiwcDtPT-kA;2279:vtXKJ8lnQImdiwcDtPT-kA;0;

雖然搞不清楚裏面是什麼內容,可是看到了一堆規則的鍵值對,老是讓人興奮一下!

測試from&size VS scroll的性能

首先呢,須要在java中引入elasticsearch-jar,好比使用maven:

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>1.4.4</version>
</dependency>

而後初始化一個client對象:

private static TransportClient client;
    private static String INDEX = "index_name";
    private static String TYPE = "type_name";
    
    public static TransportClient init(){
        Settings settings = ImmutableSettings.settingsBuilder()
                 .put("client.transport.sniff", true)
                 .put("cluster.name", "cluster_name")
                 .build();
        client = new TransportClient(settings).addTransportAddress(new InetSocketTransportAddress("localhost",9300));
        return client;
    }
    public static void main(String[] args) {
        TransportClient client = init();
        //這樣就可使用client執行查詢了
    }

而後就是建立兩個查詢過程了 ,下面是from-size分頁的執行代碼:

System.out.println("from size 模式啓動!");
Date begin = new Date();
long count = client.prepareCount(INDEX).setTypes(TYPE).execute().actionGet().getCount();
SearchRequestBuilder requestBuilder = client.prepareSearch(INDEX).setTypes(TYPE).setQuery(QueryBuilders.matchAllQuery());
for(int i=0,sum=0; sum<count; i++){
    SearchResponse response = requestBuilder.setFrom(i).setSize(50000).execute().actionGet();
    sum += response.getHits().hits().length;
    System.out.println("總量"+count+" 已經查到"+sum);
}
Date end = new Date();
System.out.println("耗時: "+(end.getTime()-begin.getTime()));

下面是scroll分頁的執行代碼,注意啊!scroll裏面的size是相對於每一個分片來講的,因此實際返回的數量是:分片的數量*size

System.out.println("scroll 模式啓動!");
begin = new Date();
SearchResponse scrollResponse = client.prepareSearch(INDEX)
    .setSearchType(SearchType.SCAN).setSize(10000).setScroll(TimeValue.timeValueMinutes(1)) 
    .execute().actionGet();  
count = scrollResponse.getHits().getTotalHits();//第一次不返回數據
for(int i=0,sum=0; sum<count; i++){
    scrollResponse = client.prepareSearchScroll(scrollResponse.getScrollId())  
        .setScroll(TimeValue.timeValueMinutes(8))  
    .execute().actionGet();
    sum += scrollResponse.getHits().hits().length;
    System.out.println("總量"+count+" 已經查到"+sum);
}
end = new Date();
System.out.println("耗時: "+(end.getTime()-begin.getTime()));

我這裏總的數據有33萬多,分別以每頁5000,10000,50000的數據量請求,獲得以下的執行時間:

能夠看到僅僅30萬,就相差接近一倍的性能,更況且是現在的大數據環境...所以,若是想要對全量數據進行操做,快換掉fromsize,使用scroll吧!

參考

1 簡書:elasticsearch 的滾動(scroll)
2 16php:Elasticsearch Scroll API詳解
3 elastic:from-size查詢
4 elastic:scroll query

相關文章
相關標籤/搜索