avatar

十六小站

欢迎来到我的个人主页! 期待与您分享我的经验与故事,一起探索技术的无穷可能!

  • 首页
  • NAS专题
  • 关于
Home OpenSearch踩坑记录
文章

OpenSearch踩坑记录

Posted 2024-11-14 Updated 2024-11- 14
By 十六 已删除用户
14~18 min read

Opensearch继承这里不做赘述,文中代码皆为Demo,不可直接用于生产需要包装。

分页查询相关

  1. Opensearch是支持分页查询功能的其代码如下:

import org.opensearch.client.RequestOptions;
import org.opensearch.client.RestHighLevelClient;
import org.opensearch.client.indices.GetIndexRequest;
import org.opensearch.client.indices.GetIndexResponse;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.client.RequestOptions;
import org.opensearch.search.SearchRequest;
import org.opensearch.search.SearchResponse;
import org.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.opensearch.search.aggregations.AggregationBuilders;

import java.io.IOException;

public class OpenSearchPaginationClient extends RestHighLevelClient {

    public OpenSearchPaginationClient(RestClientBuilder builder) {
        super(builder);
    }

    public SearchResponse searchWithPagination(String index, int page, int size) throws IOException {
        // 创建搜索请求
        SearchRequest searchRequest = new SearchRequest(index);
        
        // 构建搜索源
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        
        // 设置查询条件(示例为匹配所有)
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        
        // 设置分页参数
        searchSourceBuilder.from(page * size); // 从第几条记录开始
        searchSourceBuilder.size(size); // 每页的条目数
        
        // 将源构建器应用到搜索请求
        searchRequest.source(searchSourceBuilder);
        
        // 执行搜索并返回响应
        return this.search(searchRequest, RequestOptions.DEFAULT);
    }

    public static void main(String[] args) {
        // 需要替换为您的 OpenSearch 客户端构建逻辑
        RestHighLevelClient client = new OpenSearchPaginationClient(/* Your RestClientBuilder */);
        
        int page = 0; // 目标页码
        int size = 10; // 每页文档数量
        
        try {
            SearchResponse response = client.searchWithPagination("your_index_name", page, size);
            // 处理搜索响应,如打印搜索结果
            Arrays.stream(response.getHits().getHits()).forEach(hit -> {
                System.out.println("Document ID: " + hit.getId());
                System.out.println("Source: " + hit.getSourceAsString());
            });
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

在使用传统的基于 from 和 size 的分页方法时,尤其是在数据量较大的情况下,可能会遇到以下几个问题:

  1. 性能问题 : 随着页码的增加,from 参数的值也在增加,这会导致 OpenSearch 在执行查询时,跳过越来越多的文档,从而导致性能下降。

  2. 重复数据 : 如果数据在分页之间发生了变化(例如插入、更新或删除),可能会导致某些文档在不同的页面上重复出现,或者某些文档在某个页面上缺失。

为了避免这些问题,建议使用游标分页 (或者称为深度分页),这种方式通过保持状态来避免使用 from 和 size 方法,以下列举几种常见的游标分页方法:

1. 使用 Search After

search_after 是 OpenSearch 和 Elasticsearch 提供的一个高效的分页方法。它可以根据最后一条文档的信息进行下一页的查询,避免了传统分页方法的性能问题。

public SearchResponse searchWithSearchAfter(String index, int size, Object[] searchAfter) throws IOException {
    SearchRequest searchRequest = new SearchRequest(index);
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

    // 设置查询条件(示例为匹配所有)
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    
    // 设置每页的条目数
    searchSourceBuilder.size(size); 

    // 使用 search_after 实现游标分页
    if (searchAfter != null) {
        searchSourceBuilder.searchAfter(searchAfter);
    }

    searchRequest.source(searchSourceBuilder);

    return this.search(searchRequest, RequestOptions.DEFAULT);
}

在该方法中,您可以传入 searchAfter 参数(一个 Object 数组),它表示上一个页面的最后一条文档的排序值。

改善数据的一致性

结合 search_after 使用时,可以确保在数据变更时,有效地减少重复或者遗漏。例如:

  1. 在文档或索引上使用时间戳字段,以确保在分页时可以通过自然排序来检索数据。

  2. 使用唯一标识符组合(如时间戳 + ID)作为 search_after 的参数。

2. 使用 Scroll API

如果需要在大数据集上实施深度分页,Scroll API 也是一个优秀的方案。Scroll API 主要用于查找所有文档,特别是在搜索结果集非常大的情况下可以减少性能开销。

public SearchResponse searchWithScroll(String index, int size) throws IOException {
    // 创建首次搜索请求
    SearchRequest searchRequest = new SearchRequest(index);
    searchRequest.scroll(TimeValue.timeValueMinutes(1)); // 设置 Scroll 的时间
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    searchSourceBuilder.size(size);
    searchRequest.source(searchSourceBuilder);

    // 执行首次搜索
    SearchResponse searchResponse = this.search(searchRequest, RequestOptions.DEFAULT);
    
    // 在这里处理首次搜索的结果
    // ...

    String scrollId = searchResponse.getScrollId();
    while (searchResponse.getHits().getHits().length != 0) {
        // 处理数据...
        
        // 创建新的 Scroll 请求
        SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
        scrollRequest.scroll(TimeValue.timeValueMinutes(1));

        // 执行 Scroll 请求
        searchResponse = this.scroll(scrollRequest, RequestOptions.DEFAULT);
        scrollId = searchResponse.getScrollId();
    }

    // 清除 Scroll 上下文
    ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
    clearScrollRequest.addScrollId(scrollId);
    this.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);

    return searchResponse;
}

注意事项:

  • 使用 search_after : 当你的数据支持用来排序的字段时,这是处理深度分页的最佳解决方案。

  • 使用 Scroll API : 如果有复杂的场景需要遍历大量文档,使用 Scroll API 是合适的选择。

  • 对于实时性要求较高的场景,需在实现中充分考虑数据一致性和状态管理。

高手之路
Java
License:  CC BY 4.0
Share

Further Reading

Sep 6, 2025

记一次前端优化(vue2)

由于博主所在公司的前端缺乏高端人员,导致前端代码臃肿,前端页面加载耗时基本上在min级别,于是博主决定进行一次优化 。首先贴上优化前的加载截图 ,明显可以看出 ,资源加载竟然加载了45.2MB,耗时在2.1min。 1. 引入Bundle Size检查打包体积 (1)下载依赖

Sep 6, 2025

SpringBoot3.X-2(缓存Redis/memory)

本文主要是实现缓存的集成,由于是单体项目 ,目前整合了内存缓存和Redis缓存两种,可以通过配置来切换。 引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-sta

Sep 5, 2025

SpringBoot3.X-1(MP+Druid)

本次基于springboot-3.5.5,先附上文档地址:https://docs.spring.io/spring-boot/reference/data/sql.html 初始化springboot项目 项目创建步骤不做记录 集成mybaits-plus</

OLDER

HTML编译成应用程序

NEWER

多线程的那些事

Recently Updated

  • 记一次前端优化(vue2)
  • SpringBoot3.X-2(缓存Redis/memory)
  • SpringBoot3.X-1(MP+Druid)
  • Onlyoffice编译
  • K6+Playwright实现并发测试

Trending Tags

Java Docker 前端 中间件 数据库 群晖 unraid

Contents

©2025 十六小站. Some rights reserved.

Using the Halo theme Chirpy