1、命令的方式作分頁java
一、常見的分頁方式:from+sizejson
elasticsearch默認採用的分頁方式是from+size的形式,可是在深度分頁的狀況下,這種使用方式的效率是很是低的,好比from=5000,size=10,es須要在各個分片上匹配排序並獲得5000*10條有效數據,而後在結果集中取最後10條數據返回。除了會遇到效率上的問題,還有一個沒法解決的問題是es目前支持最大的skip值是max_result_window默認爲10000,也就是說當from+size > max_result_window時,es將返回錯誤。api
解決方案:緩存
問題描述:好比當客戶線上的es數據出現問題,當分頁到幾百頁的時候,es沒法返回數據,此時爲了恢復正常使用,咱們能夠採用緊急規避的方式,就是將max_result_window的值調至50000。app
curl -XPUT "127.0.0.1:9200/custm/_settings" -d '{ "index" : { "max_result_window" : 50000 } }'
對於上面這種解決方案只是暫時解決問題,當es的使用愈來愈多時,數據量愈來愈大,深度分頁的場景愈來愈複雜時,可使用另外一種分頁方式scroll。curl
二、scroll方式elasticsearch
爲了知足深度分頁的場景,es提供了scroll的方式進行分頁讀取。原理上是對某次查詢生成一個遊標scroll_id,後續的查詢只須要根據這個遊標去取數據,知道結果集中返回的hits字段爲空,就表示遍歷結束。Scroll的做用不是用於實時查詢數據,由於它會對es作屢次請求,不願能作到實時查詢。它的主要做用是用來查詢大量數據或所有數據。工具
使用scroll,每次只能獲取一頁的內容,而後會返回一個scroll_id。根據返回的這個scroll_id能夠不斷地獲取下一頁的內容,因此scroll並不適用於有跳頁的情景ui
使用curl進行深度分頁讀取過程以下:url
一、 先獲取第一個scroll_id,url參數包括/index/type和scroll,scroll字段指定了scroll_id的有效生存時間,過時後會被es自動清理。
[root@master ~]# curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty&scroll=2m' -d' {"query":{"match_all":{}}, "sort": ["_doc"]}'
二、在遍歷時候,拿到上一次遍歷中的_scroll_id,而後帶scroll參數,重複上一次的遍歷步驟,直到返回的數據爲空,表示遍歷完成。
每次都要傳參數scroll,刷新搜索結果的緩存時間,另外不須要指定index和type(不要把緩存的時時間設置太長,佔用內存)後續查詢:
curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/_search/scroll?pretty' -d' { "scroll" : "2m", "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAABWFm43cDd3eERJVHNHMHJzSlNkajdPUHcAAAAAAAAAVxZuN3A3d3hESVRzRzByc0pTZGo3T1B3AAAAAAAAAFsWazlvUFptQnNTdXlmNmZRTl80cVdCdwAAAAAAAABVFm43cDd3eERJVHNHMHJzSlNkajdPUHcAAAAAAAAAWhZrOW9QWm1Cc1N1eWY2ZlFOXzRxV0J3" }'
三、scroll的刪除
刪除全部scroll_id
curl -XDELETE 192.168.200.100:9200/_search/scroll/_all
指定scroll_id刪除:
curl -XDELETE 192.168.200.100:9200/_search/scroll -d '{"scroll_id" : ["cXVlcnlBbmRGZXRjaDsxOzg3OTA4NDpTQzRmWWkwQ1Q1bUlwMjc0WmdIX2ZnOzA7"]}'
三、 search_after 的方式
使用search_after必需要設置from=0。 這裏我使用_id做爲惟一值排序。 咱們在返回的最後一條數據裏拿到sort屬性的值傳入到search_after。
數據:
scroll的方式,官方不建議用於實時的請求(通常用於數據導出),由於每個scroll_id不只會佔用大量的資源,並且會生成歷史快照,對於數據的變動不會反映到快照上。而search_after分頁的方式是根據上一頁的最後一條數據來肯定下一頁的位置,同時再分頁請求的過程當中,若是有索引數據的增刪改查,這些變動也會實時的反映到遊標上。可是須要注意,由於每一頁的數據依賴於上一頁的最後一條數據,因此無法跳頁請求。
爲了找到每一頁最後一條數據,每一個文檔那個必須有一個全局惟一值,官方推薦使用_uuid做爲全局惟一值,固然在業務上的id也能夠。
例如:在下面實例中我先根據id作倒序排列:
curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty' -d' { "size": 2, "from": 0, "sort": [ { "_id": { "order": "desc" } } ] }'
結果:
[root@master ~]# curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty' -d' > { > "size": 2, > "from": 0, > "sort": [ > { > "_id": { > "order": "desc" > } > } > ] > }' { "took" : 7, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : 3, "max_score" : null, "hits" : [ { "_index" : "chuyun", "_type" : "article", "_id" : "3", "_score" : null, "_source" : { "id" : 3, "title" : "《青玉案·元夕》", "content" : "東風夜放花千樹,更吹落,星如雨。寶馬雕車香滿路。鳳簫聲動,玉壺光轉,一晚上魚龍舞。蛾兒雪柳黃金縷,笑語盈盈暗香去。衆裏尋他千百度,驀然回首,那人卻在,燈火闌珊處。", "viewCount" : 786, "createTime" : 1557471088252, "updateTime" : 1557471088252 }, "sort" : [ "3" ] }, { "_index" : "chuyun", "_type" : "article", "_id" : "2", "_score" : null, "_source" : { "id" : 2, "title" : "《蝶戀花》", "content" : "佇倚危樓風細細,望極春愁,黯黯生天際。草色煙光殘照裏,無言誰會憑闌意。擬把疏狂圖一醉,對酒當歌,強樂還無味。衣帶漸寬終不悔,爲伊消得人憔悴。", "viewCount" : null, "createTime" : 1557471087998, "updateTime" : 1557471087998 }, "sort" : [ "2" ] } ] } }
使用sort返回的值搜索下一頁:
curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty' -d' { "size": 2, "from": 0, "search_after": [ 2 ], "sort": [ { "_id": { "order": "desc" } } ] }'
結果:
[root@master ~]# curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty' -d' > { > "size": 2, > "from": 0, > "search_after": [ > 2 > ], > "sort": [ > { > "_id": { > "order": "desc" > } > } > ] > }' { "took" : 12, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : 3, "max_score" : null, "hits" : [ { "_index" : "chuyun", "_type" : "article", "_id" : "1", "_score" : null, "_source" : { "id" : 1, "title" : "《蝶戀花》", "content" : "檻菊愁煙蘭泣露,羅幕輕寒,燕子雙飛去。明月不諳離恨苦,斜光到曉穿朱戶。昨夜西風凋碧樹,獨上高樓,望盡天涯路。欲寄彩箋兼尺素,山長水闊知何處?", "viewCount" : 678, "createTime" : 1557471087754, "updateTime" : 1557471087754 }, "sort" : [ "1" ] } ] } }
2、java api作elasticsearch分頁
按照通常的查詢流程,好比我想查找前10條數據:
一、 客戶端請求發給某個節點
二、 節點轉發給各個分片,查詢每一個分片上的前10條數據
三、 結果返回給節點,整合數據,提取前10條
四、 返回給請求客戶端
然而當我想查詢第10條到20條的時候,就須要用到分頁查詢。
工具類:
** * 構建elasticsrarch client */ public class LowClientUtil { private static TransportClient client; public TransportClient CreateClient() throws Exception { // 先構建client System.out.println("11111111111"); Settings settings=Settings.builder() .put("cluster.name","elasticsearch1") .put("client.transport.ignore_cluster_name", true) //若是集羣名不對,也能鏈接 .build(); //建立Client TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress( new TransportAddress( InetAddress.getByName( "192.168.200.100"), 9300)); return client; } }
準備數據:
/** * 準備數據 * @throws Exception */ public static void createDocument100() throws Exception { for (int i = 1; i <= 100; i++) { try { HashMap<String, Object> map = new HashMap<>(); map.put("title", "第" + i + "本書"); map.put("author", "做者" + i); map.put("id", i); map.put("message", i + "是英國物理學家斯蒂芬·霍金創做的科學著做,首次出版於1988年。全書"); IndexResponse response = client.prepareIndex("blog2", "article") .setSource(map) .get(); // 索引名稱 String _index = response.getIndex(); // 類型 String _type = response.getType(); // 文檔ID String _id = response.getId(); // 版本 long _version = response.getVersion(); // 返回的操做狀態 RestStatus status = response.status(); System.out.println("索引名稱:" + _index + " " + "類型 :" + _type + " 文檔ID:" + _id + " 版本 :" + _version + " 返回的操做狀態:" + status ); } catch (Exception e) { e.printStackTrace(); } } }
淺分頁:from_size
原理:就好比查詢前20條數據,而後截斷前10條,只返回10-20條。
/** * from-size searchRequestBuilder 的 setFrom【從0開始】 和 setSize【查詢多少條記錄】方法實現 * */ public static void sortPages(){ // 搜索數據 SearchRequestBuilder searchRequestBuilder = client.prepareSearch("blog2").setTypes("article") .setQuery(QueryBuilders.matchAllQuery());//默認每頁10條記錄 final long totalHits = searchRequestBuilder.get().getHits().getTotalHits();//總條數 final int pageDocument = 10 ;//每頁顯示多少條 final long totalPage = totalHits / pageDocument;//總共分多少頁 for(int i=1;i<=totalPage;i++){ System.out.println("=====================當前打印的是第 :"+i+" 頁=============="); //setFrom():從第幾條開始檢索,默認是0。 //setSize():查詢多少條文檔。 searchRequestBuilder.setFrom(i*pageDocument).setSize(pageDocument); SearchResponse searchResponse = searchRequestBuilder.get(); SearchHits hits = searchResponse.getHits(); Iterator<SearchHit> iterator = hits.iterator(); while (iterator.hasNext()) { SearchHit searchHit = iterator.next(); // 每一個查詢對象 System.out.println(searchHit.getSourceAsString()); // 獲取字符串格式打印 } } }
使用scroll深分頁:
對於上面介紹的淺分頁(from-size),當Elasticsearch響應請求時,它必須肯定docs的順序,排列響應結果。
若是請求的頁數較少(假設每頁20個docs), Elasticsearch不會有什麼問題,可是若是頁數較大時,好比請求第20頁,Elasticsearch不得不取出第1頁到第20頁的全部docs,再去除第1頁到第19頁的docs,獲得第20頁的docs。
解決的方式就是使用scroll,scroll就是維護了當前索引段的一份快照信息--緩存(這個快照信息是你執行這個scroll查詢時的快照)在這個查詢後的任何新索引進來的數據,都不會在這個快照中查詢到。可是它相對於from和size,不是查詢全部數據而後剔除不要的部分,而是記錄一個讀取的位置,保證下一次快速繼續讀取。
能夠把 scroll 分爲初始化和遍歷兩步:
1、初始化時將全部符合搜索條件的搜索結果緩存起來,能夠想象成快照;
2、遍歷時,從這個快照裏取數據,也就是說,在初始化後對索引插入、刪除、更新數據都不會影響遍歷結果
public static void scrollPages(){ //獲取Client對象,設置索引名稱,搜索類型(SearchType.SCAN)[5.4移除,對於java代碼,直接返回index順序,不對結果排序],搜索數量,發送請求 SearchResponse searchResponse = client .prepareSearch("blog2") .setSearchType(SearchType.DEFAULT)//執行檢索的類別 .setSize(10).setScroll(new TimeValue(1000)).execute() .actionGet();//注意:首次搜索並不包含數據 //獲取總數量 long totalCount=searchResponse.getHits().getTotalHits(); int page=(int)totalCount/(10);//計算總頁數 System.out.println("總頁數: ================="+page+"============="); for (int i = 1; i <= page; i++) { System.out.println("=========================頁數:"+i+"=================="); searchResponse = client .prepareSearchScroll(searchResponse.getScrollId())//再次發送請求,並使用上次搜索結果的ScrollId .setScroll(new TimeValue(1000)).execute() .actionGet(); SearchHits hits = searchResponse.getHits(); for(SearchHit searchHit : hits){ System.out.println(searchHit.getSourceAsString());// 獲取字符串格式打印 } } }