有時候咱們可能想要讀取整個es索引的數據或者其中的大部分數據,來重建索引或者加工數據,相信大多數人都會說這很簡單啊直接用from+size就能搞定,但實際狀況是from+size的分頁方法不適合用於這種全量數據的抽取,越到後面這種方法的性能就越低,這也是es裏面爲何限制了單次查詢結果的數據不能超過1萬條數據的緣由。java
es裏面提供了scroll的方式來全量讀取索引數據其與數據庫裏面的遊標(cursor)的概念很是相似,使用scroll讀取數據的時候,只須要發送一次查詢請求,而後es服務端會生成一個當前請求索引的快照數據集,接着咱們每次經過scrollId來讀取指定大小的批次數據,直到把整個索引的數據讀取完畢。node
這裏面須要注意,當索引快照集生成的時候,其實在es內部維護了一個search context的上下文,這個上下文在指定的時間間隔內是隻讀的和不可變的,也就是隻要它生成,那麼後續你的添加,刪除,更新操做的數據都不會被感知。算法
下面看下如何使用:數據庫
(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請求時會維護一個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的認知。