在数据爆炸的时代,传统关系型数据库在全文检索、复杂条件过滤等场景下性能不足。Elasticsearch(简称 ES)作为一款分布式、高扩展、高实时的全文搜索引擎,凭借其强大的检索能力和灵活的架构,成为日志分析、电商搜索、内容推荐等场景的首选工具。本文将从 Elasticsearch 核心概念出发,结合 Java 实战,带你快速入门 ES 的使用。

一、Elasticsearch 核心概念

在使用 Elasticsearch 前,需要先理解其核心术语和数据模型,这是后续操作的基础。

1. 基本概念

Elasticsearch 是基于 Lucene 的分布式搜索引擎,其核心概念与传统数据库有对应关系,但设计理念不同:

Elasticsearch 概念传统数据库概念说明
Index(索引)Database(库)一个索引是具有相似结构的文档集合(如“商品索引”“用户索引”)。
Document(文档)Row(行)索引中的每条数据记录,以 JSON 格式存储(如一个商品信息)。
Field(字段)Column(列)文档中的属性(如商品的“名称”“价格”)。
Mapping(映射)Schema(表结构)定义文档的字段类型、分词器等元数据(类似数据库表的字段定义)。
Shard(分片)-索引的分片,分布式存储的核心(一个索引可分为多个分片,分散在不同节点)。
Replica(副本)-分片的备份,用于故障转移和负载均衡(副本数越多,查询性能越好)。

2. 核心特性

  • 全文检索:支持关键词、短语、模糊匹配等复杂检索,内置分词器(可自定义)。
  • 分布式架构:自动分片和副本管理,支持水平扩展,应对海量数据。
  • 实时性:文档写入后近实时可查(延迟毫秒级)。
  • 聚合分析:支持统计、分组、排序等复杂分析(类似 SQL 的 GROUP BY、SUM 等)。

二、环境准备

1. 安装 Elasticsearch

推荐使用 Docker 快速部署(本地安装需提前配置 JDK 环境):

# 拉取 ES 镜像(7.14.0 版本为例,与后续客户端版本保持一致)
docker pull elasticsearch:7.14.0

# 启动容器(映射 9200 端口为 HTTP 接口,9300 为节点间通信端口)
docker run -d --name es -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" \  # 单节点模式(开发环境)
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \  # 限制 JVM 内存
  elasticsearch:7.14.0

启动后访问 http://localhost:9200,若返回以下 JSON 则说明启动成功:

{
  "name" : "xxxx",
  "cluster_name" : "docker-cluster",
  "version" : { "number" : "7.14.0", ... },
  "tagline" : "You Know, for Search"
}

2. 引入 Java 客户端依赖

Elasticsearch 官方推荐使用 Elasticsearch High Level REST Client(高level REST客户端),它封装了底层 HTTP 通信,支持所有 ES 操作。

在 Maven 项目的 pom.xml 中添加依赖(版本需与 ES 服务器一致):

<!-- Elasticsearch 核心依赖 -->
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.14.0</version>
</dependency>
<!-- 高level REST客户端 -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.14.0</version>
</dependency>
<!-- JSON 处理(客户端依赖 Jackson) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.5</version>
</dependency>

3. 初始化客户端

创建 ElasticsearchClient 工具类,初始化连接:

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

public class ESClient {
    // 单例客户端实例
    private static RestHighLevelClient client;

    static {
        // 初始化客户端(连接本地 ES 服务)
        client = new RestHighLevelClient(
            RestClient.builder(new HttpHost("localhost", 9200, "http"))
        );
    }

    // 获取客户端
    public static RestHighLevelClient getClient() {
        return client;
    }

    // 关闭客户端(程序退出时调用)
    public static void close() throws IOException {
        if (client != null) {
            client.close();
        }
    }
}

三、Java 操作 Elasticsearch 实战

以“商品搜索”场景为例,演示索引创建、文档 CRUD、查询等核心操作。

1. 定义商品实体类

文档在 Java 中通常映射为实体类,使用 Jackson 注解指定 JSON 字段名:

import com.fasterxml.jackson.annotation.JsonProperty;

public class Product {
    @JsonProperty("id")  // 对应文档中的 id 字段
    private Long id;
    @JsonProperty("name")  // 商品名称(需分词检索)
    private String name;
    @JsonProperty("price")  // 价格(数值类型)
    private Double price;
    @JsonProperty("category")  // 分类(keyword 类型,不分词)
    private String category;

    // 构造函数、getter、setter 省略
}

2. 创建索引与映射(Mapping)

索引需要先定义映射(类似表结构),指定字段类型和分词器。例如为商品索引定义映射:

import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.xcontent.XContentType;

public class IndexDemo {
    public static void createProductIndex() throws IOException {
        // 1. 创建创建索引的请求
        CreateIndexRequest request = new CreateIndexRequest("product");  // 索引名:product

        // 2. 定义映射(JSON 格式)
        String mappingJson = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\": { \"type\": \"long\" },\n" +  // 长整型
            "      \"name\": { \n" +
            "        \"type\": \"text\", \n" +  // text 类型支持分词
            "        \"analyzer\": \"ik_max_word\" \n" +  // 使用 IK 分词器(需提前安装)
            "      },\n" +
            "      \"price\": { \"type\": \"double\" },\n" +  // 浮点型
            "      \"category\": { \"type\": \"keyword\" }  // keyword 类型不分词
            "    }\n" +
            "  }\n" +
            "}";
        request.source(mappingJson, XContentType.JSON);

        // 3. 执行请求
        CreateIndexResponse response = ESClient.getClient()
            .indices()
            .create(request, RequestOptions.DEFAULT);

        // 4. 处理响应
        if (response.isAcknowledged()) {
            System.out.println("索引创建成功!");
        } else {
            System.out.println("索引创建失败!");
        }
    }

    public static void main(String[] args) throws IOException {
        createProductIndex();
        ESClient.close();  // 关闭客户端
    }
}
注意:ik_max_word 是中文分词器,需提前在 ES 中安装(下载 IK 插件,解压到 ES 插件目录)。

3. 文档操作(CRUD)

(1)新增文档(Create)

import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import com.fasterxml.jackson.databind.ObjectMapper;

public class DocumentDemo {
    // 新增商品文档
    public static void addProduct(Product product) throws IOException {
        // 1. 创建新增请求(指定索引和文档 ID)
        IndexRequest request = new IndexRequest("product");
        request.id(product.getId().toString());  // 文档 ID 设为商品 ID

        // 2. 将实体类转为 JSON 字符串
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(product);
        request.source(json, XContentType.JSON);

        // 3. 执行请求
        IndexResponse response = ESClient.getClient()
            .index(request, RequestOptions.DEFAULT);

        // 4. 输出结果
        System.out.println("新增文档结果:" + response.getResult());
    }

    public static void main(String[] args) throws IOException {
        // 新增一个商品
        Product product = new Product(1L, "华为 Mate 60 手机", 5999.0, "手机");
        addProduct(product);
        ESClient.close();
    }
}

(2)查询文档(Read)

import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.RequestOptions;
import com.fasterxml.jackson.databind.ObjectMapper;

public class DocumentDemo {
    // 根据 ID 查询文档
    public static Product getProductById(Long id) throws IOException {
        // 1. 创建查询请求
        GetRequest request = new GetRequest("product", id.toString());

        // 2. 执行请求
        GetResponse response = ESClient.getClient()
            .get(request, RequestOptions.DEFAULT);

        // 3. 解析结果
        if (response.isExists()) {
            String json = response.getSourceAsString();
            ObjectMapper mapper = new ObjectMapper();
            return mapper.readValue(json, Product.class);  // JSON 转实体类
        }
        return null;
    }

    public static void main(String[] args) throws IOException {
        Product product = getProductById(1L);
        System.out.println("查询到商品:" + product.getName());  // 输出:华为 Mate 60 手机
        ESClient.close();
    }
}

(3)更新文档(Update)

import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import com.fasterxml.jackson.databind.ObjectMapper;

public class DocumentDemo {
    // 更新商品价格
    public static void updateProductPrice(Long id, Double newPrice) throws IOException {
        // 1. 创建更新请求
        UpdateRequest request = new UpdateRequest("product", id.toString());

        // 2. 设置更新字段(部分更新)
        request.doc("price", newPrice);  // 仅更新 price 字段

        // 3. 执行请求
        UpdateResponse response = ESClient.getClient()
            .update(request, RequestOptions.DEFAULT);

        System.out.println("更新结果:" + response.getResult());
    }

    public static void main(String[] args) throws IOException {
        updateProductPrice(1L, 6299.0);  // 将价格改为 6299
        ESClient.close();
    }
}

(4)删除文档(Delete)

import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.client.RequestOptions;

public class DocumentDemo {
    // 删除文档
    public static void deleteProduct(Long id) throws IOException {
        // 1. 创建删除请求
        DeleteRequest request = new DeleteRequest("product", id.toString());

        // 2. 执行请求
        DeleteResponse response = ESClient.getClient()
            .delete(request, RequestOptions.DEFAULT);

        System.out.println("删除结果:" + response.getResult());
    }

    public static void main(String[] args) throws IOException {
        deleteProduct(1L);
        ESClient.close();
    }
}

4. 高级查询:全文检索与过滤

Elasticsearch 的核心价值在于强大的查询能力,例如“搜索名称包含‘手机’且价格小于 6000 的商品”:

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;

public class SearchDemo {
    // 搜索商品
    public static List<Product> searchProducts() throws IOException {
        // 1. 创建搜索请求
        SearchRequest request = new SearchRequest("product");

        // 2. 构建查询条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 组合查询:名称包含“手机”(全文检索)且价格 < 6000(范围过滤)
        sourceBuilder.query(QueryBuilders.boolQuery()
            .must(QueryBuilders.matchQuery("name", "手机"))  // 全文检索
            .filter(QueryBuilders.rangeQuery("price").lt(6000)));  // 过滤

        request.source(sourceBuilder);

        // 3. 执行查询
        SearchResponse response = ESClient.getClient()
            .search(request, RequestOptions.DEFAULT);

        // 4. 解析结果
        List<Product> products = new ArrayList<>();
        ObjectMapper mapper = new ObjectMapper();
        for (SearchHit hit : response.getHits().getHits()) {
            Product product = mapper.readValue(hit.getSourceAsString(), Product.class);
            products.add(product);
        }
        return products;
    }

    public static void main(String[] args) throws IOException {
        List<Product> products = searchProducts();
        System.out.println("搜索到 " + products.size() + " 个商品");
        for (Product p : products) {
            System.out.println(p.getName() + " - " + p.getPrice());
        }
        ESClient.close();
    }
}

四、入门必备:常见问题与最佳实践

1. 索引设计原则

  • 索引名小写:ES 索引名不允许大写字母。
  • 合理拆分索引:按时间(如日志索引 log-2024-10)或业务拆分,避免单索引过大。
  • Mapping 提前定义:避免动态映射导致字段类型不符合预期(如数字被识别为字符串)。

2. 性能优化建议

  • 批量操作:大量文档写入/更新时使用 BulkRequest,减少网络开销。
  • 合理设置分片:单索引分片数建议 3-5 个(过多会增加集群负担),副本数根据节点数配置(如 2 节点可设 1 个副本)。
  • 避免深分页:使用 scrollsearch_after 替代 from+size 实现大数据量分页。

3. 分词器选择

  • 中文场景:优先使用 IK 分词器(ik_max_word 细粒度分词,ik_smart 粗粒度分词)。
  • 英文场景:默认的 standard 分词器即可,或使用 english 分词器(支持词根提取)。

五、总结与进阶方向

本文通过实战案例,介绍了 Elasticsearch 的核心概念、Java 客户端的基本用法,包括索引创建、文档 CRUD 和高级查询。掌握这些内容后,你已具备 ES 入门能力。

进阶学习方向:

  • 聚合分析:使用 AggregationBuilder 实现统计(如按分类分组统计商品数量)。
  • 集群部署:学习 ES 集群配置、节点角色、分片分配策略。
  • 性能调优:JVM 内存配置、索引刷新频率、缓存机制等。
  • 结合框架:集成 Spring Data Elasticsearch,简化开发(提供 Repository 接口)。

Elasticsearch 是分布式搜索领域的佼佼者,学好它能让你在数据检索、分析场景中如虎添翼。动手实践是掌握的关键,赶紧尝试扩展本文案例吧!


本文作者:
文章标签:Java指南微服务Spring Cloud服务治理Elasticsearch
文章标题:Java 实现 Elasticsearch 入门指南:从概念到实战
本文地址:https://www.ducky.vip/archives/elasticsearch.html
版权说明:若无注明,本文皆 iDuckie's Blog 原创,转载请保留文章出处。
最后修改:2024 年 08 月 24 日
如果觉得我的文章对你有用,请随意赞赏