Elasticsearch的理解与使用

发布于:2025-09-13 ⋅ 阅读:(12) ⋅ 点赞:(0)

        在大数据与云计算时代,“高效检索” 与 “实时分析” 成为业务突破的关键能力。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>