ElasticSearch第5天 es实现分页查询的几种方式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ElasticSearch第5天 es实现分页查询的几种方式相关的知识,希望对你有一定的参考价值。

参考技术A es实现分页查询,在ES中有三种方式可以实现分页:from+size、scroll、search_after

这种分页方式虽然查询变快了,但滚动上下文代价很高,每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上,那么在实时情况下如果处理深度分页的问题呢?es 给出了 search_after 的方式,这是在 >= 5.0 版本才提供的功能。

searchAfter的方式通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景。
search_after的理念是,=在不同分片上(假设有5个分片),先按照指定顺序排好,根据我们传的search_after值 ,然后仅取这个值之后的size个文档。这 5*size 个文档拿到Es内存中排序后,返回前size个文档即可。避免了浅分页导致的内存爆炸情况,经实际使用性能良好,ES空闲状态下查询耗时稳定在50ms以内,平均10~20ms。

ElasticSearch之Search_After的注意事项

1.搜索时,需要指定sort,并且保证值是唯一的(可以通过加入_id或者文档body中的业务唯一值来保证);
2.再次查询时,使用上一次最后一个文档的sort值作为search_after的值来进行查询;
3.不能使用随机跳页,只能是下一页或者小范围的跳页(一次查询出小范围内各个页数,利用缓存等技术,来实现小范围分页,比较麻烦,比如从第一页调到第五页,则依次查询出2,3,4页的数据,利用每一次最后一个文档的sort值进行下一轮查询,客户端或服务端都可以进行,如果跳的比较多,则可能该方法并不适用)
它与滚动API非常相似,但与它不同,search_after参数是无状态的,它始终针对最新版本的搜索器进行解析。因此,排序顺序可能会在步行期间发生变化,具体取决于索引的更新和删除

from+ size 分页,如果数据量不大或者from、size不大的情况下,效率还是蛮高的。但是在深度分页的情况下,这种使用方式效率是非常低的,并发一旦过大,还有可能直接拖垮整个ElasticSearch的集群。
scroll 分页通常不会用在客户端,因为每一个 scroll_id 都会占用大量的资源,一般是后台用于全量读取数据使用
search_after通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景,一般用于客户端的分页查询
大体而言就是在这三种分页方式中,from + size不适合数据量很大的场景,scroll不适合实时场景,而search after在es5.x版本之后应运而生,较好的解决了这个问题。

SpringBoot整合ElasticSearch实现模糊查询,批量CRUD,排序,分页,高亮

上一章:《ElasticSearch相关概念》


文章目录


上述部分为理论部分,本章跟着我一起来看一下具体开发中es是如何使用的

本章的完整代码在文末可以自行查看下载

4.1 导入elasticsearch依赖

在pom.xml里加入如下依赖:

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

我的SpringBoot版本:2.6.2
引入之后记得要看一下你的依赖版本是否和es的版本是否适配,如果不一致,会连接失败

启动es,在浏览器输入http://localhost:9200/查看es版本

很明显,我们的版本是不兼容的,我找了半天spring-boot-starter-data-elasticsearch依赖包也没找到适配es 8.6.1的依赖,为了不影响进度,我先退而求其次,先使用7.15.2这个版本的es

安装包:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.15.2-windows-x86_64.zip

安装过程和我们第二章的过程一样,详情可参考:《第二章:ElasticSearch安装》

安装之后,我们可以看到我们的版本号已经变为7.15.2啦

4.2 创建高级客户端

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticSearchClientConfig 
    @Bean
    public RestHighLevelClient restHighLevelClient() 
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1", 9200, "http")));
        return client;
    

如果你的es是部署在服务器上,那么127.0.0.1则需要改成你服务器的ip地址

4.3 基本用法

1.创建、判断存在、删除索引

  • 创建索引
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    /**
     * 创建索引
     *
     * @return
     * @throws IOException
     */
    @GetMapping("/index/createIndex")
    public Object createIndex() throws IOException 
        //1.创建索引请求
        CreateIndexRequest request = new CreateIndexRequest("ninesunindex");
        //2.客户端执行请求IndicesClient,执行create方法创建索引,请求后获得响应
        CreateIndexResponse response =
                restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        return response;
    

可以看到索引已经创建成功

PS:如果不知道索引以及我们后面提到的名次概念,可以花几分钟读一下:《第三章:ElasticSearch相关概念》

  • 查询索引
    /**
     * 查询索引
     *
     * @return
     * @throws IOException
     */
    @GetMapping("/index/searchIndex")
    public Object searchIndex() throws IOException 
        //1.查询索引请求
        GetIndexRequest request = new GetIndexRequest("ninesunindex");
        //2.执行exists方法判断是否存在
        boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
        return exists;
    
  • 删除索引
    /**
     * 删除索引
     *
     * @return
     * @throws IOException
     */
    @GetMapping("/index/delIndex")
    public Object delIndex() throws IOException 
        //1.删除索引请求
        DeleteIndexRequest request = new DeleteIndexRequest("ninesunindex");
        //执行delete方法删除指定索引
        AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
        return delete.isAcknowledged();
    

2.对文档的CRUD

创建文档:

注意:如果添加时不指定文档ID,他就会随机生成一个ID,ID唯一。

创建文档时若该ID已存在,发送创建文档请求后会更新文档中的数据

  • 新增文档
    /**
     * 新增文档
     *
     * @return
     * @throws IOException
     */
    @GetMapping("/document/add")
    public Object add() throws IOException 
        //1.创建对象
        User user = new User("Go", 21, new String[]"内卷", "吃饭");
        //2.创建请求
        IndexRequest request = new IndexRequest("ninesunindex");
        //3.设置规则 PUT /ljx666/_doc/1
        //设置文档id=6,设置超时=1s等,不设置会使用默认的
        //同时支持链式编程如 request.id("6").timeout("1s");
        request.id("6");
        request.timeout("1s");

        //4.将数据放入请求,要将对象转化为json格式
        //XContentType.JSON,告诉它传的数据是JSON类型
        request.source(JSONValue.toJSONString(user), XContentType.JSON);

        //5.客户端发送请求,获取响应结果
        IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        System.out.println(indexResponse.toString());
        System.out.println(indexResponse.status());
        return indexResponse;
    
  • 获取文档中的数据
    /**
     * 获取文档中的数据
     *
     * @return
     * @throws IOException
     */
    @GetMapping("/document/get")
    public Object get() throws IOException 
        //1.创建请求,指定索引、文档id
        GetRequest request = new GetRequest("ninesunindex", "6");
        GetResponse getResponse = restHighLevelClient.get(request, RequestOptions.DEFAULT);
        System.out.println(getResponse);//获取响应结果
        //getResponse.getSource() 返回的是Map集合
        System.out.println(getResponse.getSourceAsString());//获取响应结果source中内容,转化为字符串
        return getResponse;
    
  • 更新文档数据

注意:需要将User对象中的属性全部指定值,不然会被设置为空,如User只设置了名称,那么只有名称会被修改成功,其他会被修改为null。

    /**
     * 更新文档数据
     *
     * @return
     * @throws IOException
     */
    @GetMapping("/document/update")
    public Object update() throws IOException 
        //1.创建请求,指定索引、文档id
        UpdateRequest request = new UpdateRequest("ninesunindex", "6");

        User user = new User("GoGo", 21, new String[]"内卷", "吃饭");
        //将创建的对象放入文档中
        request.doc(JSONValue.toJSONString(user), XContentType.JSON);
        UpdateResponse updateResponse = restHighLevelClient.update(request, RequestOptions.DEFAULT);
        System.out.println(updateResponse.status());//更新成功返回OK
        return updateResponse;
    
  • 删除文档数据
    /**
     * 删除文档数据
     *
     * @return
     * @throws IOException
     */
    @GetMapping("/document/delete")
    public Object delete() throws IOException 
        //1.创建删除文档请求
        DeleteRequest request = new DeleteRequest("ninesunindex", "6");
        DeleteResponse deleteResponse = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        System.out.println(deleteResponse.status());//更新成功返回OK
        return deleteResponse;
    

  

3.批量新增文档数据

  • 批量新增文档数据
    /**
     * 批量新增文档数据
     *
     * @return
     * @throws IOException
     */
    @GetMapping("/document/addBatch")
    public Object addBatch() throws IOException 
        BulkRequest bulkRequest = new BulkRequest();
        //设置超时
        bulkRequest.timeout("10s");

        List<User> list = new ArrayList<>();
        list.add(new User("Java", 25, new String[]"内卷"));
        list.add(new User("Go", 18, new String[]"内卷"));
        list.add(new User("C", 30, new String[]"内卷"));
        list.add(new User("C++", 26, new String[]"内卷"));
        list.add(new User("Python", 20, new String[]"内卷"));

        int id = 1;
        //批量处理请求
        for (User u : list) 
            //不设置id会生成随机id
            bulkRequest.add(new IndexRequest("ninesunindex")
                    .id("" + (id++))
                    .source(JSONValue.toJSONString(u), XContentType.JSON));
        

        BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulkResponse.hasFailures());//是否执行失败,false为执行成功
        return bulkResponse;
    

4.查询所有、模糊查询、分页查询、排序、高亮显示

    @GetMapping("test")
    public Object test() throws IOException 
        SearchRequest searchRequest = new SearchRequest("ninesunindex");//里面可以放多个索引
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//构造搜索条件
        //此处可以使用QueryBuilders工具类中的方法
        //1.查询所有
        sourceBuilder.query(QueryBuilders.matchAllQuery());
        //2.查询name中含有Java的
        sourceBuilder.query(QueryBuilders.multiMatchQuery("java", "userName"));
        //3.分页查询
        sourceBuilder.from(0).size(5);
        //4.按照score正序排列
        sourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.ASC));
        //5.按照id倒序排列(score会失效返回NaN)
        sourceBuilder.sort(SortBuilders.fieldSort("_id").order(SortOrder.DESC));
        //6.给指定字段加上指定高亮样式
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("userName").preTags("<span style='color:red;'>").postTags("</span>");
        sourceBuilder.highlighter(highlightBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //获取总条数
        System.out.println(searchResponse.getHits().getTotalHits().value);
        //输出结果数据(如果不设置返回条数,大于10条默认只返回10条)
        SearchHit[] hits = searchResponse.getHits().getHits();
        for (SearchHit hit : hits) 
            System.out.println("分数:" + hit.getScore());
            Map<String, Object> source = hit.getSourceAsMap();
            System.out.println("index->" + hit.getIndex());
            System.out.println("id->" + hit.getId());
            for (Map.Entry<String, Object> s : source.entrySet()) 
                System.out.println(s.getKey() + "--" + s.getValue());
            
        
        return searchResponse;
    

4.4 总结

1.大致流程

创建对应的请求 --> 设置请求(添加规则,添加数据等) --> 执行对应的方法(传入请求,默认请求选项)–> 接收响应结果(执行方法返回值)–> 输出响应结果中需要的数据(source,status等)

2.注意事项

如果不指定id,会自动生成一个随机id

正常情况下,不应该这样使用new IndexRequest(“indexName”),如果索引发生改变了,那么代码都需要修改,可以定义一个枚举类或者一个专门存放常量的类,将变量用final static等进行修饰,并指定索引值。其他地方引用该常量即可,需要修改也只需修改该类即可。

elasticsearch相关的东西,版本都必须一致,不然会报错

elasticsearch很消耗内存,建议在内存较大的服务器上运行elasticsearch,否则会因为内存不足导致elasticsearch自动killed


git地址:https://gitee.com/ninesuntec/es-better.git

PS:本章git上的代码如果有被注释掉的,只是为了防止和后面的章节不冲突,并无错误,大家自行解注查看即可

下一章:《ElasticSearchRepository和ElasticsearchRestTemplate的使用》

以上是关于ElasticSearch第5天 es实现分页查询的几种方式的主要内容,如果未能解决你的问题,请参考以下文章

bos 第5天(定区的添加定区的分页查询hessian远程调用实现获取客户信息)

ElasticSearch在JavaAPI中的操作(入门)

SpringBoot整合ElasticSearch实现模糊查询,批量CRUD,排序,分页,高亮

elasticsearch(es)查询api,结果集排序,分页,范围查询

Elasticsearch——使用Java API实现ES中的索引映射文档操作

Elasticsearch——使用Java API实现ES中的索引映射文档操作