Java Elasticsearch 实战指南

Java Elasticsearch 实战指南

1. 引言

Elasticsearch 是一个基于 Lucene 的分布式搜索和分析引擎,它提供了一个分布式、多租户能力的全文搜索引擎,具有 HTTP Web 接口和无模式 JSON 文档。Elasticsearch 被广泛应用于日志分析、实时监控、全文搜索、数据可视化等场景。

1.1 Elasticsearch 的优势

  • 分布式架构:天生支持水平扩展,可轻松处理 PB 级数据
  • 实时性:近实时搜索和分析能力
  • 全文搜索:强大的全文搜索功能,支持多种语言
  • RESTful API:简单易用的 HTTP 接口
  • 弹性伸缩:支持节点的动态添加和移除
  • 高可用性:自动分片和副本机制确保数据安全

1.2 Elasticsearch 在 Java 生态中的应用

Java 是 Elasticsearch 的主要开发语言,同时 Elasticsearch 也为 Java 开发者提供了完善的客户端 API。在 Java 应用中,Elasticsearch 常用于:

  • 搜索引擎功能实现
  • 日志收集与分析(ELK Stack)
  • 实时监控与告警
  • 数据挖掘与分析
  • 企业级搜索解决方案

2. Elasticsearch 核心概念

在开始使用 Elasticsearch 之前,需要了解其核心概念:

2.1 索引(Index)

索引是 Elasticsearch 中存储数据的逻辑容器,可以理解为关系型数据库中的数据库。每个索引都有自己的映射(Mapping)和设置(Settings)。

2.2 文档(Document)

文档是 Elasticsearch 中存储的基本单位,类似于关系型数据库中的行。文档以 JSON 格式存储,具有唯一的 ID。

2.3 类型(Type)

在 Elasticsearch 7.x 之前,一个索引可以包含多个类型,类似于关系型数据库中的表。但在 7.x 版本之后,类型被废弃,一个索引只能包含一个类型(默认名为 _doc)。

2.4 映射(Mapping)

映射定义了文档的结构,类似于关系型数据库中的表结构。它指定了每个字段的数据类型、分词器等属性。

2.5 分片(Shard)

分片是索引的水平分割,每个分片都是一个独立的 Lucene 索引。分片允许索引扩展到超出单个节点的存储限制。

2.6 副本(Replica)

副本是分片的备份,用于提高可用性和搜索性能。副本分片不能与主分片在同一个节点上。

2.7 节点(Node)

节点是 Elasticsearch 集群中的单个服务器实例,负责存储数据和处理请求。

2.8 集群(Cluster)

集群由一个或多个节点组成,共同存储数据并提供搜索和分析功能。

3. Elasticsearch 安装与环境配置

3.1 系统要求

  • JDK 11 或更高版本
  • 至少 4GB RAM(推荐 8GB 或更高)
  • 足够的磁盘空间

3.2 安装步骤

3.2.1 下载 Elasticsearch

Elasticsearch 官网 下载最新版本的 Elasticsearch。

3.2.2 解压并运行
bash 复制代码
# 解压文件
tar -xzf elasticsearch-7.17.0-linux-x86_64.tar.gz

# 进入目录
cd elasticsearch-7.17.0

# 启动 Elasticsearch
./bin/elasticsearch
3.2.3 验证安装
bash 复制代码
curl -X GET "localhost:9200/"

如果安装成功,会返回类似以下信息:

json 复制代码
{
  "name" : "node-1",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "...",
  "version" : {
    "number" : "7.17.0",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "...",
    "build_date" : "...",
    "build_snapshot" : false,
    "lucene_version" : "8.11.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

3.3 安装 Kibana(可选)

Kibana 是 Elasticsearch 的可视化工具,用于数据探索和可视化。

bash 复制代码
# 下载 Kibana
wget https://artifacts.elastic.co/downloads/kibana/kibana-7.17.0-linux-x86_64.tar.gz

# 解压并运行
tar -xzf kibana-7.17.0-linux-x86_64.tar.gz
cd kibana-7.17.0-linux-x86_64
./bin/kibana

访问 http://localhost:5601 即可使用 Kibana。

4. Java 客户端 API

Elasticsearch 提供了两种 Java 客户端:

  1. High Level REST Client:高级 REST 客户端,提供了更友好的 API
  2. Java API Client:Elasticsearch 7.16+ 推荐使用的新客户端

4.1 添加依赖

在 Maven 项目中添加以下依赖:

xml 复制代码
<!-- Elasticsearch Java API Client -->
<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>7.17.0</version>
</dependency>

<!-- Jackson 依赖 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.2</version>
</dependency>

<!-- Apache HttpComponents 依赖 -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.1.3</version>
</dependency>

4.2 客户端初始化

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;

public class ElasticsearchClientUtil {
    
    public static ElasticsearchClient getClient() {
        // 创建低级 REST 客户端
        RestClient restClient = RestClient.builder(
                new HttpHost("localhost", 9200)
        ).build();

        // 创建传输层
        ElasticsearchTransport transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper());

        // 创建 API 客户端
        return new ElasticsearchClient(transport);
    }
}

5. 索引与文档操作

5.1 创建索引

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;

public class IndexOperations {
    
    public static void createIndex(ElasticsearchClient client, String indexName) throws Exception {
        CreateIndexRequest request = CreateIndexRequest.of(i -> i
                .index(indexName)
                .mappings(m -> m
                        .properties("title", p -> p.text(t -> t.analyzer("ik_max_word")))
                        .properties("content", p -> p.text(t -> t.analyzer("ik_max_word")))
                        .properties("author", p -> p.keyword())
                        .properties("publishDate", p -> p.date(d -> d.format("yyyy-MM-dd")))
                        .properties("views", p -> p.integer())
                )
        );
        
        CreateIndexResponse response = client.indices().create(request);
        System.out.println("Index created: " + response.acknowledged());
    }
}

5.2 创建文档

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.IndexResponse;

public class DocumentOperations {
    
    public static String createDocument(ElasticsearchClient client, String indexName, Article article) throws Exception {
        IndexRequest<Article> request = IndexRequest.of(i -> i
                .index(indexName)
                .document(article)
        );
        
        IndexResponse response = client.index(request);
        System.out.println("Document created with id: " + response.id());
        return response.id();
    }
}

// 文章实体类
class Article {
    private String title;
    private String content;
    private String author;
    private String publishDate;
    private int views;
    
    // 构造函数、getter 和 setter 方法
    // ...
}

5.3 查询文档

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.GetRequest;
import co.elastic.clients.elasticsearch.core.GetResponse;

public class DocumentOperations {
    
    public static Article getDocument(ElasticsearchClient client, String indexName, String id) throws Exception {
        GetRequest request = GetRequest.of(g -> g
                .index(indexName)
                .id(id)
        );
        
        GetResponse<Article> response = client.get(request, Article.class);
        if (response.found()) {
            return response.source();
        } else {
            System.out.println("Document not found");
            return null;
        }
    }
}

5.4 更新文档

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.UpdateRequest;
import co.elastic.clients.elasticsearch.core.UpdateResponse;

public class DocumentOperations {
    
    public static void updateDocument(ElasticsearchClient client, String indexName, String id, Article article) throws Exception {
        UpdateRequest<Article, Article> request = UpdateRequest.of(u -> u
                .index(indexName)
                .id(id)
                .doc(article)
        );
        
        UpdateResponse<Article> response = client.update(request, Article.class);
        System.out.println("Document updated: " + response.result().name());
    }
}

5.5 删除文档

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.DeleteRequest;
import co.elastic.clients.elasticsearch.core.DeleteResponse;

public class DocumentOperations {
    
    public static void deleteDocument(ElasticsearchClient client, String indexName, String id) throws Exception {
        DeleteRequest request = DeleteRequest.of(d -> d
                .index(indexName)
                .id(id)
        );
        
        DeleteResponse response = client.delete(request);
        System.out.println("Document deleted: " + response.result().name());
    }
}

6. 查询与过滤

6.1 全文搜索

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;

import java.util.List;

public class SearchOperations {
    
    public static List<Article> fullTextSearch(ElasticsearchClient client, String indexName, String keyword) throws Exception {
        SearchRequest request = SearchRequest.of(s -> s
                .index(indexName)
                .query(q -> q
                        .multiMatch(m -> m
                                .query(keyword)
                                .fields("title", "content")
                        )
                )
        );
        
        SearchResponse<Article> response = client.search(request, Article.class);
        
        return response.hits().hits().stream()
                .map(Hit::source)
                .toList();
    }
}

6.2 精确查询

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;

import java.util.List;

public class SearchOperations {
    
    public static List<Article> termSearch(ElasticsearchClient client, String indexName, String author) throws Exception {
        SearchRequest request = SearchRequest.of(s -> s
                .index(indexName)
                .query(q -> q
                        .term(t -> t
                                .field("author")
                                .value(author)
                        )
                )
        );
        
        SearchResponse<Article> response = client.search(request, Article.class);
        
        return response.hits().hits().stream()
                .map(Hit::source)
                .toList();
    }
}

6.3 范围查询

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;

import java.util.List;

public class SearchOperations {
    
    public static List<Article> rangeSearch(ElasticsearchClient client, String indexName, int minViews) throws Exception {
        SearchRequest request = SearchRequest.of(s -> s
                .index(indexName)
                .query(q -> q
                        .range(r -> r
                                .field("views")
                                .gte(minViews)
                        )
                )
        );
        
        SearchResponse<Article> response = client.search(request, Article.class);
        
        return response.hits().hits().stream()
                .map(Hit::source)
                .toList();
    }
}

6.4 复合查询

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;

import java.util.List;

public class SearchOperations {
    
    public static List<Article> boolSearch(ElasticsearchClient client, String indexName, String keyword, String author) throws Exception {
        SearchRequest request = SearchRequest.of(s -> s
                .index(indexName)
                .query(q -> q
                        .bool(b -> b
                                .must(m -> m
                                        .multiMatch(mm -> mm
                                                .query(keyword)
                                                .fields("title", "content")
                                        )
                                )
                                .filter(f -> f
                                        .term(t -> t
                                                .field("author")
                                                .value(author)
                                        )
                                )
                        )
                )
        );
        
        SearchResponse<Article> response = client.search(request, Article.class);
        
        return response.hits().hits().stream()
                .map(Hit::source)
                .toList();
    }
}

7. 聚合分析

7.1 统计聚合

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Aggregate;
import co.elastic.clients.elasticsearch.core.search.DoubleTermsAggregate;

import java.util.Map;

public class AggregationOperations {
    
    public static void termAggregation(ElasticsearchClient client, String indexName) throws Exception {
        SearchRequest request = SearchRequest.of(s -> s
                .index(indexName)
                .size(0)
                .aggregations("author_stats", a -> a
                        .terms(t -> t
                                .field("author")
                                .size(10)
                        )
                )
        );
        
        SearchResponse<Article> response = client.search(request, Article.class);
        
        Map<String, Aggregate> aggregations = response.aggregations();
        DoubleTermsAggregate authorStats = aggregations.get("author_stats").sterms();
        
        authorStats.buckets().array().forEach(bucket -> {
            System.out.println("Author: " + bucket.key() + ", Count: " + bucket.docCount());
        });
    }
}

7.2 指标聚合

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Aggregate;
import co.elastic.clients.elasticsearch.core.search.ValueCountAggregate;
import co.elastic.clients.elasticsearch.core.search.AvgAggregate;

import java.util.Map;

public class AggregationOperations {
    
    public static void metricsAggregation(ElasticsearchClient client, String indexName) throws Exception {
        SearchRequest request = SearchRequest.of(s -> s
                .index(indexName)
                .size(0)
                .aggregations("total_articles", a -> a.valueCount(v -> v.field("_id")))
                .aggregations("avg_views", a -> a.avg(v -> v.field("views")))
        );
        
        SearchResponse<Article> response = client.search(request, Article.class);
        
        Map<String, Aggregate> aggregations = response.aggregations();
        
        ValueCountAggregate totalArticles = aggregations.get("total_articles").valueCount();
        AvgAggregate avgViews = aggregations.get("avg_views").avg();
        
        System.out.println("Total articles: " + totalArticles.value());
        System.out.println("Average views: " + avgViews.value());
    }
}

8. 高级特性

8.1 高亮搜索

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.highlight.HighlightField;

import java.util.List;
import java.util.Map;

public class AdvancedSearchOperations {
    
    public static void highlightSearch(ElasticsearchClient client, String indexName, String keyword) throws Exception {
        SearchRequest request = SearchRequest.of(s -> s
                .index(indexName)
                .query(q -> q
                        .multiMatch(m -> m
                                .query(keyword)
                                .fields("title", "content")
                        )
                )
                .highlight(h -> h
                        .fields("title", f -> f)
                        .fields("content", f -> f)
                )
        );
        
        SearchResponse<Article> response = client.search(request, Article.class);
        
        for (Hit<Article> hit : response.hits().hits()) {
            System.out.println("Title: " + hit.source().getTitle());
            
            // 输出高亮内容
            Map<String, List<String>> highlightFields = hit.highlight();
            if (highlightFields.containsKey("title")) {
                System.out.println("Highlighted Title: " + highlightFields.get("title").get(0));
            }
            if (highlightFields.containsKey("content")) {
                System.out.println("Highlighted Content: " + highlightFields.get("content").get(0));
            }
        }
    }
}

8.2 分页搜索

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;

import java.util.List;

public class AdvancedSearchOperations {
    
    public static List<Article> paginatedSearch(ElasticsearchClient client, String indexName, int page, int pageSize) throws Exception {
        SearchRequest request = SearchRequest.of(s -> s
                .index(indexName)
                .from((page - 1) * pageSize)
                .size(pageSize)
                .sort(sort -> sort
                        .field(f -> f
                                .field("publishDate")
                                .order("desc")
                        )
                )
        );
        
        SearchResponse<Article> response = client.search(request, Article.class);
        
        return response.hits().hits().stream()
                .map(Hit::source)
                .toList();
    }
}

8.3 批量操作

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import co.elastic.clients.elasticsearch.core.BulkResponse;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;

import java.util.List;

public class BulkOperations {
    
    public static void bulkIndex(ElasticsearchClient client, String indexName, List<Article> articles) throws Exception {
        List<BulkOperation> operations = articles.stream()
                .map(article -> BulkOperation.of(o -> o
                        .index(i -> i
                                .index(indexName)
                                .document(article)
                        )
                ))
                .toList();
        
        BulkRequest request = BulkRequest.of(b -> b
                .operations(operations)
        );
        
        BulkResponse response = client.bulk(request);
        
        if (response.errors()) {
            System.out.println("Bulk operation failed");
            response.items().forEach(item -> {
                if (item.error() != null) {
                    System.out.println("Error: " + item.error().reason());
                }
            });
        } else {
            System.out.println("Bulk operation succeeded. Total: " + response.items().size());
        }
    }
}

9. 最佳实践

9.1 索引设计

  • 合理规划索引:根据业务需求设计索引结构,避免过度设计
  • 选择合适的字段类型:根据数据特点选择合适的字段类型
  • 使用适当的分词器:中文建议使用 IK 分词器
  • 避免过多的字段:每个文档的字段数量不宜过多

9.2 查询优化

  • 使用过滤查询:对于不需要评分的查询,使用 filter 上下文
  • 减少返回字段:只返回需要的字段,使用 _source 过滤
  • 合理设置分页:避免深分页,使用 search_after 替代 from/size
  • 使用缓存:对于频繁的查询,考虑使用查询缓存

9.3 性能优化

  • 合理设置分片数:根据数据量和节点数设置合适的分片数
  • 监控资源使用:定期监控内存、CPU、磁盘使用情况
  • 优化 JVM 参数:根据实际情况调整 JVM 堆大小
  • 使用索引别名:便于索引的平滑迁移和升级

9.4 数据安全

  • 启用认证和授权:设置用户名和密码,限制访问权限
  • 启用 HTTPS:加密传输数据
  • 定期备份:使用快照功能定期备份数据
  • 设置合理的索引生命周期:自动管理索引的创建、删除和归档

10. 总结

Elasticsearch 是一个功能强大的搜索和分析引擎,结合 Java 客户端 API,可以轻松地在 Java 应用中实现搜索、分析和数据可视化功能。本文介绍了 Elasticsearch 的核心概念、Java 客户端的使用、索引和文档操作、查询和聚合分析、高级特性以及最佳实践。

通过本文的学习,您应该能够:

  1. 理解 Elasticsearch 的核心概念和架构
  2. 安装和配置 Elasticsearch 环境
  3. 使用 Java 客户端 API 与 Elasticsearch 交互
  4. 实现索引创建、文档增删改查操作
  5. 进行复杂的查询和聚合分析
  6. 应用 Elasticsearch 最佳实践优化性能

Elasticsearch 生态系统还包括 Kibana(可视化)、Logstash(数据收集)等组件,它们共同构成了 ELK Stack,可以帮助您构建完整的日志分析和监控系统。

随着大数据和实时分析需求的不断增长,Elasticsearch 在企业级应用中的应用越来越广泛。掌握 Elasticsearch 的使用,将为您的 Java 开发技能增添重要的一笔。

相关推荐
okseekw1 小时前
Java 中的注释与关键字的初步学习
java
雾岛听蓝1 小时前
C++ 类和对象(一):从概念到实践,吃透类的核心基础
开发语言·c++·经验分享·笔记
luv_sw1 小时前
JavaSE-面向对象-构造器
java
okseekw1 小时前
Java 中的类型转换:结合实战代码深入解析
java
CoderYanger1 小时前
优选算法-优先级队列(堆):75.数据流中的第K大元素
java·开发语言·算法·leetcode·职场和发展·1024程序员节
luv_sw2 小时前
JavaSE-面向对象-抽象类和接口
java
TracyCoder1232 小时前
MySQL 实战宝典(八):Java后端MySQL分库分表工具解析与选型秘籍
java·开发语言·mysql
非凡的世界2 小时前
为什么我和越来越多的PHP程序员,选择了 Webman ?
开发语言·php·workman·webman
wasp5202 小时前
做了技术管理后,我发现技术和管理其实可以兼得
java·运维·网络