elastisearch多索引查询解析

elastisearch多索引查询解析


SearchRequest 是用来构建搜索请求的对象,它允许你指定要查询的一个或多个索引。下面我将详细解释 new SearchRequest(“index1”, “index2”) 如何实现多索引查询的过程。

1.创建 SearchRequest

  • 当你创建一个 SearchRequest 对象时,例如 new SearchRequest(“index1”, “index2”),你会传递索引名称作为构造函数的参数。
SearchRequest searchRequest = new SearchRequest("index1", "index2");

2.设置 SearchSourceBuilder

  • 在创建了 SearchRequest 对象之后,你可以通过 source() 方法设置查询条件。这通常涉及到一个 SearchSourceBuilder 对象。
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);

3. 发送请求

SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

4. 内部处理流程

4.1 解析请求

  • 当 Elasticsearch 收到请求时,它会解析 SearchRequest 中的信息,包括索引名称。

4.2 分发请求

  • Elasticsearch 会将请求分发到每个指定的索引。如果索引名称是以逗号分隔的列表,则会将其拆分成单独的索引名称

4.3 构建 ShardRequest

  • 对于每个索引,Elasticsearch 会构建一个 ShardRequest,它包含了具体的分片信息和查询条件。

4.4 分发到分片

  • 每个 ShardRequest 会被发送到对应的分片节点上。如果一个索引有多个分片,那么每个分片都会收到一个 ShardRequest。

4.5 执行查询

  • 每个分片上的 ShardRequest 会被处理,执行查询并返回结果。这个过程可能涉及到读取索引文件、执行过滤器、评分等操作。

4.6 收集结果

  • 每个分片完成后,结果会被收集到一个或多个 ShardSearchTransportService 实例中。

4.7 合并结果

  • 结果会被合并到协调节点(coordinating node),这是接收原始 SearchRequest 的节点。协调节点负责将所有分片的结果合并成一个统一的响应。

4.8 返回结果

  • 最终,协调节点会将合并后的结果打包成一个 SearchResponse 对象,并返回给客户端。

5.源码分析

5.1 SearchRequest

  • 在源码中,SearchRequest 的构造函数接受一个可变参数的索引名称列表,这些名称会被保存在 indices 字段中。
public SearchRequest(String... indices) {
    this.indices = indices;
}

5.2 TransportSearchAction

  • 当请求到达 Elasticsearch 时,它会被 TransportSearchAction 处理。TransportSearchAction 是处理搜索请求的核心类。

5.3 ShardSearchRequest

  • TransportSearchAction 会为每个索引构建 ShardSearchRequest 对象,这些对象包含了具体的分片信息和查询条件。
// 构建 ShardSearchRequest
ShardSearchRequest shardRequest = new ShardSearchRequest(indices, routing, preference, searchType, searchSource, globalContext);

5.4 ShardSearchTransportService

  • ShardSearchTransportService 负责将 ShardSearchRequest 发送到正确的分片,并收集结果。
// 发送 ShardSearchRequest 到分片
SearchPhaseController controller = clusterService.getClusterApplierService().getSearchPhaseController();
controller.executePhase(shardRequest, listener);

5.5 ShardSearchRequest 执行

  • 在分片节点上,ShardSearchRequest 会被执行,执行过程中涉及到查询解析、评分、过滤等操作。

5.6 ShardSearchResponse

  • 每个分片执行完毕后,会返回一个 ShardSearchResponse,其中包含了该分片的查询结果。

5.7 SearchPhaseController

  • SearchPhaseController 负责协调查询的不同阶段,包括查询、聚合、排序等。

5.8 SearchPhase

  • 查询的不同阶段(如查询阶段、聚合阶段等)由 SearchPhase 实现,它们负责处理查询的不同方面。

5.9 SearchResponse

  • 最终,所有分片的结果会被合并到一个 SearchResponse 对象中,该对象包含了整个查询的结果。

总结

通过上述流程,new SearchRequest(“index1”, “index2”) 实现了对多个索引的查询。Elasticsearch 会将请求分发到指定的索引,并在每个索引的所有分片上执行查询。最后,结果会被合并并返回给客户端。这种方式保证了查询能够高效地处理多个索引,同时也充分利用了集群的分布式特性。

6.索引数量设置多少合适

索引数量的影响

  • 元数据开销: 每个索引都有一定的元数据开销,包括索引的元数据存储和维护。
  • 分片管理: 每个索引都有主分片和副本分片,这增加了集群管理的复杂性。
  • 资源消耗: 索引越多意味着更多的资源被占用,包括内存、CPU 和磁盘 I/O。
  • 查询效率: 跨多个索引的查询会增加查询的时间,尤其是在没有使用别名的情况下。

索引数量的最佳实践

  • 避免过多的索引: 通常建议尽量减少索引的数量,以降低系统开销。
  • 使用别名: 利用别名来管理多个相关的索引,可以简化查询并提高效率。
  • 定期合并索引: 如果可能的话,定期合并旧的索引到一个新的索引中,以减少索引的数量。
  • 生命周期管理: 使用 Elasticsearch 的 Index Lifecycle Management (ILM) 来自动管理索引的生命周期,包括删除不再需要的索引。

推荐的索引数量

  • 一般情况下: 通常来说,一个集群中拥有几十到几百个索引是比较常见的。但是,这取决于你的具体应用场景和集群规模。
  • 最佳实践: 一个集群中索引的数量应该控制在一个合理的范围内,比如不超过 1000 个索引。当然,这个数字也会受到集群硬件配置、数据量大小、查询模式等因素的影响。
  • 极端情况: 如果你的集群非常强大并且经过精心优化,可以支持更多的索引数量。但是,超过 1000 个索引时,你应该考虑采取措施来优化集群性能。

索引设计

  • 合理设计索引: 为了减少索引数量,可以考虑将相似的数据类型放在同一个索引中,使用别名来访问这些索引。
  • 时间分区: 如果数据是按照时间进行分区的,可以使用日期模式来创建索引,并使用别名指向当前活动的索引。

监控和调整

  • 监控性能: 定期监控集群的性能指标,如 CPU 使用率、内存使用情况、磁盘 I/O 等。
  • 调整索引策略: 根据监控结果调整索引策略,比如合并索引、优化查询等。

总结

  • 索引数量应该根据你的具体需求和集群的实际情况来确定。
  • 使用别名和生命周期管理可以帮助你更好地管理索引。
  • 监控和调整策略是保持集群高性能的关键。

如何合并索引

索引合并的背景

  • 在 Elasticsearch 中,每个索引由一个或多个分片组成,每个分片又由一个或多个段(Segment)构成。每次写入新文档时,都会创建一个新的段。随着索引的增长,段的数量也会增加,这会导致查询性能下降,因为每次查询都需要遍历更多的段。

快速合并索引的方法

2.1 强制合并

你可以使用 _forcemerge API 来强制合并索引中的段。这可以显著减少段的数量,从而提高查询性能。但是,强制合并是一个资源密集型的操作,可能会暂时降低集群的写入性能。

POST /your_index/_forcemerge
{
  "max_num_segments": 1
}

这里的 max_num_segments 参数指定了合并后每个分片的最大段数量。设置为 1 表示每个分片只保留一个段。

2.2 配置合并策略

除了使用 _forcemerge API,你还可以通过调整合并策略来自动控制索引的合并行为。Elasticsearch 提供了一组默认的合并策略,你可以根据需要调整这些策略。

PUT /your_index/_settings
{
  "index": {
    "merge.policy": {
      "segments": {
        "max_merge_at_once": 2,
        "max_merged_segment_size": "5gb"
      }
    },
    "merge.scheduler.auto_throttle": false,
    "merge.scheduler.max_thread_count": 2
  }
}

这里的一些关键设置包括:

  • max_merge_at_once: 控制一次可以合并的段的最大数量。
  • max_merged_segment_size: 控制合并后段的最大大小。
  • auto_throttle: 是否自动调整合并的速度。
  • max_thread_count: 合并时使用的最大线程数。

3. 手动合并

手动合并通常在集群负载较低的时候进行,以减少对集群性能的影响。

POST /your_index/_forcemerge
{
  "max_num_segments": 1,
  "only_expunge_deletes": true
}
  • 这里的 only_expunge_deletes 参数表示只删除已经标记为删除的文档,而不合并其他段。

4. 避免频繁合并

频繁合并可能会导致性能问题。你可以通过以下方式来避免频繁合并:

  • 减少写入频率: 尽量减少小批量写入操作,可以考虑使用缓冲区来累积写入操作。
  • 使用批量索引: 使用批量索引 API 来一次性写入多个文档。
  • 调整刷新间隔: 通过调整 refresh_interval 来控制文档何时被刷新到磁盘。
PUT /your_index/_settings
{
  "index": {
    "refresh_interval": "-1"
  }
}

使用别名和重新索引

如果你想要合并多个索引为一个索引,可以使用别名和重新索引 API。

5.1 创建别名

创建一个别名指向所有需要合并的索引。

PUT /_aliases
{
  "actions": [
    { "add": { "index": "index1", "alias": "combined_index" } },
    { "add": { "index": "index2", "alias": "combined_index" } }
  ]
}

5.2 重新索引

使用重新索引 API 将数据从别名复制到一个新的索引。

POST _reindex
{
  "source": {
    "index": "combined_index"
  },
  "dest": {
    "index": "merged_index"
  }
}

5.3 删除旧索引

完成重新索引后,可以删除旧的索引。

DELETE index1,index2

5.4 总结

  • 使用 _forcemerge API 来快速合并索引内的段。
  • 调整合并策略来自动控制合并行为。
  • 手动合并应在低峰时段进行。
  • 避免频繁合并以减少性能影响。
  • 使用别名和重新索引 API 来合并多个索引为一个索引。

示例

我现在索引的创建是按月动态创建索引的,我想让三个月之前的索引合并为一个索引,如何用代码实现

  1. 确定需要合并的索引
  2. 创建别名
  3. 重新索引
  4. 验证数据
  5. 删除旧索引

1. 确定需要合并的索引

首先,你需要确定哪些索引需要被合并。假设你的索引命名规则是 logs-yyyy-MM,你可以使用 Elasticsearch 的 API 来获取所有符合条件的索引名称。

import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsRequest;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

public class MergeOldIndices {

    private static final String INDEX_PATTERN = "logs-%d-%02d"; // logs-yyyy-MM
    private static final String ALIAS_NAME = "old_logs";
    private static final int MONTHS_TO_KEEP_SEPARATE = 3;

    public static void main(String[] args) throws IOException {
        RestHighLevelClient client = new RestHighLevelClient(...); // 创建客户端

        LocalDate now = LocalDate.now();
        LocalDate threeMonthsAgo = now.minusMonths(MONTHS_TO_KEEP_SEPARATE);

        List<String> indicesToMerge = new ArrayList<>();
        for (int month = threeMonthsAgo.getMonthValue(); month <= now.getMonthValue(); month++) {
            LocalDate date = LocalDate.of(now.getYear(), month, 1);
            String indexName = String.format(INDEX_PATTERN, date.getYear(), date.getMonthValue());
            if (indexExists(client, indexName)) {
                indicesToMerge.add(indexName);
            }
        }

        mergeIndices(client, indicesToMerge, ALIAS_NAME);
    }

    private static boolean indexExists(RestHighLevelClient client, String indexName) throws IOException {
        IndicesExistsRequest request = new IndicesExistsRequest(indexName);
        IndicesExistsResponse response = client.indices().exists(request, RequestOptions.DEFAULT);
        return response.isExists();
    }

    private static void mergeIndices(RestHighLevelClient client, List<String> indicesToMerge, String aliasName) throws IOException {
        createAlias(client, indicesToMerge, aliasName);
        reindex(client, aliasName);
        validateData(client, aliasName);
        deleteOldIndices(client, indicesToMerge);
    }

    private static void createAlias(RestHighLevelClient client, List<String> indicesToMerge, String aliasName) throws IOException {
        // 创建别名
        List<AliasActions> actions = new ArrayList<>();
        for (String index : indicesToMerge) {
            actions.add(AliasActions.add().alias(aliasName).index(index));
        }

        client.indices().putAlias(new PutAliasRequest().actions(actions), RequestOptions.DEFAULT);
    }

    private static void reindex(RestHighLevelClient client, String aliasName) throws IOException {
        // 重新索引到新索引
        ReindexRequest reindexRequest = new ReindexRequest()
            .source(new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).index(aliasName))
            .destination("merged_logs_" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")));

        ReindexResponse reindexResponse = client.reindex(reindexRequest, RequestOptions.DEFAULT);
        System.out.println("Reindexing took: " + reindexResponse.getTotal() + " documents.");
    }

    private static void validateData(RestHighLevelClient client, String aliasName) throws IOException {
        // 验证数据
        SearchRequest searchRequest = new SearchRequest(aliasName);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        long totalHits = searchResponse.getHits().getTotalHits().value;
        System.out.println("Total hits in the alias: " + totalHits);
    }

    private static void deleteOldIndices(RestHighLevelClient client, List<String> indicesToMerge) throws IOException {
        // 删除旧索引
        client.indices().delete(new DeleteIndexRequest(indicesToMerge.toArray(new String[0])), RequestOptions.DEFAULT);
    }
}

1.确定需要合并的索引:

  • 使用当前日期计算出三个月之前的日期。
  • 构造索引名称并检查索引是否存在。

2.创建别名:

  • 为所有需要合并的索引创建一个别名。

3.重新索引

  • 使用重新索引 API 将数据从别名复制到一个新的索引。

4.验证数据:

  • 查询新索引以验证数据是否正确迁移。

5.删除旧索引:

  • 删除所有被合并的旧索引。

注意事项

  • 确保在生产环境中进行此类操作之前,先在测试环境中进行充分测试。
  • 重新索引操作可能会消耗较多资源,应选择在低峰时段进行。
  • 在删除旧索引之前,确保新索引中的数据完整无误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值