ES深度分页以及搜索实战(基于ES7.x)

本文深入探讨了Elasticsearch中深分页(scroll)的实现原理与应用,对比浅分页(from+size),详细解析了scroll如何通过缓存快照提升大规模数据查询效率。同时,介绍了复杂业务场景下,利用bool查询组合多种查询类型(term、match、multi_match)进行精准数据筛选的方法。
@Service
@Slf4j
public class DynamicSecurityScanServiceImpl implements DynamicSecurityScanService {

    @Qualifier("elasticsearchTemplate")
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Qualifier("elasticsearchClient")
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private DynamicSecurityScanDocRepository repository;
    /**
     *
     * @param overviewDto
     * @return Page 自定义分页对象
     * @throws IOException
     * bool包含四种操作符,分别是must,should,must_not,query filter查询查询对结果进行缓存
     * match分析器,模糊匹配  term精准  multi_match多个字段同时进行匹配
     * from to浅分页  scroll 深分页
     * int from  = (paramPI.getPageNum()-1)*paramPI.getPageSize();
     * sourceBuilder.from(from);
     */
    @Override
    public Page<DynamicSecurityScanDoc> searchSystemDoc(OverviewDto overviewDto) throws IOException {
        Page page = new Page(overviewDto.getCurPage(), overviewDto.getPageSize());
        final Scroll scroll = new Scroll(TimeValue.timeValueSeconds(60));
        // 1、创建search请求
        SearchRequest searchRequest = new SearchRequest(Constant.INDEXFORVERSIONDETAUL);
        searchRequest.scroll(scroll);
        // 2、用SearchSourceBuilder来构造查询请求体 ,请仔细查看它的方法,构造各种查询的方法都在这。
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().trackTotalHits(true);
        sourceBuilder.size(overviewDto.getPageSize());
        //搜索条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();


        if (!StringUtils.isEmpty(overviewDto.getUser())) {
            MultiMatchQueryBuilder userQuery = QueryBuilders.multiMatchQuery(overviewDto.getUser(), "owner", "developer", "tester");
            boolQuery.must(userQuery);
        }

        if (!StringUtils.isEmpty(overviewDto.getAppName())) {
            MatchQueryBuilder appNameQuery = QueryBuilders.matchQuery("appName", overviewDto.getAppName());
            boolQuery.must(appNameQuery);
        }

        if (!StringUtils.isEmpty(overviewDto.getAppVersion())) {
            TermQueryBuilder appversionQuery = QueryBuilders.termQuery("appVersion.keyword", overviewDto.getAppVersion());
            boolQuery.must(appversionQuery);
        }
        if(!StringUtils.isEmpty(overviewDto.getBranchName())){
            MatchQueryBuilder branchNameQuery=QueryBuilders.matchQuery("branchName", overviewDto.getBranchName());
            boolQuery.must(branchNameQuery);
        }
        if(!StringUtils.isEmpty(overviewDto.getVulnerabilityResult())){
            TermQueryBuilder vulnerabilityResultQuery=QueryBuilders.termQuery("vulnerabilityResult.keyword", overviewDto.getVulnerabilityResult());
            boolQuery.must(vulnerabilityResultQuery);
        }
        if(!StringUtils.isEmpty(overviewDto.getType())){
            TermQueryBuilder typeQuery=QueryBuilders.termQuery("type.keyword", overviewDto.getType());
            boolQuery.must(typeQuery);
        }
        sourceBuilder.query(boolQuery);
        DslService.printDsl(sourceBuilder);
        //将请求体加入到请求中
        searchRequest.source(sourceBuilder);
        //3、发送请求
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //处理搜索命中文档结果
        SearchHits hits = searchResponse.getHits();
        int totalHits = (int) hits.getTotalHits().value;
        List<DynamicSecurityScanDoc> list = new ArrayList<>();
        String scrollId = null;
        int pageNum = overviewDto.getCurPage();
        int count = 1;
        while (searchResponse.getHits().getHits().length != 0){
            if(count == pageNum){
                execute(hits, list);
                log.info("ES分页查询成功");
                break;
            }
            count++;
            //每次循环完后取得scrollId,用于记录下次将从这个游标开始取数
            scrollId = searchResponse.getScrollId();
            SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
            scrollRequest.scroll(scroll);
            searchResponse = restHighLevelClient.scroll(scrollRequest, RequestOptions.DEFAULT);
        }
        if(scrollId != null){
            //清除滚屏
            ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
            //也可以选择setScrollIds()将多个scrollId一起使用
            clearScrollRequest.addScrollId(scrollId);
            restHighLevelClient.clearScroll(clearScrollRequest,RequestOptions.DEFAULT);
        }
        page.setTotalCount(totalHits);
        page.setList(list);
        return page;
    }

ES分页

size+from浅分页 按照一般的查询流程来说,如果我想查询前10条数据: 客户端请求发送给某个节点 节点转发给各个分片,查询每个分片上的前10条中的部分数据 结果返回给节点, 整合数据,提取前10条 返回给请求客户端

GET sdl-overview/_search
{
  "from": 1, 
  "size": 20, 
  "query": {
    "wildcard": {
        "appName.keyword": {
          "value": "*0*"
        }
        
    }
  }
}
  • 这种浅分页只适合少量数据, 因为隋from增大,查询的时间就会越大;而且数据越大,查询的效率指数下降.

  • 优点: from+size在数据量不大的情况下,效率比较高.

  • 缺点: 在数据量非常大的情况下,from+size分页会把全部记录加载到内存中,这样做不但运行速递特别慢,而且容易让es出现内存不足而挂掉.

scroll深分页

如果请求的页数较少(假设每页20个docs), Elasticsearch不会有什么问题,但是如果页数较大时,比如请求第20页,Elasticsearch不得不取出第1页到第20页的所有docs,再去除第1页到第19页的docs,得到第20页的docs。

解决的方式就是使用scroll,scroll就是维护了当前索引段的一份快照信息–缓存(这个快照信息是你执行这个scroll查询时的快照)。

  • 初始化 可以把 scroll 分为初始化和遍历两步: 1、初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照 2、遍历时,从这个快照里取数据

GET sdl-overview/_search?scroll=5m
{
  "size": 5, 
  "query": {
    "wildcard": {
        "appName.keyword": {
          "value": "*0*"
        }
        
    }
  }
}
  • 遍历 在遍历时候,拿到上一次遍历中的_scroll_id,然后带scroll参数,重复上一次的遍历步骤,知道返回的数据为空,就表示遍历完成

复杂的业务查询DSL(bool)

 

{
  "query": {
    "bool": {
      "must": [
         {"term":{"appVersion.keyword":"external-20200702-5009"}},
         {"match":{"appName":"葵花谱"}},
         {"term":{"type.keyword":"0"}},
         {"match":{"branchName":"release-1.0"}},
         {"term":{"vulnerabilityResult.keyword":"1"}},
         {"multi_match": {
            "query": "Gosaint3",
            "fields": ["developer","tester","owner"]
           }
        }
      ]
    }
  }
}

 

### Elasticsearch 7.x 和 6.x 版本在创建索引方面的差异 Elasticsearch 在版本升级过程中,对索引的创建和管理进行了多项调整。以下是对 Elasticsearch 7.x 和 6.x 在创建索引方面的主要差异: #### 1. 类型(Type)的概念变化 在 Elasticsearch 6.x 中,一个索引可以包含多个类型[^2]。虽然支持单索引多类型结构,但官方已经不建议使用这种方式,并推荐每个索引只包含一个类型。而在 Elasticsearch 7.x 中,这种限制被正式实施,即一个索引只能有一个类型[^2]。这意味着,在 7.x 版本中,`_type` 字段不再具有实际意义,且在未来的版本中可能会完全移除。 #### 2. 索引模板的变更 Elasticsearch 7.x 引入了新的索引模板机制,称为“组件模板”(Component Templates)。与之前的单一模板相比,组件模板允许用户将模板拆分为更小的部分,例如设置映射、别名或配置等,然后通过组合这些部分来创建完整的索引模板[^1]。这使得索引模板的管理和复用更加灵活。 #### 3. 默认分片数量的变化 在 Elasticsearch 6.x 中,默认情况下,每个索引会被分配 5 个主分片和 1 个副本分片。然而,在 Elasticsearch 7.x 中,为了优化资源利用率和性能,默认的主分片数量减少为 1。这一更改旨在减少小型索引的开销,同时提高集群的整体性能。 #### 4. 创建索引时的参数校验 Elasticsearch 7.x 对索引创建时的参数进行了更严格的校验。例如,某些字段名称或映射类型可能在新版本中被视为无效或已废弃。此外,7.x 版本还引入了一些新的限制条件,例如禁止使用动态字段生成嵌套对象等。 #### 5. 索引别名的增强 在 Elasticsearch 7.x 中,索引别名的功能得到了进一步增强。用户可以通过别名直接执行写操作,而无需显式指定底层索引[^3]。这为索引轮换和数据迁移提供了更大的灵活性。 #### 示例代码:创建索引 以下是使用 Elasticsearch 高级客户端在不同版本中创建索引的示例代码: **Elasticsearch 6.x 版本** ```java CreateIndexRequest request = new CreateIndexRequest("my_index"); request.mapping("{ \"type\": { \"properties\": { \"field\": { \"type\": \"text\" } } } }", XContentType.JSON); client.indices().create(request, RequestOptions.DEFAULT); ``` **Elasticsearch 7.x 版本** ```java CreateIndexRequest request = new CreateIndexRequest("my_index"); request.mapping("{ \"properties\": { \"field\": { \"type\": \"text\" } } }", XContentType.JSON); client.indices().create(request, RequestOptions.DEFAULT); ``` 可以看到,在 7.x 版本中,`type` 字段已被移除。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值