Elasticsearch中數據都存儲在分片中,當執行搜索時每個分片獨立搜索後,數據再經過整合返回。那麼,如果要實現分頁查詢該怎麼辦呢? 更多內容參考 "Elasticsearch資料彙總" 按照一般的查詢流程來說,如果我想查詢前10條數據: 1 客戶端請求發給某個節點 2 節點轉發給個個分片,查詢
Elasticsearch中數據都存儲在分片中,當執行搜索時每個分片獨立搜索後,數據再經過整合返回。那麼,如果要實現分頁查詢該怎麼辦呢?
更多內容參考Elasticsearch資料彙總
按照一般的查詢流程來說,如果我想查詢前10條數據:
- 1 客戶端請求發給某個節點
- 2 節點轉發給個個分片,查詢每個分片上的前10條
- 3 結果返回給節點,整合數據,提取前10條
- 4 返回給請求客戶端
那麼當我想要查詢第10條到第20條的數據該怎麼辦呢?這個時候就用到分頁查詢了。
from-size"淺"分頁
"淺"分頁的概念是小博主自己定義的,可以理解為簡單意義上的分頁。它的原理很簡單,就是查詢前20條數據,然後截斷前10條,只返回10-20的數據。這樣其實白白浪費了前10條的查詢。
查詢的方法如:
{
"from" : 0, "size" : 10,
"query" : {
"term" : { "user" : "kimchy" }
}
}
其中,from定義了目標數據的偏移值,size定義當前返回的事件數目。
預設from為0,size為10,即所有的查詢預設僅僅返回前10條數據。
做過測試,越往後的分頁,執行的效率越低。
通過下圖可以看出,刨去一些異常的數據,總體上還是會隨著from的增加,消耗時間也會增加。而且數據量越大,效果越明顯!
也就是說,分頁的偏移值越大,執行分頁查詢時間就會越長!
scroll“深”分頁
相對於from和size的分頁來說,使用scroll可以模擬一個傳統數據的游標,記錄當前讀取的文檔信息位置。這個分頁的用法,不是為了實時查詢數據,而是為了一次性查詢大量的數據(甚至是全部的數據)。
因為這個scroll相當於維護了一份當前索引段的快照信息,這個快照信息是你執行這個scroll查詢時的快照。在這個查詢後的任何新索引進來的數據,都不會在這個快照中查詢到。但是它相對於from和size,不是查詢所有數據然後剔除不要的部分,而是記錄一個讀取的位置,保證下一次快速繼續讀取。
API使用方法如:
curl -XGET 'localhost:9200/twitter/tweet/_search?scroll=1m' -d '
{
"query": {
"match" : {
"title" : "elasticsearch"
}
}
}
'
會自動返回一個_scroll_id,通過這個id可以繼續查詢(實際上這個ID會很長哦!):
curl -XGET 'localhost:9200/_search/scroll?scroll=1m&scroll_id=c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1'
註意,我在使用1.4版本的ES時,只支持把參數放在URL路徑裡面,不支持在JSON body中使用。
有個很有意思的事情,細心的會發現,這個ID其實是通過base64編碼的:
cXVlcnlUaGVuRmV0Y2g7MTY7MjI3NTp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyNzQ6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjgwOnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI4MTp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyODM6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjgyOnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI4Njp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyODc6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjg5OnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI4NDp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyODU6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjg4OnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI3Njp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzIyNzc6dnRYS0o4bG5RSW1kaXdjRHRQVC1rQTsyMjc4OnZ0WEtKOGxuUUltZGl3Y0R0UFQta0E7MjI3OTp2dFhLSjhsblFJbWRpd2NEdFBULWtBOzA7
如果使用解碼工具可以看到:
queryThenFetch;16;2275:vtXKJ8lnQImdiwcDtPT-kA;2274:vtXKJ8lnQImdiwcDtPT-kA;2280:vtXKJ8lnQImdiwcDtPT-kA;2281:vtXKJ8lnQImdiwcDtPT-kA;2283:vtXKJ8lnQImdiwcDtPT-kA;2282:vtXKJ8lnQImdiwcDtPT-kA;2286:vtXKJ8lnQImdiwcDtPT-kA;2287:vtXKJ8lnQImdiwcDtPT-kA;2289:vtXKJ8lnQImdiwcDtPT-kA;2284:vtXKJ8lnQImdiwcDtPT-kA;2285:vtXKJ8lnQImdiwcDtPT-kA;2288:vtXKJ8lnQImdiwcDtPT-kA;2276:vtXKJ8lnQImdiwcDtPT-kA;2277:vtXKJ8lnQImdiwcDtPT-kA;2278:vtXKJ8lnQImdiwcDtPT-kA;2279:vtXKJ8lnQImdiwcDtPT-kA;0;
雖然搞不清楚裡面是什麼內容,但是看到了一堆規則的鍵值對,總是讓人興奮一下!
測試from&size VS scroll的性能
首先呢,需要在java中引入elasticsearch-jar,比如使用maven:
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>1.4.4</version>
</dependency>
然後初始化一個client對象:
private static TransportClient client;
private static String INDEX = "index_name";
private static String TYPE = "type_name";
public static TransportClient init(){
Settings settings = ImmutableSettings.settingsBuilder()
.put("client.transport.sniff", true)
.put("cluster.name", "cluster_name")
.build();
client = new TransportClient(settings).addTransportAddress(new InetSocketTransportAddress("localhost",9300));
return client;
}
public static void main(String[] args) {
TransportClient client = init();
//這樣就可以使用client執行查詢了
}
然後就是創建兩個查詢過程了 ,下麵是from-size分頁的執行代碼:
System.out.println("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吧!
參考
1 簡書:elasticsearch 的滾動(scroll)
2 16php:Elasticsearch Scroll API詳解
3 elastic:from-size查詢
4 elastic:scroll query