如何優雅的全量讀取Elasticsearch索引裏面的數據

(一)scroll的介紹

有時候咱們可能想要讀取整個es索引的數據或者其中的大部分數據,來重建索引或者加工數據,相信大多數人都會說這很簡單啊直接用from+size就能搞定,但實際狀況是from+size的分頁方法不適合用於這種全量數據的抽取,越到後面這種方法的性能就越低,這也是es裏面爲何限制了單次查詢結果的數據不能超過1萬條數據的緣由。java

es裏面提供了scroll的方式來全量讀取索引數據其與數據庫裏面的遊標(cursor)的概念很是相似,使用scroll讀取數據的時候,只須要發送一次查詢請求,而後es服務端會生成一個當前請求索引的快照數據集,接着咱們每次經過scrollId來讀取指定大小的批次數據,直到把整個索引的數據讀取完畢。node

這裏面須要注意,當索引快照集生成的時候,其實在es內部維護了一個search context的上下文,這個上下文在指定的時間間隔內是隻讀的和不可變的,也就是隻要它生成,那麼後續你的添加,刪除,更新操做的數據都不會被感知。算法

(二)scroll的使用

下面看下如何使用:數據庫

(1)要使用scroll方式來讀取數據,須要兩步操做,第一步先作一個search context的初始化操做,以下命令:api

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

注意上面url裏面的scroll=1m表明,這個search context只保留一分鐘的有效期。數組

(2)在第一步操做裏面咱們可以獲取一個scrollId,而後後面的每一個讀取都會獲得一個scrollId,咱們在讀取next批次的數據要把這個scrollId回傳,以下:curl

curl -XGET  'localhost:9200/_search/scroll'  -d'
{
    "scroll" : "1m", 
    "scroll_id" : "c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1" 
}
'

或者經過search lite api的方式:elasticsearch

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

這樣依次循環讀取直到searchHits數組爲空的狀況下就表明數據讀取完畢。性能

同理聚合的scroll請求,也是如此,但聚合請求的數據體只會在初始化的search裏面存在,這一點須要注意,不過聚合請求的scroll通常沒有這種應用場景,畢竟聚合後的結果通常都是少了好幾個數量級的。優化

此外scroll請求還能夠添加一個或多個排序字段,若是你讀取的索引數據徹底忽略它的順序,那麼咱們還可使用doc字段排序來提高性能。

curl -XGET 'localhost:9200/_search?scroll=1m' -d '
{
  "sort": [
    "_doc"
  ]
}
'

ok,再補充下再java api裏面如何全量讀取es索引數據的方法:

`           //指定一個index和type    
            SearchRequestBuilder search = client.prepareSearch("active2018").setTypes("active");
            //使用原生排序優化性能
            search.addSort("_doc", SortOrder.ASC);
            //設置每批讀取的數據量
            search.setSize(100);
            //默認是查詢全部
            search.setQuery(QueryBuilders.queryStringQuery("*:*"));
            //設置 search context 維護1分鐘的有效期
            search.setScroll(TimeValue.timeValueMinutes(1));

            //得到首次的查詢結果
            SearchResponse scrollResp=search.get();
            //打印命中數量
            System.out.println("命中總數量:"+scrollResp.getHits().getTotalHits());
            //打印計數
            int count=1;
            do {
                System.out.println("第"+count+"次打印數據:");
                //讀取結果集數據
                for (SearchHit hit : scrollResp.getHits().getHits()) {
                    System.out.println(hit.getSource())  ;
                }
                count++;
                //將scorllId循環傳遞
                scrollResp = client.prepareSearchScroll(scrollResp.getScrollId())
                .setScroll(TimeValue.timeValueMinutes(1))
                .execute().actionGet();
                
                //當searchHits的數組爲空的時候結束循環,至此數據所有讀取完畢
            } while(scrollResp.getHits().getHits().length != 0);

(三)刪除無用的scroll

上文提到scroll請求時會維護一個search context快照集,這是如何作到的? 經過前面的幾篇文章(點底部菜單欄能夠看到),咱們知道es在寫入數據時,會在內存中不斷的生成segment,而後有一個merge線程,會不斷的合併小segment到更大的segment裏面,而後再刪除舊的segment,來減小es對系統資源的佔用, 尤爲是文件句柄,那麼維護一個時間段內的索引快照,則意味着這段時間內的全部segment不能被合併,不然就破壞了快照的靜態性,這樣以來暫時不能被合併的小segment會佔系統大量的文件句柄和系統資源,因此scroll的方式必定是離線使用的而不是提供給近實時使用的。

咱們須要養成一個好習慣,當咱們用完以後應該手動清除scroll,雖然search context超時也會自動清除。

es中提供了能夠查看當前系統中有多少個open search context的api命令:

curl -XGET localhost:9200/_nodes/stats/indices/search?pretty

下面看下刪除scrollId的方式

(1)刪除一個scrollId

DELETE /_search/scroll
{
    "scroll_id" : "UQlNsdDcwakFMNjU1QQ=="
}

(2)刪除多個scrollId

DELETE /_search/scroll
{
    "scroll_id" : [
      "aNmRMaUhiQlZkMWFB==",
      "qNmRMaUhiQlZkMWFB=="
    ]
}

(3)刪除全部的scrollId

DELETE /_search/scroll/_all

(4)search lite api的刪除多個scrollId用法

DELETE /_search/scroll/aNmRMaUhiQlZkMWFB==,qNmRMaUhiQlZkMWFB==

上面的全部的功能在es2.3.4的版本中已經驗證過,此外在es5.x以後的版本中,還增長了一個分片讀取索引的功能,經過分片支持並行的讀取方式,來提升導出效率:

一個例子以下:

GET /twitter/_search?scroll=1m
{
    "slice": {
        "id": 0, 
        "max": 2 
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
GET /twitter/_search?scroll=1m
{
    "slice": {
        "id": 1,
        "max": 2
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

注意上面的slice參數,裏面id字段表明當前讀取的按個分片的數據,max參數表明咱們將整個索引數據切分紅分片的個數,默認的分片算法:

slice(doc) = floorMod(hashCode(doc._uid), max)

從上面能看到是基於uid字段的hashCode與分片的最大個數求模得出來的,注意floorMod方法與%求模在都是正整數的狀況下結果是同樣的。

slice字段還能夠加入自定義的字段參與分片,好比基於日期字段:

"slice": {
        "field": "date",
        "id": 0,
        "max": 10
    }

參與分片的字段必須是數值字段並須要開啓doc value,另外設置的max數量最好不要超過shard的個數,不然查詢性能會降低,默認es對每一個索引限制的最大分片量是1024,不過在setting裏面經過設置index.max_slices_per_scroll參數改變。

(四)總結

本篇文章介紹瞭如何優雅的全量讀取es的索引數據以及它的一些原理和注意事項,瞭解這些有助於咱們在平常工做中更好的使用es,從而提高咱們對es的認知。

相關文章
相關標籤/搜索