在大数据与云计算时代,“高效检索” 与 “实时分析” 成为业务突破的关键能力。Elasticsearch(简称 ES)作为一款开源分布式搜索与分析引擎,凭借其低延迟、高可扩、强灵活的特性,已成为日志分析、全文检索、业务监控等场景的 “标配工具”。本文将从 ES 的核心本质出发,深入剖析其架构原理,再通过完整的 Java 代码实现核心功能,帮助开发者从 “认知” 到 “落地” 全面掌握 ES。
一、Elasticsearch 本质认知:它到底是什么?
1. 定位与核心价值
ES 并非传统数据库,而是一款 **“搜索 + 分析” 一体化引擎 **,核心解决 “从海量非结构化 / 半结构化数据中快速找到目标信息” 的问题。与传统数据库(如 MySQL)相比,其优势体现在:
- 全文检索能力:支持中文、英文等多语言分词,可实现模糊匹配、短语搜索、权重排序(如 “搜索‘苹果手机’时,优先展示销量高的商品”)。
- 分布式天然适配:数据自动分片存储,集群可轻松扩展至数百节点,承载 PB 级数据。
- 实时性:数据写入后秒级可检索,延迟通常低于 100ms,满足日志监控、实时推荐等场景。
- 多维度分析:无需依赖 Hadoop 等工具,通过聚合功能即可实现 “按地区统计订单量”“计算商品价格分布” 等分析需求。
2. 核心概念与传统数据库对比
ES 的术语体系是理解其设计思想的关键,通过与 MySQL 类比可快速掌握:
Elasticsearch 概念 |
传统数据库(MySQL) |
核心作用 |
Index(索引) |
数据库(Database) |
存储一类结构相似的数据(如 “商品索引”“用户日志索引”),索引名需小写,无特殊字符 |
Document(文档) |
数据行(Row) |
索引的最小数据单元,以 JSON 格式存储(如{"id":"1001","name":"iPhone 15","price":7999}) |
Field(字段) |
数据列(Column) |
文档的属性(如 “name”“price”),支持多种类型(text、keyword、integer、date 等) |
Mapping(映射) |
表结构(Schema) |
定义字段的类型、分词器、是否可搜索等规则(如 “将‘name’设为 text 类型,使用 IK 分词器”) |
Shard(分片) |
无直接对应 |
将索引拆分后的小分片,分布式存储在不同节点,实现水平扩展(解决单节点存储上限问题) |
Replica(副本) |
主从复制(Slave) |
分片的备份,用于高可用(分片故障时自动切换)和读写分离(副本承担读请求) |
注意:ES 7.x 后已废弃Type(类型)(原对应 MySQL 的 Table),原因是 “同一索引下不同 Type 的字段可能冲突,导致分片存储效率低下”,建议通过不同索引区分数据类型(如 “商品索引” 和 “订单索引” 分开创建)。
二、Elasticsearch 架构原理:分布式背后的逻辑
ES 的强大源于其分布式架构设计,理解底层逻辑能帮助开发者在实际项目中避免 “踩坑”(如分片数量不合理导致性能瓶颈)。
1. 集群的核心组成:节点(Node)
一个 ES 集群由多个节点组成,每个节点是一台运行 ES 服务的服务器,按功能可分为 4 类:
- Master 节点:集群 “管理者”,负责创建索引、分片分配、节点加入 / 退出等管理操作,不承担数据存储和查询压力。建议部署 3 个节点(避免单点故障,通过选举机制实现高可用)。
- Data 节点:集群 “数据载体”,负责数据的存储(分片)、索引写入和查询请求处理。根据数据量和查询压力横向扩展(如 “从 3 个 Data 节点扩展到 5 个,提升查询吞吐量”)。
- Coordinating 节点:“请求入口”,接收客户端请求后,将请求分发到相关 Data 节点,汇总结果后返回给客户端。可由 Master 或 Data 节点兼任,高并发场景建议单独部署。
- Ingest 节点:数据 “预处理管道”,负责数据写入前的清洗(如 “过滤日志中的敏感字段”“将日期格式从‘yyyy-MM-dd’转为‘timestamp’”)。
2. 分片机制:分布式存储的核心
ES 通过分片(Shard) 实现数据的分布式存储,分为 “主分片” 和 “副本分片”:
- 主分片(Primary Shard):索引创建时指定的分片数量(一旦创建不可修改),用于数据写入和核心查询(如 “创建商品索引时,设置 3 个主分片”)。
- 副本分片(Replica Shard):主分片的备份,数量可动态调整(如 “为 3 个主分片各创建 1 个副本,共 6 个分片”),与主分片不在同一节点(避免节点宕机导致数据丢失)。
分片分配示例:
3 个 Data 节点,创建 “商品索引”(3 个主分片,1 个副本),最终分片分布如下:
- 节点 1:主分片 P0、副本分片 R1
- 节点 2:主分片 P1、副本分片 R2
- 节点 3:主分片 P2、副本分片 R0
此时:
- 写入数据:客户端请求先到 Coordinating 节点,根据文档 ID 哈希值路由到对应主分片(如 “文档 ID=1001” 路由到 P0),写入成功后同步到副本 R0。
- 查询数据:Coordinating 节点将请求分发到主分片或副本分片(如 “查询‘iPhone 15’时,同时查询 P0、P1、P2 的副本,并行返回结果”),提升读性能。
3. 数据写入与检索流程
(1)数据写入流程(以 “新增商品文档” 为例)
- 客户端通过 Java 代码(High Level REST Client)向 Coordinating 节点发送写入请求。
- Coordinating 节点计算文档 ID 的哈希值,确定应写入的主分片(如 P0)。
- 主分片 P0 验证文档格式(是否符合 Mapping 规则,如 “price” 是否为数值类型),写入数据并生成倒排索引(全文检索的核心结构,后文详解)。
- 主分片 P0 将数据同步到副本分片 R0。
- 副本 R0 同步完成后,主分片 P0 向 Coordinating 节点返回 “写入成功”,最终反馈给客户端。
(2)数据检索流程(以 “搜索‘苹果手机’为例)
- 客户端发送查询请求到 Coordinating 节点。
- Coordinating 节点将查询请求分发到所有主分片 / 副本分片(如 P0、P1、P2 及其副本)。
- 各分片执行查询,返回匹配的文档 ID 和相关性得分(Score,根据 “关键词出现频率”“文档热度” 等计算)。
- Coordinating 节点汇总所有分片的结果,按得分排序,取前 N 条(如前 20 条),再向对应分片请求完整文档数据。
- 分片返回完整文档,Coordinating 节点整理结果并返回给客户端。
4. 全文检索核心:倒排索引
ES 之所以能实现高效全文检索,核心在于倒排索引(Inverted Index) 结构,与传统数据库的 “正排索引”(按文档 ID 存储数据)相反:
- 正排索引:文档 ID → 文档内容(如 “文档 1001 → 我买了一部苹果手机”),适合按 ID 查询,但无法快速匹配 “包含‘手机’的文档”。
- 倒排索引:关键词 → 包含该关键词的文档 ID 列表(如 “手机 → 文档 1001、文档 1003、文档 1005”),直接通过关键词定位文档,检索效率提升 10 倍以上。
倒排索引的构成:
- 词典(Dictionary):存储所有去重后的关键词(如 “苹果”“手机”“华为”),通常以 B + 树结构存储,便于快速查找。
- postings 列表(Postings List):记录每个关键词对应的文档 ID、出现位置(如 “‘手机’在文档 1001 的第 3 个字符处出现”)、出现频率(如 “‘手机’在文档 1001 中出现 2 次”),用于计算文档与查询的相关性(Score)。
三、Elasticsearch 核心功能 Java 实战
ES 官方提供多种客户端(如 Transport Client、High Level REST Client),其中High Level REST Client(7.x + 推荐) 基于 HTTP 协议,支持所有 ES 功能,且与 ES 版本兼容性好。以下实战基于 ES 7.17.0 版本,通过 Java 代码实现全文搜索、聚合分析、数据同步、集群监控等核心功能。
1. 环境准备
(1)Maven 依赖配置
在pom.xml中引入 ES 客户端及相关依赖(版本需与 ES 集群一致):
<dependencies>
<!-- Elasticsearch High Level REST Client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.0</version>
</dependency>
<!-- Elasticsearch 核心依赖 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.17.0</version>
</dependency>
<!-- JSON 解析(用于文档序列化/反序列化) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
<!-- 日志依赖(用于调试) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
<scope>test</scope>
</dependency>
</dependencies>
(2)ES 客户端初始化(单例模式)
客户端创建成本较高,建议通过单例模式复用,避免频繁创建销毁连接:
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import java.io.IOException;
/**
* ES客户端工具类(单例模式)
*/
public class EsClientUtils {
// 单例客户端实例
private static RestHighLevelClient client;
// ES集群节点(多个节点用逗号分隔)
private static final String ES_NODES = "192.168.1.100:9200,192.168.1.101:9200";
// ES账号密码(若未开启认证,可删除相关代码)
private static final String USERNAME = "elastic";
private static final String PASSWORD = "123456";
// 私有构造器(防止外部实例化)
private EsClientUtils() {}
/**
* 获取ES客户端实例
*/
public static RestHighLevelClient getClient() {
if (client == null) {
synchronized (EsClientUtils.class) {
if (client == null) {
// 1. 解析ES节点
String[] nodes = ES_NODES.split(",");
HttpHost[] httpHosts = new HttpHost[nodes.length];
for (int i = 0; i < nodes.length; i++) {
String[] hostPort = nodes[i].split(":");
httpHosts[i] = new HttpHost(hostPort[0], Integer.parseInt(hostPort[1]), "http");
}
// 2. 配置账号密码认证(若未开启认证,可省略)
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(USERNAME, PASSWORD)
);
// 3. 构建客户端
client = new RestHighLevelClient(
RestClient.builder(httpHosts)
.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
)
);
}
}
}
return client;
}
/**
* 关闭客户端
*/
public static void closeClient() throws IOException {
if (client != null) {
client.close();
}
}
}
2. 索引与映射操作(基础必备)
在写入数据前,需先创建索引并定义映射(类似 MySQL 中 “先建表,再插入数据”)。以下代码实现 “创建商品索引(product)”,并定义字段映射规则:
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import java.io.IOException;
/**
* 索引与映射操作示例
*/
public class EsIndexDemo {
// 索引名称
private static final String INDEX_NAME = "product";
/**
* 创建索引并定义映射
* 需求:
* 1. 主分片数3,副本数1(适合3个Data节点的集群)
* 2. 字段映射:
* - id:keyword类型(不分词,用于精确匹配)
* - name:text类型(分词,使用IK中文分词器)
* - brand:keyword类型(不分词,用于分组统计)
* - price:double类型(用于范围查询、排序)
* - sales:integer类型(用于排序、聚合)
* - create_time:date类型(用于时间范围查询)
*/
public static void createIndexWithMapping() throws IOException {
RestHighLevelClient client = EsClientUtils.getClient();
// 1. 构建创建索引请求
CreateIndexRequest request = new CreateIndexRequest(INDEX_NAME);
// 2. 设置索引参数(分片、副本等)
request.settings(Settings.builder()
.put("number_of_shards", 3) // 主分片数
.put("number_of_replicas", 1) // 副本数
.put("refresh_interval", "1s") // 刷新间隔(数据写入后1秒可检索)
);
// 3. 定义映射规则
XContentBuilder mappingBuilder = JsonXContent.contentBuilder();
mappingBuilder.startObject()
.startObject("properties")
// id字段:keyword类型(不分词)
.startObject("id")
.field("type", "keyword")
.endObject()
// name字段:text类型,使用IK分词器(需提前在ES安装IK插件)
.startObject("name")
.field("</doubaocanvas>