Elasticsearch 讲解及 Java 应用实战:从入门到落地
在数据量爆炸的今天,传统数据库的查询能力越来越难以满足复杂的检索需求。比如电商平台的商品搜索,需要支持关键词模糊匹配、多条件筛选、热门度排序等功能,这时候 Elasticsearch(简称 ES)就成了最佳选择。作为一款分布式全文搜索引擎,ES 以其强大的检索能力、灵活的扩展性被广泛应用。本文将从 ES 核心概念讲起,结合 Java 实战案例,带你快速掌握 ES 的应用技巧。
一、Elasticsearch 核心概念与优势
在使用 ES 前,我们需要先理清它的核心概念 —— 很多人会把 ES 和数据库类比,这个思路其实很实用。
1.1 ES 与关系型数据库的概念对应
如果把 ES 比作数据库,两者的核心概念可以这样对应:
关系型数据库
Elasticsearch
说明
Database(数据库)
Index(索引)
一个索引对应一类数据集合,比如 “商品索引”“用户索引”
Table(表)
Type(类型)
早期 ES 用于区分索引内的不同数据类型,7.x 后已废弃,一个索引只对应一种类型
Row(行)
Document(文档)
索引中的一条数据,以 JSON 格式存储
Column(列)
Field(字段)
文档中的一个属性,比如商品的 “名称”“价格”
Schema(表结构)
Mapping(映射)
定义文档中字段的类型、分词器等规则
1.2 ES 的核心优势
相比传统数据库和其他搜索引擎,ES 的核心竞争力体现在这几点:
- 全文检索能力:支持关键词分词、模糊匹配、同义词扩展等,比如搜索 “手机” 时能匹配 “智能手机”“移动电话”。
- 分布式架构:天然支持分片和副本,数据自动分片存储,副本保证高可用,可轻松扩展至海量数据。
- 近实时搜索:数据写入后秒级可查,兼顾实时性和性能。
- 灵活的聚合分析:支持统计、排序、分组等复杂分析,比如按 “商品分类” 统计销量 Top10。
二、Elasticsearch 核心操作入门
要使用 ES,首先要掌握其基础操作。ES 提供 RESTful API 接口,可通过 HTTP 请求直接操作,也可通过客户端工具(如 Kibana 的 Dev Tools)执行。
2.1 索引相关操作
(1)创建索引及映射
创建一个 “商品索引”,并定义字段映射(类似数据库建表时定义字段类型):
# 创建商品索引
PUT /product_index
{
\"mappings\": {
\"properties\": {
\"id\": { \"type\": \"keyword\" }, // 商品ID,精确匹配,不分词
\"name\": {
\"type\": \"text\",
\"analyzer\": \"ik_max_word\", // 使用IK分词器(中文分词必备)
\"fields\": {
\"keyword\": { \"type\": \"keyword\" } // 用于精确查询或排序
}
},
\"price\": { \"type\": \"double\" }, // 价格
\"category\": { \"type\": \"keyword\" }, // 分类,精确匹配
\"createTime\": { \"type\": \"date\", \"format\": \"yyyy-MM-dd HH:mm:ss\" } // 创建时间
}
}
}
这里有两个关键注意点:
- text 与 keyword:text类型用于全文检索(会分词),keyword用于精确匹配(不分词),同一个字段可通过fields同时支持两种类型。
- 中文分词:必须使用 IK 分词器(需提前安装),ik_max_word会将文本拆分为最细粒度(如 “华为手机” 拆为 “华为”“手机”),ik_smart为粗粒度拆分。
(2)查看索引映射
GET /product_index/_mapping
(3)删除索引
DELETE /product_index
2.2 文档相关操作
文档是 ES 中最小的数据单元,所有操作围绕文档展开。
(1)新增文档
# 新增文档(指定ID)
PUT /product_index/_doc/1001
{
\"id\": \"1001\",
\"name\": \"华为Mate 60 Pro 智能手机\",
\"price\": 6999.00,
\"category\": \"手机\",
\"createTime\": \"2024-01-15 09:30:00\"
}
# 新增文档(自动生成ID)
POST /product_index/_doc
{
\"id\": \"1002\",
\"name\": \"苹果iPhone 15 手机\",
\"price\": 7999.00,
\"category\": \"手机\",
\"createTime\": \"2024-01-20 14:20:00\"
}
(2)查询文档
ES 的查询能力非常强大,这里列举几种常用场景:
- 精确查询(根据分类查询手机商品):
GET /product_index/_search
{
\"query\": {
\"term\": {
\"category\": { \"value\": \"手机\" }
}
}
}
- 全文检索(搜索包含 “华为” 的商品):
GET /product_index/_search
{
\"query\": {
\"match\": {
\"name\": \"华为\"
}
}
}
- 组合条件查询(价格在 5000-8000,且分类为手机):
GET /product_index/_search
{
\"query\": {
\"bool\": {
\"must\": [
{ \"term\": { \"category\": \"手机\" } },
{ \"range\": { \"price\": { \"gte\": 5000, \"lte\": 8000 } } }
]
}
},
\"sort\": [{\"createTime\": \"desc\"}], // 按创建时间倒序
\"from\": 0, \"size\": 10 // 分页(从第0条开始,取10条)
}
(3)更新与删除文档
- 更新文档(全量更新或局部更新):
# 局部更新(只更新价格)
POST /product_index/_update/1001
{
\"doc\": {
\"price\": 6799.00
}
}
- 删除文档:
DELETE /product_index/_doc/1001
三、Java 操作 Elasticsearch 实战
实际开发中,我们很少直接调用 REST API,而是通过官方客户端工具操作。ES 官方推荐的 Java 客户端是 Elasticsearch Java Client(7.x 后替代了旧的 Transport Client)。
3.1 环境准备
(1)引入依赖
在 Maven 项目的pom.xml中添加依赖(版本需与 ES 服务器一致,这里以 8.10.4 为例):
co.elastic.clients
elasticsearch-java
8.10.4
com.fasterxml.jackson.core
jackson-databind
2.15.2
org.apache.httpcomponents.client5
httpclient5
5.3
(2)初始化客户端
创建 ES 客户端连接(类似数据库连接池):
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 EsClient {
// 单例客户端
private static ElasticsearchClient client;
static {
// 创建REST客户端(连接ES服务器,可配置多个节点)
RestClient restClient = RestClient.builder(
new HttpHost(\"localhost\", 9200, \"http\")
).build();
// 创建传输层
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);
// 创建客户端
client = new ElasticsearchClient(transport);
}
public static ElasticsearchClient getClient() {
return client;
}
}
注意:ES 客户端版本必须与服务器版本保持一致,否则可能出现兼容性问题。
3.2 核心操作实战:商品搜索功能
以 “电商商品搜索” 为例,实现文档新增、条件查询、分页排序等核心功能。
(1)定义商品实体类
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class Product {
private String id;
private String name;
private Double price;
private String category;
@JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")
private Date createTime;
}
(2)新增商品文档
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import java.io.IOException;
public class ProductService {
private final ElasticsearchClient client = EsClient.getClient();
// 新增商品到ES
public void addProduct(Product product) throws IOException {
// 构建索引请求(指定索引名、文档ID和数据)
IndexRequest request = IndexRequest.of(b -> b
.index(\"product_index\") // 索引名
.id(product.getId()) // 文档ID(可选,不指定则自动生成)
.document(product) // 文档数据
);
// 执行请求
IndexResponse response = client.index(request);
System.out.println(\"新增结果:\" + response.result());
}
}
(3)多条件查询商品
实现一个带条件、分页、排序的商品搜索功能:
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.TotalHits;
import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ProductService {
// ... 其他代码省略
/**
* 搜索商品
* @param keyword 关键词(搜索商品名称)
* @param category 分类(可选)
* @param minPrice 最低价格(可选)
* @param maxPrice 最高价格(可选)
* @param page 页码(从1开始)
* @param size 每页条数
* @return 商品列表及总条数
*/
public SearchResult searchProducts(String keyword, String category,
Double minPrice, Double maxPrice,
int page, int size) throws IOException {
// 构建查询条件
SearchRequest request = SearchRequest.of(b -> b
.index(\"product_index\") // 指定索引
.query(q -> q
.bool(bool -> {
// 拼接查询条件
if (keyword != null && !keyword.isEmpty()) {
// 全文检索商品名称
bool.must(m -> m.match(t -> t.field(\"name\").query(keyword)));
}
if (category != null && !category.isEmpty()) {
// 精确匹配分类
bool.filter(f -> f.term(t -> t.field(\"category\").value(category)));
}
if (minPrice != null || maxPrice != null) {
// 价格范围过滤
bool.filter(f -> f.range(r -> {
if (minPrice != null) r.gte(minPrice);
if (maxPrice != null) r.lte(maxPrice);
return r.field(\"price\");
}));
}
return bool;
})
)
.sort(s -> s.field(f -> f.field(\"createTime\").order(\"desc\"))) // 按创建时间倒序
.from((page - 1) * size) // 起始位置(分页)
.size(size) // 每页条数
);
// 执行查询
SearchResponse response = client.search(request, Product.class);
// 处理结果
List products = new ArrayList();
for (Hit hit : response.hits().hits()) {
products.add(hit.source()); // 获取文档数据
}
// 总条数
TotalHits totalHits = response.hits().total();
long total = 0;
if (totalHits != null && totalHits.relation() == TotalHitsRelation.Eq) {
total = totalHits.value();
}
return new SearchResult(total, products);
}
// 定义结果封装类
@Data
public static class SearchResult {
private long total; // 总条数
private List data; // 数据列表
public SearchResult(long total, List data) {
this.total = total;
this.data = data;
}
}
}
这个方法包含了 ES 查询的核心场景:
- 多条件组合:用bool查询拼接 “必须满足(must)” 和 “过滤(filter)” 条件;
- 全文检索:match查询用于商品名称的关键词搜索;
- 精确匹配与范围过滤:term查询匹配分类,range查询过滤价格;
- 分页与排序:通过from、size实现分页,sort指定排序规则。
3.3 性能优化技巧
在实际应用中,ES 的性能优化不可忽视,尤其是数据量大或查询频繁的场景:
- 合理设计映射:
-
- 不需要检索的字段设置index: false(如商品详情的大文本);
-
- 精确匹配字段用keyword类型,避免text类型的不必要分词。
- 优化查询语句:
-
- 用filter代替must(filter不计算评分,且可缓存结果);
-
- 避免wildcard前缀匹配(如name:*手机),会导致全表扫描;
-
- 分页查询时,深分页(如from:10000)改用search_after。
- 索引分片优化:
-
- 初始分片数设置为 “节点数 ×1~3”(如 3 个节点可设 5 个分片);
-
- 分片大小控制在 20GB~50GB 之间,过大影响查询性能。
- 批量操作:
// 批量新增示例
BulkRequest.Builder bulk = new BulkRequest.Builder();
for (Product product : productList) {
bulk.operations(op -> op
.index(idx -> idx
.index(\"product_index\")
.id(product.getId())
.document(product)
)
);
}
client.bulk(bulk.build());
-
- 批量新增 / 更新用BulkRequest,减少网络请求次数;
四、常见问题与解决方案
在 ES 应用中,新手容易遇到一些 “坑”,这里总结几个高频问题:
- 中文分词效果差:
-
- 原因:未安装 IK 分词器,默认分词器会把中文拆成单字;
-
- 解决:在 ES 插件目录安装 IK 分词器(版本与 ES 一致),并在映射中指定analyzer: \"ik_max_word\"。
- 查询结果与预期不符:
-
- 检查字段类型:如果用term查询text类型字段,可能因分词导致匹配失败(需用match查询);
-
- 检查分词结果:通过GET /product_index/_analyze测试字段分词效果。
- 写入 / 查询性能慢:
-
- 写入慢:检查分片副本数(初期可设 1 个副本)、是否开启批量写入;
-
- 查询慢:查看是否命中索引(通过explain分析查询计划)、是否需要增加节点或优化分片。
五、总结
Elasticsearch 是一款强大的搜索引擎,其核心价值在于全文检索和分布式扩展能力。本文从基础概念出发,通过 “理论 + 实战” 的方式讲解了 ES 的核心操作,并结合 Java 案例实现了商品搜索功能。
要熟练掌握 ES,关键在于:
- 理解索引、文档、映射等核心概念,明确text与keyword的区别;
- 掌握bool组合查询、范围查询、聚合分析等核心语法;
- 结合实际场景优化映射设计和查询语句,避免性能问题。
如果你在 ES 应用中遇到过特殊场景(如大数据量同步、复杂聚合分析),欢迎在评论区分享你的解决方案!