Elasticsearch 入门教程 – 分页搜索

按照一般的查询流程来说,如果我想查询前10条数据:

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

  • 2 节点转发给个个分片,查询每个分片上的前10条

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

  • 4 返回给请求客户端

那么当我想要查询第10条到第20条的数据该怎么办呢?这个时候就用到分页查询了。

常用的语法:

   GET /_search?size=10

   GET /_search?size=10&from=0

   GET /_search?size=10&from=20

Java 客户端:

SearchResponse response = client.prepareSearch("ecommerce")
				.setTypes("product")
			    .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
			    .setQuery(QueryBuilders.termQuery("name","jiajieshi"))    // Query
			    .setFrom(0).setSize(1).setExplain(true)
			    .execute()
			    .actionGet();
		
		for (SearchHit searchHit : response.getHits()) {
			Map source = searchHit.getSource();
			System.out.println(source.toString());
		}

分页的搜索原理:

image.png

其中,from定义了目标数据的偏移值,size定义当前返回的事件数目。
默认from为0,size为10,即所有的查询默认仅仅返回前10条数据。

做过测试,越往后的分页,执行的效率越低。
通过下图可以看出,刨去一些异常的数据,总体上还是会随着from的增加,消耗时间也会增加。而且数据量越大,效果越明显!

image.png

也就是说,分页的偏移值越大,执行分页查询时间就会越长!

    相对于from和size的分页来说,使用scroll可以模拟一个传统数据的游标,记录当前读取的文档信息位置。这个分页的用法,不是为了实时查询数据,而是为了一次性查询大量的数据(甚至是全部的数据)。

     因为这个scroll相当于维护了一份当前索引段的快照信息,这个快照信息是你执行这个scroll查询时的快照。在这个查询后的任何新索引进来的数据,都不会在这个快照中查询到。但是它相对于from和size,不是查询所有数据然后剔除不要的部分,而是记录一个读取的位置,保证下一次快速继续读取。

API使用方法如:

GET /my_index/my_type/_search?scroll=1m
{
  "query": {
    "match_all": {}
  },
  "sort": [ "_doc" ],
  "size": 3
}

image.png

会自动返回一个_scroll_id,通过这个id可以继续查询(实际上这个ID会很长哦!):

GET /_search/scroll
{
    "scroll": "1m", 
    "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAB3sWTEJxekNyanZTOFdDWE5CV0Nic3J1QQ=="
}

测试from&size VS scroll的性能

然后初始化一个client对象:

/**
		 * 1:通过 setting对象来指定集群配置信息
		 */
		// 先构建client
		Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();

		/**
		 * 2:创建客户端 通过setting来创建,若不指定则默认链接的集群名为elasticsearch 链接使用tcp协议即9300
		 */
		TransportClient client = new PreBuiltTransportClient(settings);
		TransportAddress transportAddress = new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300);
		client.addTransportAddresses(transportAddress);

		/**
		 * 3:查看集群信息 注意我的集群结构是: 131的elasticsearch.yml中指定为主节点不能存储数据,
		 * 128的elasticsearch.yml中指定不为主节点只能存储数据。 所有控制台只打印了192.168.79.128,只能获取数据节点
		 * 
		 */
		List<DiscoveryNode> connectedNodes = client.connectedNodes();
		for (DiscoveryNode node : connectedNodes) {
			System.out.println(node.getHostAddress());
		}

然后就是创建两个查询过程了 ,下面是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吧!

资料:

http://lxwei.github.io/posts/%E4%BD%BF%E7%94%A8scroll%E5%AE%9E%E7%8E%B0Elasticsearch%E6%95%B0%E6%8D%AE%E9%81%8D%E5%8E%86%E5%92%8C%E6%B7%B1%E5%BA%A6%E5%88%86%E9%A1%B5.html

http://www.cnblogs.com/xing901022/p/5284902.html


发表评论