通常搜索请求都是返回一"页"数据,不管数据量多大都一块儿返回给用户,Scroll API能够容许咱们检索大量数据(甚至所有数据)。Scroll API容许咱们作一个初始阶段搜索而且持续批量从Elasticsearch里拉取结果直到没有结果剩下。这有点像传统数据库里的cursors(游标)。php
Scroll API的建立并非为了实时的用户响应,而是为了处理大量的数据(Scrolling is not intended for real time user requests, but rather for processing large amounts of data)。从 scroll 请求返回的结果只是反映了 search 发生那一时刻的索引状态,就像一个快照(The results that are returned from a scroll request reflect the state of the index at the time that the initial search request was made, like a snapshot in time)。后续的对文档的改动(索引、更新或者删除)都只会影响后面的搜索请求。css
假设咱们想一次返回大量数据,下面代码中一次请求58000条数据:html
/**
* 普通搜索
*
*/
public static void search(Client client) {
String index = "simple-index";
String type = "simple-type";
// 搜索条件
SearchRequestBuilder searchRequestBuilder = client.prepareSearch();
searchRequestBuilder.setIndices(index);
searchRequestBuilder.setTypes(type);
searchRequestBuilder.setSize(58000);
// 执行
SearchResponse searchResponse = searchRequestBuilder.get();
// 搜索结果
SearchHit[] searchHits = searchResponse.getHits().getHits();
for (SearchHit searchHit : searchHits) {
String source = searchHit.getSource().toString();
logger.info("--------- searchByScroll source {}", source);
} // for
}
运行结果:java
Caused by: QueryPhaseExecutionException[Result window is too large, from + size must be less than or equal to: [10000] but was [58000]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level parameter.]
at org.elasticsearch.search.internal.DefaultSearchContext.preProcess(DefaultSearchContext.java:212)
at org.elasticsearch.search.query.QueryPhase.preProcess(QueryPhase.java:103)
at org.elasticsearch.search.SearchService.createContext(SearchService.java:676)
at org.elasticsearch.search.SearchService.createAndPutContext(SearchService.java:620)
at org.elasticsearch.search.SearchService.executeQueryPhase(SearchService.java:371)
at org.elasticsearch.search.action.SearchServiceTransportAction$SearchQueryTransportHandler.messageReceived(SearchServiceTransportAction.java:368)
at org.elasticsearch.search.action.SearchServiceTransportAction$SearchQueryTransportHandler.messageReceived(SearchServiceTransportAction.java:365)
at org.elasticsearch.transport.TransportRequestHandler.messageReceived(TransportRequestHandler.java:33)
at org.elasticsearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:75)
at org.elasticsearch.transport.TransportService$4.doRun(TransportService.java:376)
at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37)
... 3 more
从上面咱们能够知道,搜索请求一次请求最大量为[10000]。咱们的请求量已经超标,所以报错,异常信息提示咱们请求大数据量的状况下使用Scroll API。数据库
为了使用 scroll,初始搜索请求应该在查询中指定 scroll 参数,告诉 Elasticsearch 须要保持搜索的上下文环境多长时间(滚动时间)。api
searchRequestBuilder.setScroll(new TimeValue(60000));
下面代码中指定了查询条件以及滚动属性,如滚动的有效时长(使用setScroll()方法)。咱们经过SearchResponse对象的getScrollId()方法获取滚动ID。滚动ID会在下一次请求中使用。数组
/**
* 使用scroll进行搜索
*
*/
public static String searchByScroll(Client client) {
String index = "simple-index";
String type = "simple-type";
// 搜索条件
SearchRequestBuilder searchRequestBuilder = client.prepareSearch();
searchRequestBuilder.setIndices(index);
searchRequestBuilder.setTypes(type);
searchRequestBuilder.setScroll(new TimeValue(30000));
// 执行
SearchResponse searchResponse = searchRequestBuilder.get();
String scrollId = searchResponse.getScrollId();
logger.info("--------- searchByScroll scrollID {}", scrollId);
SearchHit[] searchHits = searchResponse.getHits().getHits();
for (SearchHit searchHit : searchHits) {
String source = searchHit.getSource().toString();
logger.info("--------- searchByScroll source {}", source);
} // for
return scrollId;
}
使用上面的请求返回的结果中的滚动ID,这个 ID 能够传递给 scroll API 来检索下一个批次的结果。这一次请求中不用添加索引和类型,这些都指定在了原始的 search 请求中。less
每次返回下一个批次结果 直到没有结果返回时中止 即hits数组空时(Each call to the scroll
API returns the next batch of results until there are no more results left to return, ie the hits
array is empty)。elasticsearch
/**
* 经过滚动ID获取文档
*
*
*/
public static void searchByScrollId(Client client, String scrollId){
TimeValue timeValue = new TimeValue(30000);
SearchScrollRequestBuilder searchScrollRequestBuilder;
SearchResponse response;
// 结果
while (true) {
logger.info("--------- searchByScroll scrollID {}", scrollId);
searchScrollRequestBuilder = client.prepareSearchScroll(scrollId);
// 从新设定滚动时间
searchScrollRequestBuilder.setScroll(timeValue);
// 请求
response = searchScrollRequestBuilder.get();
// 每次返回下一个批次结果 直到没有结果返回时中止 即hits数组空时
if (response.getHits().getHits().length == 0) {
break;
} // if
// 这一批次结果
SearchHit[] searchHits = response.getHits().getHits();
for (SearchHit searchHit : searchHits) {
String source = searchHit.getSource().toString();
logger.info("--------- searchByScroll source {}", source);
} // for
// 只有最近的滚动ID才能被使用
scrollId = response.getScrollId();
} // while
}
备注:ide
初始搜索请求和每一个后续滚动请求返回一个新的 滚动ID——只有最近的滚动ID才能被使用。(The initial search request and each subsequent scroll request returns a new_scroll_id
— only the most recent _scroll_id
should be used)
我每次后续滚动请求返回的滚动ID都是相同的,因此对上面的备注,不是很懂,有明白的能够告知,谢谢。
若是超过滚动时间,继续使用该滚动ID搜索数据,则会报错:
Caused by: SearchContextMissingException[No search context found for id [2861]]
at org.elasticsearch.search.SearchService.findContext(SearchService.java:613)
at org.elasticsearch.search.SearchService.executeQueryPhase(SearchService.java:403)
at org.elasticsearch.search.action.SearchServiceTransportAction$SearchQueryScrollTransportHandler.messageReceived(SearchServiceTransportAction.java:384)
at org.elasticsearch.search.action.SearchServiceTransportAction$SearchQueryScrollTransportHandler.messageReceived(SearchServiceTransportAction.java:381)
at org.elasticsearch.transport.TransportRequestHandler.messageReceived(TransportRequestHandler.java:33)
at org.elasticsearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:75)
at org.elasticsearch.transport.TransportService$4.doRun(TransportService.java:376)
at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
虽然当滚动有效时间已过,搜索上下文(Search Context)会自动被清除,可是一值保持滚动代价也是很大的,因此当咱们不在使用滚动时要尽快使用Clear-Scroll API进行清除。
/**
* 清除滚动ID
*
*
*
*/
public static boolean clearScroll(Client client, List<String> scrollIdList){
ClearScrollRequestBuilder clearScrollRequestBuilder = client.prepareClearScroll();
clearScrollRequestBuilder.setScrollIds(scrollIdList);
ClearScrollResponse response = clearScrollRequestBuilder.get();
return response.isSucceeded();
}
/**
* 清除滚动ID
*
*
*
*/
public static boolean clearScroll(Client client, String scrollId){
ClearScrollRequestBuilder clearScrollRequestBuilder = client.prepareClearScroll();
clearScrollRequestBuilder.addScrollId(scrollId);
ClearScrollResponse response = clearScrollRequestBuilder.get();
return response.isSucceeded();
}
https://www.elastic.co/guide/en/elasticsearch/reference/2.4/search-request-scroll.html
http://www.jianshu.com/p/14aa8b09c789
---------------------------------------
原文地址: