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 开发技能增添重要的一笔。

相关推荐
摇滚侠6 分钟前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌10 分钟前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包
Agent产品评测局17 分钟前
生产排期与MES/ERP系统打通,实操方法详解 —— 2026企业级智能体自动化选型与实战指南
java·运维·人工智能·ai·chatgpt·自动化
kft131417 分钟前
AI 驱动测试 2.0:当测试智能体成为你的“超级 QA“
大数据·人工智能·elasticsearch
cen__y19 分钟前
Linux07(信号01)
linux·运维·服务器·c语言·开发语言
阿丰资源37 分钟前
基于Spring Boot的电影城管理系统(直接运行)
java·spring boot·后端
xingpanvip39 分钟前
星盘接口开发文档:星相日历接口指南
android·开发语言·前端·css·php·lua
guygg881 小时前
基于遗传算法的双层规划模型求解MATLAB实现
开发语言·matlab
呱牛do it1 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)
java
凯瑟琳.奥古斯特1 小时前
SQLAlchemy核心功能解析
开发语言·python·flask