使用Elasticsearch(ES)时的简单优化案例

137 阅读4分钟

在使用Elasticsearch(ES)时,通过合理地优化索引,不仅可以大幅减少存储空间,还能显著提升查询效率。下面提供一个基于Spring Boot的实际案例,包含以下几个核心优化点:

  1. 合理的数据映射(Mapping)优化:定义合适的数据类型,避免不必要的字段。
  2. 索引分片和副本配置:根据数据量设定分片数和副本数,防止资源浪费。
  3. 文档压缩:启用压缩以减少索引的存储空间。
  4. 字段排除和包含策略:只索引和存储查询所需字段。
  5. 尽量使用聚合查询和批量查询

假设我们有一个社交媒体评论分析系统,用户可以通过关键词查询用户评论的相关信息。为了提高查询效率并减少存储空间,我们对ES索引进行以下优化。

场景描述

假设数据库里有一个 comments 索引,用于存储用户的评论数据。数据包含了评论内容、用户信息、点赞数、评论时间等。我们通过Spring Boot实现一个系统来优化索引的存储,并支持多条件查询和聚合分析。

项目结构

项目结构如下:

src
└── main
    ├── java
    │   └── com.example.elasticsearch
    │       ├── config
    │       │   └── ElasticsearchConfig.java
    │       ├── controller
    │       │   └── CommentController.java
    │       ├── service
    │       │   └── CommentService.java
    │       └── model
    │           └── Comment.java
    └── resources
        └── application.yml

步骤 1:配置 Elasticsearch 连接

ElasticsearchConfig.java 配置文件

package com.example.elasticsearch.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;

@Configuration
public class ElasticsearchConfig {
    
    @Bean
    public RestHighLevelClient client() {
        RestClientBuilder builder = RestClient.builder(new HttpHost("localhost", 9200, "http"));
        return new RestHighLevelClient(builder);
    }
}

步骤 2:定义索引的 Mapping 配置

创建索引时,我们可以通过Mapping配置优化字段的数据类型。通过使用 textkeyword 字段类型的组合、booleaninteger 类型的使用,我们既能减少存储空间,还能提升查询效率。

public void createIndexWithMapping() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("comments");

    request.mapping(
        "{\n" +
        "  \"properties\": {\n" +
        "    \"user_id\": {\"type\": \"keyword\"},\n" +
        "    \"comment_text\": {\"type\": \"text\", \"analyzer\": \"standard\"},\n" +
        "    \"like_count\": {\"type\": \"integer\"},\n" +
        "    \"is_flagged\": {\"type\": \"boolean\"},\n" +
        "    \"created_at\": {\"type\": \"date\"},\n" +
        "    \"tags\": {\"type\": \"keyword\"}\n" +
        "  }\n" +
        "}", XContentType.JSON);

    client.indices().create(request, RequestOptions.DEFAULT);
}

步骤 3:应用文档压缩

在创建索引时,通过启用存储压缩来减少存储空间。压缩会对存储的数据进行gzip压缩,大幅度减少存储量。

request.settings(Settings.builder()
        .put("index.codec", "best_compression")
        .put("number_of_shards", 3)
        .put("number_of_replicas", 1));

步骤 4:定义数据模型

Comment.java 数据模型

package com.example.elasticsearch.model;

import java.util.Date;

public class Comment {
    private String userId;
    private String commentText;
    private int likeCount;
    private boolean isFlagged;
    private Date createdAt;
    private String[] tags;

    // Getters and Setters
}

步骤 5:实现服务层的批量写入和查询优化

在服务层中,我们实现批量写入评论数据以及多条件查询和聚合查询以优化查询效率。

批量插入评论

批量插入能够减少对Elasticsearch的写入请求数量,提高性能。

public void bulkInsertComments(List<Comment> comments) throws IOException {
    BulkRequest bulkRequest = new BulkRequest();
    for (Comment comment : comments) {
        IndexRequest indexRequest = new IndexRequest("comments")
                .source("user_id", comment.getUserId(),
                        "comment_text", comment.getCommentText(),
                        "like_count", comment.getLikeCount(),
                        "is_flagged", comment.isFlagged(),
                        "created_at", comment.getCreatedAt(),
                        "tags", comment.getTags());
        bulkRequest.add(indexRequest);
    }
    client.bulk(bulkRequest, RequestOptions.DEFAULT);
}

优化的多条件查询和聚合查询

通过以下方法实现多条件组合查询,同时在查询中使用聚合功能统计每个标签的评论数。这不仅可以提高查询的准确性和性能,还能使聚合分析更加高效。

public SearchResponse searchComments(String keyword, String[] tags, Date startDate, Date endDate) throws IOException {
    SearchRequest searchRequest = new SearchRequest("comments");

    // 设置查询条件
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    boolQuery.must(QueryBuilders.matchQuery("comment_text", keyword));

    // 按标签过滤
    if (tags != null && tags.length > 0) {
        boolQuery.filter(QueryBuilders.termsQuery("tags", tags));
    }

    // 时间范围过滤
    if (startDate != null && endDate != null) {
        boolQuery.filter(QueryBuilders.rangeQuery("created_at")
                .gte(startDate.getTime())
                .lte(endDate.getTime()));
    }

    // 聚合查询,统计每个标签的数量
    searchRequest.source().query(boolQuery)
        .aggregation(AggregationBuilders.terms("tags_count").field("tags"));

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

步骤 6:定义 Controller 实现 REST API

CommentController.java 用于暴露 REST API,用户可以根据关键词、标签、时间范围等进行查询。

package com.example.elasticsearch.controller;

import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Date;

@RestController
@RequestMapping("/comments")
public class CommentController {

    private final CommentService commentService;

    public CommentController(CommentService commentService) {
        this.commentService = commentService;
    }

    @PostMapping("/bulk-insert")
    public String bulkInsertComments(@RequestBody List<Comment> comments) {
        commentService.bulkInsertComments(comments);
        return "Bulk insert successful";
    }

    @GetMapping("/search")
    public SearchResponse searchComments(
            @RequestParam String keyword,
            @RequestParam(required = false) String[] tags,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {

        return commentService.searchComments(keyword, tags, startDate, endDate);
    }
}

步骤 7:配置文件

application.yml 中配置Elasticsearch的连接信息。

spring:
  elasticsearch:
    rest:
      uris: http://localhost:9200

总结

通过优化 Mapping、启用文档压缩、批量写入数据、按需查询字段和使用聚合查询,我们可以显著提高Elasticsearch在评论分析系统中的存储和查询效率。这种基于Spring Boot和Elasticsearch的优化实践对于处理大规模社交媒体数据是非常有效的。