心丨悦 2020-02-10
在公司的操作es进行查询数据时,es默认分页且只返回十条数据,并且size最大只能传10000,这种查询方式称之为深度分页的方式也就是用 from
和 size
参数分页查询。由于我们当时开发任务紧急,所以在需要获取全量数据时就直接更改了这一限制,改为了10000000(一千万)条。但是这一方式可能在之后项目上线后随着数据量逐渐增多的情况下可能会对es服务造成一定隐患,因此需要改为官方推荐的scroll(游标)方式查询获取全量数据。
对于深度分页获取大量数据的劣势官方文档已给出了较为详细的解释,并且官方建议使用scroll的方式来进行全量数据的获取。具体如下:
摘自 《Elasticsearch: 权威指南》→ 基础入门 → 搜索——最基本的工具 → 分页
摘自 《Elasticsearch: 权威指南》→ 基础入门→ 执行分布式检索→ 取回阶段
相对于from和size的分页来说,可以把scroll理解为关系型数据库里的 cursor(游标),不是查询所有数据然后剔除不要的部分,而是记录了当前读取的文档信息位置,保证下一次快速继续读取。也可以理解为维护了一份当前索引段的快照信息,这个快照信息是你执行这个scroll查询时的快照。在这个查询后的任何新索引进来的数据,都不会在这个快照中查询到。因此scroll 并不适合用来做实时搜索,而更适用于一次性查询大量的数据(甚至是全部的数据)。
摘自 《Elasticsearch: 权威指南》→ 基础入门→ 执行分布式检索→ 游标查询Scroll
es的删除机制为根据条件查询出来再进行删除操作,在阅读spring-data-elasticsearch包中的elasticsearchTemplate源码时,发现该类中的删除方法也是使用的scroll方式获取了全量数据再进行删除,因此scroll游标的方式查询数据更适用于全量数据的获取。以下为elasticsearchTemplate源码:
@Override public <T> void delete(DeleteQuery deleteQuery, Class<T> clazz) { String indexName = !StringUtils.isEmpty(deleteQuery.getIndex()) ? deleteQuery.getIndex() : getPersistentEntityFor(clazz).getIndexName(); String typeName = !StringUtils.isEmpty(deleteQuery.getType()) ? deleteQuery.getType() : getPersistentEntityFor(clazz).getIndexType(); Integer pageSize = deleteQuery.getPageSize() != null ? deleteQuery.getPageSize() : 1000; Long scrollTimeInMillis = deleteQuery.getScrollTimeInMillis() != null ? deleteQuery.getScrollTimeInMillis() : 10000l; SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(deleteQuery.getQuery()).withIndices(indexName) .withTypes(typeName).withPageable(PageRequest.of(0, pageSize)).build(); SearchResultMapper onlyIdResultMapper = new SearchResultMapper() { @Override public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { List<String> result = new ArrayList<String>(); for (SearchHit searchHit : response.getHits().getHits()) { String id = searchHit.getId(); result.add(id); } if (result.size() > 0) { return new AggregatedPageImpl<T>((List<T>) result, response.getScrollId()); } return new AggregatedPageImpl<T>(Collections.EMPTY_LIST, response.getScrollId()); } }; Page<String> scrolledResult = startScroll(scrollTimeInMillis, searchQuery, String.class, onlyIdResultMapper); BulkRequestBuilder bulkRequestBuilder = client.prepareBulk(); List<String> ids = new ArrayList<String>(); do { ids.addAll(scrolledResult.getContent()); scrolledResult = continueScroll(((ScrolledPage<T>)scrolledResult).getScrollId(), scrollTimeInMillis, String.class, onlyIdResultMapper); } while(scrolledResult.getContent().size() != 0); for (String id : ids) { bulkRequestBuilder.add(client.prepareDelete(indexName, typeName, id)); } if (bulkRequestBuilder.numberOfActions() > 0) { bulkRequestBuilder.execute().actionGet(); } clearScroll(((ScrolledPage<T>) scrolledResult).getScrollId()); }
故此记录在查询全量数据时使用scroll游标方式查询数据更好。
另外一部分,则需要先做聚类、分类处理,将聚合出的分类结果存入ES集群的聚类索引中。数据处理层的聚合结果存入ES中的指定索引,同时将每个聚合主题相关的数据存入每个document下面的某个field下。