Elasticsearch分页解决方案

一、命令的方式做分页

1、常见的分页方式:from+size

elasticsearch默认采用的分页方式是from+size的形式,但是在深度分页的情况下,这种使用方式的效率是非常低的,比如from=5000,size=10,es需要在各个分片上匹配排序并得到5000*10条有效数据,然后在结果集中取最后10条数据返回。除了会遇到效率上的问题,还有一个无法解决的问题是es目前支持最大的skip值是max_result_window默认为10000,也就是说当from+size > max_result_window时,es将返回错误。

解决方案:

        问题描述:比如当客户线上的es数据出现问题,当分页到几百页的时候,es无法返回数据,此时为了恢复正常使用,我们可以采用紧急规避的方式,就是将max_result_window的值调至50000。

1curl -XPUT "127.0.0.1:9200/custm/_settings" -d 2'{ 3 "index" : { 4 "max_result_window" : 50000 5 } 6}'

对于上面这种解决方案只是暂时解决问题,当es的使用越来越多时,数据量越来越大,深度分页的场景越来越复杂时,可以使用另一种分页方式scroll。

2、scroll方式

为了满足深度分页的场景,es提供了scroll的方式进行分页读取。原理上是对某次查询生成一个游标scroll_id,后续的查询只需要根据这个游标去取数据,知道结果集中返回的hits字段为空,就表示遍历结束。Scroll的作用不是用于实时查询数据,因为它会对es做多次请求,不肯能做到实时查询。它的主要作用是用来查询大量数据或全部数据。

使用scroll,每次只能获取一页的内容,然后会返回一个scroll_id。根据返回的这个scroll_id可以不断地获取下一页的内容,所以scroll并不适用于有跳页的情景

使用curl进行深度分页读取过程如下:

1、 先获取第一个scroll_id,url参数包括/index/type和scroll,scroll字段指定了scroll_id的有效生存时间,过期后会被es自动清理。

1[root@master ~]# curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty&scroll=2m' -d' 2{"query":{"match_all":{}}, "sort": ["_doc"]}'

2、在遍历时候,拿到上一次遍历中的_scroll_id,然后带scroll参数,重复上一次的遍历步骤,直到返回的数据为空,表示遍历完成。
每次都要传参数scroll,刷新搜索结果的缓存时间,另外不需要指定index和type(不要把缓存的时时间设置太长,占用内存)后续查询:

1curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/_search/scroll?pretty' -d' 2{ 3 "scroll" : "2m", 4 "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAABWFm43cDd3eERJVHNHMHJzSlNkajdPUHcAAAAAAAAAVxZuN3A3d3hESVRzRzByc0pTZGo3T1B3AAAAAAAAAFsWazlvUFptQnNTdXlmNmZRTl80cVdCdwAAAAAAAABVFm43cDd3eERJVHNHMHJzSlNkajdPUHcAAAAAAAAAWhZrOW9QWm1Cc1N1eWY2ZlFOXzRxV0J3" 5}'

3、scroll的删除

删除所有scroll_id

curl -XDELETE 192.168.200.100:9200/_search/scroll/_all

指定scroll_id删除:

1curl -XDELETE 192.168.200.100:9200/_search/scroll -d 2'{"scroll_id" : ["cXVlcnlBbmRGZXRjaDsxOzg3OTA4NDpTQzRmWWkwQ1Q1bUlwMjc0WmdIX2ZnOzA7"]}'

3、 search_after 的方式

1使用search_after必须要设置from=02 3这里我使用_id作为唯一值排序。 4 5我们在返回的最后一条数据里拿到sort属性的值传入到search_after。

数据:

 

scroll的方式,官方不建议用于实时的请求(一般用于数据导出),因为每一个scroll_id不仅会占用大量的资源,而且会生成历史快照,对于数据的变更不会反映到快照上。而search_after分页的方式是根据上一页的最后一条数据来确定下一页的位置,同时再分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。但是需要注意,因为每一页的数据依赖于上一页的最后一条数据,所以没法跳页请求。

为了找到每一页最后一条数据,每个文档那个必须有一个全局唯一值,官方推荐使用_uuid作为全局唯一值,当然在业务上的id也可以。

例如:在下面实例中我先根据id做倒序排列:

1curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty' -d' 2{ 3 "size": 2, 4 "from": 0, 5 "sort": [ 6 { 7 "_id": { 8 "order": "desc" 9 } 10 } 11 ] 12}'

结果:

1[root@master ~]# curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty' -d' 2> { 3> "size": 2, 4> "from": 0, 5> "sort": [ 6> { 7> "_id": { 8> "order": "desc" 9> } 10> } 11> ] 12> }' 13{ 14 "took" : 7, 15 "timed_out" : false, 16 "_shards" : { 17 "total" : 5, 18 "successful" : 5, 19 "skipped" : 0, 20 "failed" : 0 21 }, 22 "hits" : { 23 "total" : 3, 24 "max_score" : null, 25 "hits" : [ 26 { 27 "_index" : "chuyun", 28 "_type" : "article", 29 "_id" : "3", 30 "_score" : null, 31 "_source" : { 32 "id" : 3, 33 "title" : "《青玉案·元夕》", 34 "content" : "东风夜放花千树,更吹落,星如雨。宝马雕车香满路。凤箫声动,玉壶光转,一夜鱼龙舞。蛾儿雪柳黄金缕,笑语盈盈暗香去。众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。", 35 "viewCount" : 786, 36 "createTime" : 1557471088252, 37 "updateTime" : 1557471088252 38 }, 39 "sort" : [ 40 "3" 41 ] 42 }, 43 { 44 "_index" : "chuyun", 45 "_type" : "article", 46 "_id" : "2", 47 "_score" : null, 48 "_source" : { 49 "id" : 2, 50 "title" : "《蝶恋花》", 51 "content" : "伫倚危楼风细细,望极春愁,黯黯生天际。草色烟光残照里,无言谁会凭阑意。拟把疏狂图一醉,对酒当歌,强乐还无味。衣带渐宽终不悔,为伊消得人憔悴。", 52 "viewCount" : null, 53 "createTime" : 1557471087998, 54 "updateTime" : 1557471087998 55 }, 56 "sort" : [ 57 "2" 58 ] 59 } 60 ] 61 } 62}

使用sort返回的值搜索下一页:

1curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty' -d' 2{ 3 "size": 2, 4 "from": 0, 5 "search_after": [ 6 2 7 ], 8 "sort": [ 9 { 10 "_id": { 11 "order": "desc" 12 } 13 } 14 ] 15}'

结果:

1[root@master ~]# curl -H "Content-Type: application/json" -XGET '192.168.200.100:9200/chuyun/_search?pretty' -d' 2> { 3> "size": 2, 4> "from": 0, 5> "search_after": [ 6> 2 7> ], 8> "sort": [ 9> { 10> "_id": { 11> "order": "desc" 12> } 13> } 14> ] 15> }' 16{ 17 "took" : 12, 18 "timed_out" : false, 19 "_shards" : { 20 "total" : 5, 21 "successful" : 5, 22 "skipped" : 0, 23 "failed" : 0 24 }, 25 "hits" : { 26 "total" : 3, 27 "max_score" : null, 28 "hits" : [ 29 { 30 "_index" : "chuyun", 31 "_type" : "article", 32 "_id" : "1", 33 "_score" : null, 34 "_source" : { 35 "id" : 1, 36 "title" : "《蝶恋花》", 37 "content" : "槛菊愁烟兰泣露,罗幕轻寒,燕子双飞去。明月不谙离恨苦,斜光到晓穿朱户。昨夜西风凋碧树,独上高楼,望尽天涯路。欲寄彩笺兼尺素,山长水阔知何处?", 38 "viewCount" : 678, 39 "createTime" : 1557471087754, 40 "updateTime" : 1557471087754 41 }, 42 "sort" : [ 43 "1" 44 ] 45 } 46 ] 47 } 48}

二、java api做elasticsearch分页

按照一般的查询流程,比如我想查找前10条数据:

1、 客户端请求发给某个节点

2、 节点转发给各个分片,查询每个分片上的前10条数据

3、 结果返回给节点,整合数据,提取前10条

4、 返回给请求客户端

然而当我想查询第10条到20条的时候,就需要用到分页查询。

工具类:

1** 2 * 构建elasticsrarch client 3 */ 4public class LowClientUtil { 5 private static TransportClient client; 6 public TransportClient CreateClient() throws Exception { 7 // 先构建client 8 System.out.println("11111111111"); 9 Settings settings=Settings.builder() 10 .put("cluster.name","elasticsearch1") 11 .put("client.transport.ignore_cluster_name", true) //如果集群名不对,也能连接 12 .build(); 13 //创建Client 14 TransportClient client = new PreBuiltTransportClient(settings) 15 .addTransportAddress( 16 new TransportAddress( 17 InetAddress.getByName( 18 "192.168.200.100"), 19 9300)); 20 return client; 21 } 22}

准备数据:

1/** 2 * 准备数据 3 * @throws Exception 4 */ 5public static void createDocument100() throws Exception { 6 for (int i = 1; i <= 100; i++) { 7 try { 8 HashMap<String, Object> map = new HashMap<>(); 9 map.put("title", "第" + i + "本书"); 10 map.put("author", "作者" + i); 11 map.put("id", i); 12 map.put("message", i + "是英国物理学家斯蒂芬·霍金创作的科学著作,首次出版于1988年。全书"); 13 IndexResponse response = client.prepareIndex("blog2", "article") 14 .setSource(map) 15 .get(); 16 // 索引名称 17 String _index = response.getIndex(); 18 // 类型 19 String _type = response.getType(); 20 // 文档ID 21 String _id = response.getId(); 22 // 版本 23 long _version = response.getVersion(); 24 // 返回的操作状态 25 RestStatus status = response.status(); 26 System.out.println("索引名称:" + _index + 27 " " + "类型 :" + _type + " 文档ID:" + _id + 28 " 版本 :" + _version + " 返回的操作状态:" + status ); 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } 32 } 33}

浅分页:from_size

原理:就比如查询前20条数据,然后截断前10条,只返回10-20条。

1/** 2 * from-size 3 searchRequestBuilder 的 setFrom【从0开始】 和 setSize【查询多少条记录】方法实现 4 * */ 5public static void sortPages(){ 6 // 搜索数据 7 SearchRequestBuilder searchRequestBuilder = client.prepareSearch("blog2").setTypes("article") 8 .setQuery(QueryBuilders.matchAllQuery());//默认每页10条记录 9 final long totalHits = searchRequestBuilder.get().getHits().getTotalHits();//总条数 10 final int pageDocument = 10 ;//每页显示多少条 11 final long totalPage = totalHits / pageDocument;//总共分多少页 12 for(int i=1;i<=totalPage;i++){ 13 System.out.println("=====================当前打印的是第 :"+i+" 页=============="); 14 //setFrom():从第几条开始检索,默认是0。 15 //setSize():查询多少条文档。 16 searchRequestBuilder.setFrom(i*pageDocument).setSize(pageDocument); 17 SearchResponse searchResponse = searchRequestBuilder.get(); 18 SearchHits hits = searchResponse.getHits(); 19 Iterator<SearchHit> iterator = hits.iterator(); 20 while (iterator.hasNext()) { 21 SearchHit searchHit = iterator.next(); // 每个查询对象 22 System.out.println(searchHit.getSourceAsString()); // 获取字符串格式打印 23 } 24 } 25}

使用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、遍历时,从这个快照里取数据,也就是说,在初始化后对索引插入、删除、更新数据都不会影响遍历结果

1public static void scrollPages(){ 2 //获取Client对象,设置索引名称,搜索类型(SearchType.SCAN)[5.4移除,对于java代码,直接返回index顺序,不对结果排序],搜索数量,发送请求 3 SearchResponse searchResponse = client 4 .prepareSearch("blog2") 5 .setSearchType(SearchType.DEFAULT)//执行检索的类别 6 .setSize(10).setScroll(new TimeValue(1000)).execute() 7 .actionGet();//注意:首次搜索并不包含数据 8 //获取总数量 9 long totalCount=searchResponse.getHits().getTotalHits(); 10 int page=(int)totalCount/(10);//计算总页数 11 System.out.println("总页数: ================="+page+"============="); 12 for (int i = 1; i <= page; i++) { 13 System.out.println("=========================页数:"+i+"=================="); 14 searchResponse = client 15 .prepareSearchScroll(searchResponse.getScrollId())//再次发送请求,并使用上次搜索结果的ScrollId 16 .setScroll(new TimeValue(1000)).execute() 17 .actionGet(); 18 SearchHits hits = searchResponse.getHits(); 19 for(SearchHit searchHit : hits){ 20 System.out.println(searchHit.getSourceAsString());// 获取字符串格式打印 21 } 22 } 23}

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )