elasticsearch restHighLevelClient 游标查询全量数据示例

本文介绍了一种使用Elasticsearch的游标滚动搜索功能,实现从指定索引(admin_log)中查询所有数据的方法。通过创建SearchRequest并设置查询条件,结合RestHighLevelClient进行搜索,最终将SearchHit结果转换为AdminLog对象集合。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

查询所有数据

我这边用了很多次,所以我抽成了工具类

调用

			//admin_log是索引名
			SearchRequest searchRequest = new SearchRequest("admin_log");
			SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
			searchSourceBuilder.query(QueryBuilders.matchQuery("app", "xxx"));
			searchRequest.source(searchSourceBuilder);
			try {
				//SCROLL_TIMEOUT是快照保存时间
                List<SearchHit> searchHits = SearchHitUtils.scrollSearchAll(restHighLevelClient, SCROLL_TIMEOUT, searchRequest);
                //logList就是查询到的所有结果了
                List<AdminLog> logList= SearchHitUtils.getAdminLogForSearchHit(searchHits);
            } catch (IOException e) {
                LOGGER.error("################ 高危操作日志游标查询异常! ##################");
            } catch (IllegalAccessException | InvocationTargetException e) {
                LOGGER.error("################ 高危操作日志游标查询结果,转换AdminLog对象出现异常! ##################");
            }

工具类

import com.vivo.admin_log_web.entity.AdminLog;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.elasticsearch.common.unit.TimeValue.timeValueMillis;

/**
 * @author GaoJinShan
 */
public class SearchHitUtils {
    public static final Logger LOGGER = LoggerFactory.getLogger(SearchHitUtils.class);

    private SearchHitUtils() {
    }

    /**
     * SearchHit数组转换成AdminLog对象集合
     *
     * @param resultHits
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static List<AdminLog> getAdminLogForSearchHit(SearchHit[] resultHits) throws InvocationTargetException, IllegalAccessException {
        ArrayList<AdminLog> adminLogs = new ArrayList<>();
        for (SearchHit hit : resultHits) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            AdminLog adminLog = new AdminLog();
            BeanUtils.populate(adminLog, sourceAsMap);
            adminLogs.add(adminLog);
        }
        return adminLogs;
    }

    /**
     * SearchHit集合转换成AdminLog对象集合
     *
     * @param resultHits
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static List<AdminLog> getAdminLogForSearchHit(List<SearchHit> resultHits) throws InvocationTargetException, IllegalAccessException {
        ArrayList<AdminLog> adminLogs = new ArrayList<>();
        for (SearchHit hit : resultHits) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            AdminLog adminLog = new AdminLog();
            BeanUtils.populate(adminLog, sourceAsMap);
            adminLogs.add(adminLog);
        }
        return adminLogs;
    }

    /**
     * 使用游标获取全部结果,返回SearchHit集合
     *
     * @param restHighLevelClient
     * @param scrollTimeOut
     * @param searchRequest
     * @return
     * @throws IOException
     */
    public static List<SearchHit> scrollSearchAll(RestHighLevelClient restHighLevelClient, Long scrollTimeOut, SearchRequest searchRequest) throws IOException {
        Scroll scroll = new Scroll(timeValueMillis(scrollTimeOut));
        searchRequest.scroll(scroll);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest);
        String scrollId = searchResponse.getScrollId();
        SearchHit[] hits = searchResponse.getHits().getHits();
        List<SearchHit> resultSearchHit = new ArrayList<>();
        while (ArrayUtils.isNotEmpty(hits)) {
            for (SearchHit hit : hits) {
                resultSearchHit.add(hit);
            }
            SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
            searchScrollRequest.scroll(scroll);
            SearchResponse searchScrollResponse = restHighLevelClient.searchScroll(searchScrollRequest);
            scrollId = searchScrollResponse.getScrollId();
            hits = searchScrollResponse.getHits().getHits();
        }
        //及时清除es快照,释放资源
        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
        clearScrollRequest.addScrollId(scrollId);
        restHighLevelClient.clearScroll(clearScrollRequest);
        return resultSearchHit;
    }
}

注: 本文是参考官网然后自己摸索修改的,如有问题请大佬指正

<think>我们正在处理用户关于Java和Elasticsearch批量数据查询的最佳实践的问题。根据用户的问题,他想要一次性获取大量数据的方法或最佳实践。参考引用内容:[^1]提到了启动Elasticsearch服务的方法,但与我们当前的问题关联不大。[^2]展示了一个使用match_phrase和multi_match查询示例,这涉及到查询构造,但并非批量获取数据。[^3]介绍了Elasticsearch的背景和主要功能,提到它是一个分布式实时分析搜索引擎,支持PB级别的数据。[^4]提到脚本(Scripts)可能无法使用Elasticsearch的索引结构和优化,可能导致搜索速度变慢。这提示我们在批量查询中应避免使用脚本以提高性能。用户的问题核心是“JavaElasticsearch批量数据查询的最佳实践”。因此,我们需要关注如何高效地从Elasticsearch中获取大量数据。在Elasticsearch中,处理大量数据通常有两种主要方式:1.使用分页(Pagination):传统的from/size方式,但深度分页性能较差。2.使用游标Scroll):适用于大量数据导出,但注意游标会占用资源。3.使用SearchAfter:适用于深度分页,比Scroll更高效,因为不需要维护游标。另外,如果数据量极大,可能需要考虑使用并行处理。考虑到用户使用Java,我们可以使用Elasticsearch的高级别REST客户端(HighLevelRESTClient)来实现。最佳实践建议:1.避免深度分页:使用SearchAfter代替传统的from/size。2.使用批量处理:通过多次请求获取数据,每次获取一批,使用SearchAfter的pit(PointInTime)特性来保持索引状态一致。3.合理设置批处理大小:根据网络和系统性能调整每批的大小(例如1000条)。4.使用多线程并行获取:如果数据量极大,可以将数据分成多个分片并行处理,但注意分片数在索引创建时确定,且不能动态更改。下面给出一个使用JavaHighLevelRESTClient的示例代码,展示如何使用SearchAfter进行批量数据查询。注意:我们假设用户已经配置了Elasticsearch客户端。```javaimportorg.elasticsearch.action.search.SearchRequest;importorg.elasticsearch.action.search.SearchResponse;importorg.elasticsearch.client.RequestOptions;importorg.elasticsearch.client.RestHighLevelClient;importorg.elasticsearch.core.TimeValue;importorg.elasticsearch.index.query.QueryBuilders;importorg.elasticsearch.search.SearchHit;importorg.elasticsearch.search.builder.SearchSourceBuilder;importorg.elasticsearch.search.sort.FieldSortBuilder;importorg.elasticsearch.search.sort.SortOrder;importjava.io.IOException;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;publicclassElasticsearchBulkQuery{publicList<Map<String,Object>>fetchAllDocuments(RestHighLevelClientclient,Stringindex)throwsIOException{List<Map<String,Object>>results=newArrayList<>();//第一步:创建一个PointInTime(PIT)来保持搜索上下文StringpitId=client.openPointInTime(newOpenPointInTimeRequest(index).keepAlive(TimeValue.timeValueMinutes(10)),RequestOptions.DEFAULT).getId();SearchSourceBuildersearchSourceBuilder=newSearchSourceBuilder().size(1000)//每批获取1000条.sort(newFieldSortBuilder("_id").order(SortOrder.ASC))//必须有一个唯一的排序字段,这里使用_id.query(QueryBuilders.matchAllQuery());//查询所有文档,实际中应替换为实际查询条件SearchRequestsearchRequest=newSearchRequest();searchRequest.source(searchSourceBuilder);searchRequest.pointInTime(newPointInTimeBuilder(pitId));//设置PITSearchResponseresponse=client.search(searchRequest,RequestOptions.DEFAULT);String[]lastSortValues=null;while(response.getHits().getHits().length>0){for(SearchHithit:response.getHits().getHits()){results.add(hit.getSourceAsMap());}lastSortValues=response.getHits().getHits()[response.getHits().getHits().length-1].getSortValues();searchSourceBuilder.searchAfter(lastSortValues);//设置searchAfter为上一批最后一个文档的排序值response=client.search(searchRequest,RequestOptions.DEFAULT);}//最后关闭PITclient.closePointInTime(newClosePointInTimeRequest(pitId),RequestOptions.DEFAULT);returnresults;}}```注意事项:-使用PIT(PointInTime)可以确保在遍历过程中索引的变化不会影响结果的一致性。-排序字段必须唯一,通常使用`_id`字段,因为它是唯一的。-设置合适的批处理大小(size),过大会增加内存压力,过小会增加请求次数。-在循环中不断设置searchAfter参数,直到获取所有文档。另外,如果数据量极大(数百万以上),考虑以下优化:-使用多线程:将数据按某个字段(如范围)分成多个段,每个线程处理一个段。但注意要确保字段分布均匀,避免数据倾斜。-调整网络和JVM设置:确保有足够的内存和网络带宽。避免使用ScrollAPI,因为它在Elasticsearch7.10之后被PointInTime和SearchAfter取代,ScrollAPI会维护一个搜索上下文,对集群资源消耗较大。关于性能:如果查询条件复杂,可以尝试使用filter代替query,因为filter可以利用查询缓存。最后,如果用户需要的是聚合结果而非文档数据,则可以使用聚合(Aggregation)来一次性获取汇总数据。相关问题:1.在Elasticsearch中,PointInTime(PIT)和Scroll有什么区别?2.如何优化Elasticsearch批量查询的性能?3.在Java中如何处理Elasticsearch批量查询的异常和重试机制?4.使用SearchAfter进行分页时,排序字段的选择有什么注意事项?</think>### Java Elasticsearch批量数据查询最佳实践 在Java中使用Elasticsearch进行高效批量数据查询时,推荐以下最佳实践: #### 1. **使用Scroll API处理大数据集** - **适用场景**:需要导出全量数据或超大规模结果集(>10,000条) - **原理**:创建搜索上下文快照,避免深度分页性能问题 - **Java实现**: ```java SearchRequest searchRequest = new SearchRequest("index_name"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.query(QueryBuilders.matchAllQuery()); sourceBuilder.size(1000); // 每批获取量 searchRequest.source(sourceBuilder); searchRequest.scroll(TimeValue.timeValueMinutes(1L)); // 上下文有效期 SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); String scrollId = response.getScrollId(); SearchHit[] hits = response.getHits().getHits(); while (hits != null && hits.length > 0) { // 处理当前批次数据 SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); scrollRequest.scroll(TimeValue.timeValueSeconds(30L)); response = client.scroll(scrollRequest, RequestOptions.DEFAULT); scrollId = response.getScrollId(); hits = response.getHits().getHits(); } // 清理scroll上下文 ClearScrollRequest clearRequest = new ClearScrollRequest(); clearRequest.addScrollId(scrollId); client.clearScroll(clearRequest, RequestOptions.DEFAULT); ``` #### 2. **Search After分页(ES 5.0+)** - **优势**:比`from/size`更高效,适合深度分页 - **实现要点**: - 使用唯一排序字段(如`_id`或时间戳) - 记住上一页最后一条的排序值 - **示例**: ```java SearchRequest searchRequest = new SearchRequest("index_name"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder() .size(1000) .sort(SortBuilders.fieldSort("timestamp").order(SortOrder.ASC)) .sort(SortBuilders.fieldSort("_id")); Object[] lastSortValues = null; // 存储上一页最后结果的排序值 do { if (lastSortValues != null) { sourceBuilder.searchAfter(lastSortValues); } SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); lastSortValues = response.getHits().getHits()[response.getHits().getHits().length - 1].getSortValues(); // 处理数据... } while (hasMoreData); ``` #### 3. **批量查询(Multi-Search API)** - **适用场景**:同时执行多个独立查询请求 - **性能优势**:减少网络往返次数 - **实现**: ```java MultiSearchRequest request = new MultiSearchRequest(); request.add(new SearchRequest("index1").source(new SearchSourceBuilder().query(...))); request.add(new SearchRequest("index2").source(new SearchSourceBuilder().query(...))); MultiSearchResponse response = client.msearch(request, RequestOptions.DEFAULT); for (MultiSearchResponse.Item item : response.getResponses()) { if (item.isFailure()) { // 错误处理 } else { SearchHits hits = item.getResponse().getHits(); // 处理结果 } } ``` #### 4. **性能优化关键点** - **批量大小**:根据网络延迟和内存调整`size`值(推荐500-2000) - **避免脚本查询**:尽量使用内置查询代替脚本,因脚本会绕过索引优化[^4] - **使用Filter Context**:对不参与评分的条件使用`filter`查询,利用缓存机制 - **索引设计**: - 使用分片策略分散负载 - 冷热数据分离架构 - **客户端配置**: ```java RestClientBuilder builder = RestClient.builder( new HttpHost("localhost", 9200, "http")) .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder .setConnectTimeout(5000) .setSocketTimeout(600000) // 长超时用于批量 ); ``` #### 5. **异步处理模式** - 使用`BulkProcessor`自动批处理: ```java BulkProcessor processor = BulkProcessor.builder( (request, bulkListener) -> client.bulkAsync(request, RequestOptions.DEFAULT, bulkListener), new BulkProcessor.Listener() { /* 监听器实现 */ }) .setBulkActions(1000) // 每1000请求执行一次 .setConcurrentRequests(2) // 并发数 .build(); // 添加请求 processor.add(new IndexRequest("index").source(...)); ``` > **注意事项**: > 1. 深度分页避免使用`from/size`,当`from + size > index.max_result_window`(默认10,000)时性能急剧下降 > 2. Scroll API会占用堆内存,完成操作后必须及时清除 > 3. 监控`_nodes/stats`中的`scroll`相关指标 > 4. 对于实时性要求高的场景,优先使用Search After而非Scroll Elasticsearch的分布式特性使其能处理PB级数据,但合理的设计和查询方式对性能至关重要[^3]。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值