在数据爆炸的时代,传统关系型数据库在全文检索、复杂条件过滤等场景下性能不足。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 个副本)。
- 避免深分页:使用
scroll或search_after替代from+size实现大数据量分页。
3. 分词器选择
- 中文场景:优先使用 IK 分词器(
ik_max_word细粒度分词,ik_smart粗粒度分词)。 - 英文场景:默认的
standard分词器即可,或使用english分词器(支持词根提取)。
五、总结与进阶方向
本文通过实战案例,介绍了 Elasticsearch 的核心概念、Java 客户端的基本用法,包括索引创建、文档 CRUD 和高级查询。掌握这些内容后,你已具备 ES 入门能力。
进阶学习方向:
- 聚合分析:使用
AggregationBuilder实现统计(如按分类分组统计商品数量)。 - 集群部署:学习 ES 集群配置、节点角色、分片分配策略。
- 性能调优:JVM 内存配置、索引刷新频率、缓存机制等。
- 结合框架:集成 Spring Data Elasticsearch,简化开发(提供 Repository 接口)。
Elasticsearch 是分布式搜索领域的佼佼者,学好它能让你在数据检索、分析场景中如虎添翼。动手实践是掌握的关键,赶紧尝试扩展本文案例吧!