Elasticsearch 实战系列(一):从核心基础概念入门到实战落地

Elasticsearch 实战系列(一):从核心基础概念入门到实战落地

一、初识 Elasticsearch:是什么、为什么用、用在哪

1.1 核心定义

Elasticsearch(简称 ES)是一个基于 Apache Lucene 构建的分布式、近实时的搜索与分析引擎,是 Elastic Stack(原 ELK 技术栈,Elasticsearch、Logstash、Kibana)的核心组件。它通过 RESTful API 对外提供服务,以 JSON 格式存储文档数据,能够实现海量数据的快速检索、统计分析,是目前业界最主流的全文检索引擎。

1.2 核心特性

  • 分布式高可用架构:天然支持集群部署,数据分片存储,可通过增减节点实现无缝水平扩展
  • 近实时搜索:从文档写入索引到可被检索,延迟通常在 1 秒以内,满足绝大多数实时业务需求
  • 强大的全文检索能力:内置多种分词器,支持复杂的文本匹配、模糊查询、相关性排序,对中文场景有成熟的适配方案
  • RESTful API 风格:所有操作均可通过 HTTP 接口完成,无语言绑定,适配所有开发语言
  • 多租户能力:支持多索引、跨索引联合查询,可灵活隔离不同业务的数据
  • 文档导向存储:以 JSON 格式存储结构化 / 半结构化数据,无需提前严格定义表结构,适配灵活的业务场景

1.3 主流应用场景

ES 的能力覆盖搜索、分析、监控三大核心方向,主流落地场景包括:

  • 通用搜索引擎:电商商品搜索、内容平台文章 / 视频搜索、站内全局搜索
  • 日志实时分析:搭配 ELK/EFK 技术栈,实现海量服务日志的采集、存储、检索、可视化分析,是微服务架构下的日志排查核心方案
  • 实时数据分析:业务数据的实时统计、聚合分析,比如电商订单实时大盘、用户行为数据统计
  • 系统监控与告警:结合指标数据实现系统可用性监控、APM 性能监控、异常阈值告警
  • 个性化推荐系统:基于用户行为数据,实现内容、商品的个性化推荐检索

1.4 ES vs 传统关系型数据库(MySQL):核心差异与互补

很多新手会疑惑:有了 MySQL,为什么还要用 ES?这里通过表格清晰对比两者的核心差异,明确各自的适用边界:

核心特性 Elasticsearch MySQL
数据模型 文档型(JSON 格式),无固定表结构 关系型(二维表结构),需提前定义 Schema
查询语言 DSL(JSON 格式的查询语法) SQL 结构化查询语言
核心定位 全文检索、海量数据统计分析 事务型业务数据存储、强一致性事务处理
扩展能力 天然支持水平扩展,可无缝扩容节点 以垂直扩展为主,水平扩展需分库分表,复杂度高
全文检索 原生支持,内置分词、相关性排序能力 仅支持基础模糊查询,全文检索需额外插件适配,性能差
事务支持 无完整事务支持,不支持跨行事务 完整支持 ACID 事务,支持行级锁、表级锁
写入性能 批量写入性能优异,适合海量数据高吞吐写入 事务写入有性能开销,适合强一致性的业务数据写入

重点说明:ES 并非用来替代 MySQL,而是形成互补。绝大多数业务场景中,都是 MySQL 存储核心业务事务数据,ES 同步数据实现全文检索与统计分析,两者搭配实现完整的业务能力。


二、ES 核心概念:用 MySQL 类比快速入门

对于熟悉 MySQL 的开发者,最快理解 ES 核心概念的方式,就是通过与 MySQL 的术语类比,先建立整体认知,再逐个拆解细节。

2.1 核心概念对照表(ES ↔ MySQL)

Elasticsearch 术语 MySQL 对应术语 核心说明
Index(索引) Database(数据库) 存储文档数据的容器,一个索引对应一类业务数据,类似 MySQL 中一个库
Type(类型) Table(表) 7.x 版本已废弃,8.x 完全移除,7.x 之后一个索引仅支持固定的_doc类型,无需自定义
Document(文档) Row(行) 一条完整的数据记录,以 JSON 格式存储,类似 MySQL 中表的一行数据
Field(字段) Column(列) 文档中的属性字段,类似 MySQL 中表的一列
Mapping(映射) Schema(表结构) 定义索引中字段的类型、分词规则、存储方式等,类似 MySQL 的表结构定义
Shard(分片) Partition(分区) 索引的数据分片,将一个索引的数据拆分到多个节点存储,实现水平扩展

2.2 核心概念详解

2.2.1 索引(Index)

索引是 ES 中存储同类型文档的集合,是 ES 数据管理的顶级单元,类似 MySQL 中的数据库。索引的命名有严格的规则限制,不符合规则的索引会创建失败,具体规则如下:

ini 复制代码
✅ 仅支持小写字母
✅ 不能以 -、_、+ 开头
✅ 不能包含 \、/、*、?、"、<、>、|、空格、逗号、# 等特殊字符
✅ 名称长度不能超过255字节

合法索引名称示例:user_indexproduct-2026log_app_20260301

2.2.2 文档(Document)

文档是 ES 中可被索引、检索的最小数据单元,对应 MySQL 中的一行数据,以 JSON 格式存储,支持灵活的结构化、半结构化数据。一个标准的文档示例如下:

json 复制代码
{
  "id": 1,
  "name": "张三",
  "age": 25,
  "email": "zhangsan@example.com",
  "tags": ["Java", "Elasticsearch"],
  "create_time": "2026-01-01T10:00:00"
}

每个文档都有唯一的_id标识,可手动指定,也可由 ES 自动生成,同时自带_index_version等元数据,用于标识文档所属索引、版本号等信息。

2.2.3 映射(Mapping)

Mapping 定义了索引中字段的类型、分词规则、是否索引、是否存储等属性,类似 MySQL 中的表结构定义。ES 支持动态映射(写入数据时自动推断字段类型),但生产环境强烈建议手动定义 Mapping,避免自动推断导致的类型错误,影响检索效果。ES 常用的字段类型如下表,是 Mapping 设计的核心:

字段类型 核心说明 典型适用场景
text 全文本类型,写入时会进行分词,支持全文检索,不支持排序、聚合 文章内容、商品描述、用户昵称、新闻正文等需要模糊搜索的文本
keyword 关键字类型,写入时不分词,完整保留原始文本,支持精确匹配、排序、聚合 用户 ID、订单号、状态码、标签、城市、邮箱、手机号等需要精确匹配的字段
long/integer 整数数值类型,支持范围查询、排序、聚合 年龄、商品库存、订单数量、浏览量等整数数据
double/float 浮点数值类型,支持范围查询、排序、聚合 商品价格、评分、折扣率等带小数的数值数据
boolean 布尔值类型,仅支持 true/false 状态标识、是否删除、是否生效等二值场景
date 日期类型,支持多种日期格式,支持时间范围查询、排序、聚合 创建时间、更新时间、订单时间、登录时间等时间字段
object 对象类型,支持嵌套 JSON 对象,适合存储一对一的关联数据 用户的收货地址、商品的规格信息等单嵌套对象
nested 嵌套对象数组类型,专门用于处理对象数组,避免数组扁平化导致的查询错误 订单明细、商品的多规格列表等一对多的嵌套数组场景

新手高频踩坑提示:textkeyword是最容易用错的类型。需要模糊搜索的字段用text,需要精确匹配、排序、聚合的字段用keyword,如果把需要精确匹配的字段设为text,会导致 term 查询无法匹配到结果。

2.2.4 分片与副本(Shard & Replica)

ES 的分布式能力,核心是通过分片与副本实现的,两者的定义与作用如下:

  • 主分片(Primary Shard) :一个索引的数据会被拆分为多个主分片,分散存储在集群的不同节点上,实现数据的水平拆分。主分片的数量必须在创建索引时指定,创建后不可修改,如需调整只能重建索引。
  • 副本分片(Replica Shard) :主分片的备份副本,与主分片存储完全一致的数据,用于实现故障转移(主分片所在节点宕机时,副本分片可升级为主分片,保证数据不丢失、服务不中断),同时可分担查询请求,提升检索性能。副本分片的数量可动态调整,无需重启服务。

分片与副本的核心优势:

  1. 水平扩展:数据分散到多个节点,突破单节点的存储、性能瓶颈,可通过新增节点无缝扩容
  2. 高可用:副本分片提供故障转移能力,单个 / 多个节点宕机不影响集群正常服务
  3. 性能提升:查询请求可并行在多个分片上执行,大幅提升海量数据的检索效率

踩坑提示:单节点测试环境下,建议将副本数设置为 0,否则副本分片无法分配到其他节点,集群健康状态会变为黄色;生产环境建议副本数≥1,保证数据高可用。


三、环境搭建:3 分钟快速启动 ES 服务

本文推荐使用 7.17.10 版本,该版本是 7.x 的长期稳定支持版本,兼容性好、文档完善,适合入门学习与生产环境使用,同时配套同版本的 Kibana 可视化工具,方便后续操作与调试。

3.1 推荐方式:Docker 一键部署(单节点)

Docker 方式无需配置本地 Java 环境,一键启动,避免环境依赖问题,是本地学习测试的首选方案。

步骤 1:拉取 ES 镜像
shell 复制代码
# 拉取7.17.10版本ES镜像
docker pull elasticsearch:7.17.10
步骤 2:创建 Docker 专属网络
shell 复制代码
# 创建独立网络,用于ES与Kibana容器通信
docker network create elastic
步骤 3:启动 ES 单节点容器
shell 复制代码
# 后台启动ES容器
docker run -d \
  --name elasticsearch \
  --net elastic \
  -p 9200:9200 \
  -p 9300:9300 \
  -e "discovery.type=single-node" \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  elasticsearch:7.17.10
步骤 4:查看启动日志,确认启动成功
shell 复制代码
# 实时查看容器日志
docker logs -f elasticsearch

日志中出现started字样,即表示 ES 启动成功。

核心参数说明:

  • discovery.type=single-node:单节点模式,关闭集群发现机制,本地测试必须添加,否则会启动失败
  • ES_JAVA_OPTS=-Xms512m -Xmx512m:设置 JVM 堆内存,初始值与最大值建议保持一致,避免堆内存动态调整的性能损耗,本地测试建议不小于 512m
  • 9200端口:ES 对外提供 HTTP 服务的端口,所有 RESTful API 均通过该端口访问
  • 9300端口:ES 集群内部节点通信的 TCP 端口

3.2 配套可视化工具:Kibana 安装部署

Kibana 是 Elastic 官方提供的 ES 可视化管理工具,提供了 Dev Tools 控制台,可便捷地执行 ES 的 API 命令、查看索引数据、实现数据可视化,是 ES 开发调试的必备工具,版本必须与 ES 完全一致

步骤 1:拉取 Kibana 镜像
shell 复制代码
# 拉取与ES同版本的Kibana镜像
docker pull kibana:7.17.10
步骤 2:启动 Kibana 容器
shell 复制代码
# 后台启动Kibana容器
docker run -d \
  --name kibana \
  --net elastic \
  -p 5601:5601 \
  -e "ELASTICSEARCH_HOSTS=http://elasticsearch:9200" \
  kibana:7.17.10
步骤 3:访问 Kibana

容器启动成功后,浏览器打开 http://localhost:5601 即可访问 Kibana,后续所有 ES 的 API 操作,都可以在 Kibana 的 Dev Tools > Console 控制台中执行,无需手动拼接 curl 命令。

3.3 备用方式:本地安装包部署

如果不想使用 Docker,可通过官方安装包本地部署,以 Linux 系统为例,步骤如下:

shell 复制代码
# 1. 下载ES 7.17.10安装包
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.10-linux-x86_64.tar.gz

# 2. 解压安装包
tar -xzf elasticsearch-7.17.10-linux-x86_64.tar.gz

# 3. 进入安装目录
cd elasticsearch-7.17.10

# 4. 前台启动ES
./bin/elasticsearch

# 5. 后台启动ES(生产环境使用)
./bin/elasticsearch -d

注意:ES 不允许使用 root 用户启动,需创建普通用户执行启动命令,同时需提前配置 Java 环境,JDK 版本需与 ES 版本兼容。

3.4 安装验证:确认服务正常运行

ES 启动成功后,可通过 curl 命令验证服务是否正常,执行以下命令:

shell 复制代码
curl http://localhost:9200

正常返回结果如下,说明 ES 服务启动成功:

json 复制代码
{
  "name" : "node-1",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "xxxxxx",
  "version" : {
    "number" : "7.17.10",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "xxxxxx",
    "build_date" : "2023-01-23T05:33:12.168997957Z",
    "build_snapshot" : false,
    "lucene_version" : "8.11.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

四、ES 核心基础操作:索引、文档的 CRUD 全掌握

ES 的所有操作均通过 RESTful API 完成,遵循 HTTP 标准方法(PUT/POST/GET/DELETE),以下所有命令均可直接在 Kibana Dev Tools 中执行。

4.1 索引操作:增、删、查

索引是 ES 数据存储的顶级单元,核心操作包括创建、查看、删除三类。

4.1.1 创建索引

创建索引时,可指定主分片数、副本数等 settings 配置,也可同时定义 Mapping 映射,语法如下:

json 复制代码
# 创建用户索引,指定3个主分片,1个副本分片
PUT /user_index
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}

创建成功返回结果:

json 复制代码
{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "user_index"
}

说明:acknowledged: true 表示索引创建成功,shards_acknowledged: true 表示分片已成功分配。

4.1.2 查看索引

支持查看所有索引列表、指定索引详情、索引配置、索引映射等,常用命令如下:

json 复制代码
# 1. 查看集群中所有索引的列表(含健康状态、文档数、存储大小等)
GET /_cat/indices?v

# 2. 查看指定索引的完整详情(settings + mapping)
GET /user_index

# 3. 查看指定索引的settings配置
GET /user_index/_settings

# 4. 查看指定索引的mapping映射
GET /user_index/_mapping
4.1.3 删除索引

删除索引会同时删除该索引下的所有文档数据、配置、映射,操作不可逆,需谨慎执行:

json 复制代码
# 删除指定索引
DELETE /user_index

4.2 文档操作:增、删、改、查

文档是 ES 的最小数据单元,核心操作包括创建、查询、更新、删除四类,对应业务中的数据 CRUD。

4.2.1 创建文档

ES 支持两种创建文档的方式:手动指定文档 ID、ES 自动生成文档 ID。

方式 1:手动指定文档 ID(PUT 方法)
json 复制代码
# 创建文档,指定ID为1
PUT /user_index/_doc/1
{
  "name": "张三",
  "age": 25,
  "email": "zhangsan@example.com",
  "tags": ["Java", "Elasticsearch"]
}
方式 2:自动生成文档 ID(POST 方法)

无需手动指定 ID,ES 会自动生成唯一的随机字符串作为文档 ID,适合无唯一标识的业务数据:

json 复制代码
# 创建文档,自动生成ID
POST /user_index/_doc
{
  "name": "李四",
  "age": 30,
  "email": "lisi@example.com"
}

创建成功返回结果,其中_id即为自动生成的文档 ID:

json 复制代码
{
  "_index": "user_index",
  "_id": "xYz123AbCxxxxxx",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}
4.2.2 查询文档

最基础的查询方式是根据文档 ID 精确查询,语法如下:

json 复制代码
# 根据文档ID查询文档详情
GET /user_index/_doc/1

查询成功返回结果,其中_source即为文档的原始 JSON 数据:

json 复制代码
{
  "_index": "user_index",
  "_id": "1",
  "_version": 1,
  "_seq_no": 0,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "name": "张三",
    "age": 25,
    "email": "zhangsan@example.com",
    "tags": ["Java", "Elasticsearch"]
  }
}
4.2.3 更新文档

ES 支持两种更新方式:全量更新(覆盖整个文档)、部分更新(仅修改指定字段),两者有本质区别,需根据业务场景选择。

方式 1:全量更新(PUT 方法)

与手动指定 ID 创建文档的语法完全一致,若文档 ID 已存在,则执行全量覆盖,文档版本号_version会自增;若 ID 不存在,则执行创建操作。

json 复制代码
# 全量更新ID为1的文档,覆盖整个文档内容
PUT /user_index/_doc/1
{
  "name": "张三",
  "age": 26,
  "email": "zhangsan_new@example.com"
}

注意:全量更新会覆盖整个文档,未在请求体中指定的字段会被删除,比如上述示例中的tags字段会被移除,需谨慎使用。

方式 2:部分更新(POST 方法)

仅修改请求体中指定的字段,其他字段保持不变,不会删除未指定的字段,是业务中最常用的更新方式,语法如下:

json 复制代码
# 部分更新ID为1的文档,仅修改age字段
POST /user_index/_update/1
{
  "doc": {
    "age": 26
  }
}
4.2.4 删除文档

根据文档 ID 删除指定文档,操作不可逆,语法如下:

json 复制代码
# 删除ID为1的文档
DELETE /user_index/_doc/1

4.3 批量操作:提升写入 / 查询效率

当需要处理大量文档时,单条操作会产生大量的网络请求,性能较差,ES 提供了批量操作 API,可在一次请求中完成多条数据的写入、更新、删除、查询,大幅提升操作效率。

4.3.1 批量写入 / 更新 / 删除:Bulk API

Bulk API 支持在一次请求中,混合执行 index(创建 / 覆盖)、update(更新)、delete(删除)等多种操作,语法有严格的格式要求,核心规则如下:

  • 每一个操作分为两行:第一行是操作元数据(操作类型、所属索引、文档 ID 等),第二行是操作对应的文档数据(delete 操作无需第二行)
  • 每行数据必须单独占一行,不能换行,否则会报 JSON 解析错误

批量写入示例:

json 复制代码
# 批量写入3条用户数据
POST /_bulk
{"index":{"_index":"user_index","_id":"1"}}
{"name":"张三","age":25,"email":"zhangsan@example.com","tags":["Java","Elasticsearch"]}
{"index":{"_index":"user_index","_id":"2"}}
{"name":"李四","age":30,"email":"lisi@example.com","tags":["Python","大数据"]}
{"index":{"_index":"user_index","_id":"3"}}
{"name":"王五","age":28,"email":"wangwu@example.com","tags":["Java","SpringBoot"]}

性能优化提示:Bulk 批量操作的请求体大小建议控制在 10-15MB 之间,不要过大,否则会导致请求超时、节点内存压力过大,反而影响写入性能。

4.3.2 批量查询:Multi Get API

通过多个文档 ID,在一次请求中批量查询多条文档数据,避免多次单条查询的网络开销,语法如下:

json 复制代码
# 批量查询ID为1、2、3的3条文档
GET /user_index/_mget
{
  "ids": ["1", "2", "3"]
}

五、DSL 搜索语法入门:解锁 ES 核心检索能力

ES 的核心价值在于强大的全文检索能力,而 DSL(Domain Specific Language)是 ES 提供的 JSON 格式的查询语言,支持复杂的检索、过滤、排序、聚合操作,是 ES 开发的核心技能。

5.1 基础搜索:单条件查询

基础单条件查询是 DSL 的入门核心,覆盖了业务中 80% 的基础检索场景。

5.1.1 查询所有文档:match_all

查询指定索引下的所有文档,默认返回前 10 条,是最基础的查询语句:

json 复制代码
# 查询user_index下的所有文档
GET /user_index/_search
{
  "query": {
    "match_all": {}
  }
}
5.1.2 全文匹配查询:match

针对text类型的字段,会先对查询关键词进行分词,再与字段中的词条进行匹配,支持全文检索、模糊匹配,是 ES 全文检索的核心语法。

json 复制代码
# 全文搜索name字段中包含"张三"的文档
GET /user_index/_search
{
  "query": {
    "match": {
      "name": "张三"
    }
  }
}

说明:比如查询关键词 "张三" 会被分词为 "张"、"三",只要 name 字段中包含其中任意一个词条,都会被匹配到,实现模糊搜索的效果。

5.1.3 精确匹配查询:term

针对keyword、数值、日期等类型的字段,不会对查询关键词进行分词,直接进行完整精确匹配,适合状态、ID、标签等字段的精准过滤。

json 复制代码
# 精确匹配age字段等于25的文档
GET /user_index/_search
{
  "query": {
    "term": {
      "age": 25
    }
  }
}

新手踩坑提示:term 查询不会分词,若用于text类型的字段,会导致无法匹配到结果,比如 name 字段是text类型,用 term 查询 "张三",只会匹配字段值完全等于 "张三" 且未被分词的文档,大概率查询不到结果。

5.1.4 范围查询:range

针对数值、日期类型的字段,实现范围过滤,支持大于、小于、大于等于、小于等于四个操作符,常用参数如下:

  • gte:大于等于(greater than or equal)
  • lte:小于等于(less than or equal)
  • gt:大于(greater than)
  • lt:小于(less than)

示例:

json 复制代码
# 查询age在25-30岁之间的文档(包含25和30)
GET /user_index/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 25,
        "lte": 30
      }
    }
  }
}

5.2 组合查询:Bool 布尔查询(多条件复合检索)

实际业务中,绝大多数检索场景都需要多个条件组合,Bool 查询是 ES 中最常用的复合查询语法,支持通过四个子句组合多个查询条件,实现复杂的检索逻辑,四个子句如下:

子句 逻辑含义 分数计算 核心适用场景
must 必须满足,相当于 AND 参与相关性分数计算,匹配度越高分数越高 必须匹配的核心检索条件,需要根据匹配度排序
should 应该满足,相当于 OR 参与相关性分数计算,匹配的子句越多分数越高 可选匹配条件,提升匹配度高的文档排名
must_not 必须不满足,相当于 NOT 不参与分数计算,仅过滤 排除不符合条件的文档
filter 必须满足,与 must 一致 不参与分数计算,ES 会自动缓存结果,性能远高于 must 仅需要过滤、不需要根据匹配度排序的场景

Bool 查询完整示例:

json 复制代码
# 复合查询:name字段包含"张",age在20岁以上,不包含Python标签,优先匹配age=25的文档
GET /user_index/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"name": "张"}}
      ],
      "should": [
        {"term": {"age": 25}}
      ],
      "must_not": [
        {"term": {"tags": "Python"}}
      ],
      "filter": [
        {"range": {"age": {"gte": 20}}}
      ]
    }
  }
}

最佳实践:对于不需要参与相关性排序的过滤条件,统一放在 filter 子句中,利用 ES 的缓存机制大幅提升查询性能,避免全部放在 must 子句中导致的性能损耗。

5.3 分页与排序

5.3.1 分页查询

ES 通过fromsize两个参数实现分页,类似 MySQL 的limit offset, size

  • from:起始偏移量,从 0 开始,默认值为 0
  • size:每页返回的文档数量,默认值为 10
json 复制代码
# 分页查询,第1页,每页10条数据
GET /user_index/_search
{
  "query": {"match_all": {}},
  "from": 0,
  "size": 10
}

注意:from + size的最大值默认限制为 10000,超过该值会报错,不适合深度分页场景,深度分页可使用 scroll 或 search_after 方案,后续系列文章会详细讲解。

5.3.2 排序

ES 支持对多个字段进行排序,支持正序(asc)、倒序(desc),默认按照相关性分数_score倒序排列,匹配度越高排名越靠前。

json 复制代码
# 按age倒序排列,age相同的按相关性分数倒序排列
GET /user_index/_search
{
  "query": {"match_all": {}},
  "sort": [
    {"age": {"order": "desc"}},
    {"_score": {"order": "desc"}}
  ]
}

说明:text类型的字段不支持排序,如需排序需使用keyword类型的字段。

5.4 高亮显示:优化搜索结果展示

在搜索场景中,通常需要将匹配到的关键词高亮显示,提升用户体验,ES 通过highlight参数实现高亮功能,自动给匹配到的关键词加上高亮标签,默认使用<em>标签包裹。

json 复制代码
# 搜索name字段包含"张三"的文档,并对name字段进行高亮显示
GET /user_index/_search
{
  "query": {
    "match": {"name": "张三"}
  },
  "highlight": {
    "fields": {
      "name": {}
    }
  }
}

返回结果中会包含highlight字段,示例如下:

json 复制代码
{
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.3862942,
    "hits": [
      {
        "_index": "user_index",
        "_id": "1",
        "_score": 1.3862942,
        "_source": {
          "name": "张三",
          "age": 25,
          "email": "zhangsan@example.com"
        },
        "highlight": {
          "name": ["<em>张三</em>"]
        }
      }
    ]
  }
}

可通过pre_tagspost_tags参数自定义高亮标签,比如前端使用 Vue,可自定义为<span class="highlight"></span>


六、Mapping 映射全解析:定义数据的存储与检索规则

Mapping 是 ES 索引设计的核心,直接决定了数据的存储方式、检索效果与查询性能,生产环境中必须提前设计好 Mapping,避免使用动态映射导致的线上问题。

6.1 什么是 Mapping

Mapping 定义了索引中字段的以下核心属性:

  1. 字段的数据类型(text、keyword、integer、date 等)
  2. 字段是否被索引(是否可被检索,默认 true)
  3. 字段是否被存储(是否单独存储,默认 false)
  4. text 类型字段的分词器规则
  5. 日期类型的格式规则
  6. 字段的其他高级属性(比如是否忽略大小写、是否支持聚合等)

ES 支持两种 Mapping 模式:

  • 动态映射:写入文档时,ES 自动根据文档内容推断字段类型,无需提前定义,适合快速测试场景,生产环境不推荐使用
  • 静态映射(显式映射) :创建索引时,手动定义每个字段的类型与属性,是生产环境的最佳实践,可完全掌控字段规则,避免类型推断错误

6.2 创建带自定义 Mapping 的索引

创建索引时,可通过mappings参数定义字段的映射规则,示例如下(商品索引):

json 复制代码
# 创建商品索引,带完整的自定义Mapping
PUT /product_index
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "price": {
        "type": "double"
      },
      "stock": {
        "type": "integer"
      },
      "status": {
        "type": "keyword"
      },
      "tags": {
        "type": "keyword"
      },
      "description": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "create_time": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
      }
    }
  }
}

关键说明:

  1. analyzer:指定 text 类型字段的分词器,ik_max_word是 IK 中文分词器的细粒度分词模式,适合中文全文检索,需提前安装与 ES 版本一致的 IK 分词器插件
  2. format:指定 date 类型字段支持的日期格式,用||分隔多个格式,写入数据时符合任意一种格式即可被正常解析,epoch_millis表示支持时间戳格式

6.3 查看与修改 Mapping

6.3.1 查看索引的 Mapping
json 复制代码
# 查看指定索引的完整Mapping定义
GET /product_index/_mapping
6.3.2 新增字段 Mapping

ES 支持为已有索引新增字段映射,语法如下:

json 复制代码
# 为product_index新增description字段
PUT /product_index/_mapping
{
  "properties": {
    "description": {
      "type": "text",
      "analyzer": "ik_max_word"
    }
  }
}
6.3.3 修改已有字段的 Mapping

重点规则:ES 不支持修改已有字段的类型或核心属性。原因:字段类型决定了数据的索引方式、分词规则、倒排索引的构建方式,一旦字段已有数据,修改类型会导致已有的倒排索引失效,无法正常检索。

如果必须修改已有字段的类型,唯一的解决方案是:

  1. 创建一个新的索引,定义正确的 Mapping
  2. 使用 reindex API 将原索引的数据迁移到新索引
  3. 业务切换到新索引,删除原索引

七、实战案例:从零搭建用户搜索系统

前面我们已经掌握了 ES 的核心概念、基础操作与 DSL 语法,接下来我们通过一个完整的用户搜索系统实战,将所有知识点串联落地,所有代码可直接复制复用。

7.1 需求梳理

我们需要实现一个用户管理系统的搜索功能,核心需求如下:

  1. 支持按用户昵称进行全文模糊搜索
  2. 支持按城市、性别、兴趣标签进行精确过滤
  3. 支持按年龄范围进行过滤
  4. 支持按创建时间倒序排序
  5. 支持分页查询

7.2 步骤 1:创建用户索引(含自定义 Mapping)

根据需求设计 Mapping,核心设计思路:

  • 用户名、邮箱、性别、城市、兴趣标签:使用 keyword 类型,支持精确匹配、过滤
  • 用户昵称:使用 text 类型,支持全文模糊搜索
  • 年龄:integer 类型,支持范围查询、排序
  • 创建时间:date 类型,支持时间范围查询、排序

创建索引的完整命令:

json 复制代码
# 创建用户搜索索引
PUT /user_index
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "username": {"type": "keyword"},
      "nickname": {"type": "text"},
      "email": {"type": "keyword"},
      "age": {"type": "integer"},
      "gender": {"type": "keyword"},
      "city": {"type": "keyword"},
      "interests": {"type": "keyword"},
      "create_time": {"type": "date"}
    }
  }
}

7.3 步骤 2:批量导入测试数据

使用 Bulk API 批量导入测试数据,方便后续检索测试:

json 复制代码
# 批量导入用户测试数据
POST /_bulk
{"index":{"_index":"user_index","_id":"1"}}
{"username":"zhangsan","nickname":"张三","email":"zhangsan@example.com","age":25,"gender":"male","city":"北京","interests":["编程","阅读"],"create_time":"2024-01-01T10:00:00"}
{"index":{"_index":"user_index","_id":"2"}}
{"username":"lisi","nickname":"李四","email":"lisi@example.com","age":30,"gender":"female","city":"上海","interests":["旅游","摄影"],"create_time":"2024-01-02T11:00:00"}
{"index":{"_index":"user_index","_id":"3"}}
{"username":"wangwu","nickname":"王五","email":"wangwu@example.com","age":28,"gender":"male","city":"北京","interests":["编程","游戏"],"create_time":"2024-01-03T12:00:00"}
{"index":{"_index":"user_index","_id":"4"}}
{"username":"zhaoliu","nickname":"赵六","email":"zhaoliu@example.com","age":26,"gender":"male","city":"深圳","interests":["健身","编程"],"create_time":"2024-01-04T13:00:00"}
{"index":{"_index":"user_index","_id":"5"}}
{"username":"qianqi","nickname":"钱七","email":"qianqi@example.com","age":27,"gender":"female","city":"北京","interests":["阅读","绘画"],"create_time":"2024-01-05T14:00:00"}

7.4 步骤 3:实现复杂条件检索

根据需求,实现完整的复合查询:查询北京地区、25-30 岁、男性、兴趣包含编程的用户,按创建时间倒序排列,分页返回第 1 页,每页 10 条数据。

完整 DSL 查询语句:

json 复制代码
# 用户搜索复合查询
GET /user_index/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {"city": "北京"}},
        {"term": {"gender": "male"}},
        {"term": {"interests": "编程"}}
      ],
      "filter": [
        {"range": {"age": {"gte": 25, "lte": 30}}}
      ]
    }
  },
  "sort": [
    {"create_time": {"order": "desc"}}
  ],
  "from": 0,
  "size": 10
}

执行该查询,即可返回符合所有条件的用户数据,完美匹配业务需求。


八、ES 常用运维命令速查

日常开发与运维中,以下高频命令可帮助你快速查看集群状态、定位索引问题,建议收藏备用:

json 复制代码
# 1. 查看集群健康状态
GET /_cat/health?v

# 2. 查看集群节点列表与信息
GET /_cat/nodes?v

# 3. 查看所有索引的列表与核心信息(健康状态、文档数、存储大小等)
GET /_cat/indices?v

# 4. 查看索引的分片分配情况
GET /_cat/shards?v

# 5. 查看指定索引的文档总数
GET /user_index/_count

# 6. 删除索引中的所有数据,保留索引结构与Mapping
POST /user_index/_delete_by_query
{
  "query": {"match_all": {}}
}

# 7. 查看集群的分片分配详情,定位分片未分配问题
GET /_cluster/allocation/explain

# 8. 查看ES集群的版本、节点等基础信息
GET /

九、总结与系列预告

本文总结

本文作为 Elasticsearch 实战系列的开篇,完整覆盖了 ES 入门的全流程核心知识点:

  1. 我们明确了 ES 的核心定位、特性与适用场景,理清了 ES 与 MySQL 的差异与互补关系
  2. 通过与 MySQL 类比,快速掌握了索引、文档、映射、分片等 ES 核心概念,避开了新手高频踩坑点
  3. 完成了 ES+Kibana 的环境搭建,提供了 Docker 一键部署与本地安装两种方案
  4. 掌握了索引、文档的全量 CRUD 操作,以及批量操作的高效用法
  5. 打通了 DSL 搜索语法的核心知识点,从单条件查询到复合布尔查询,覆盖分页、排序、高亮全场景
  6. 学会了 Mapping 映射的设计规则与最佳实践,明确了生产环境的索引设计规范
  7. 通过用户搜索系统的完整实战,将所有知识点落地,实现了可直接复用的业务检索能力

系列预告

在下一篇文章中,我们将进入 Java 开发实战环节,详细讲解如何在 Spring Boot 项目中集成 Elasticsearch,使用 RestHighLevelClient 实现本文中的所有索引操作、文档 CRUD、DSL 复杂检索,将 ES 能力落地到实际的 Java 业务项目中,同时提供完整的项目代码与最佳实践,欢迎关注系列更新。


相关推荐
Elasticsearch2 小时前
AI agent 记忆:使用 Elasticsearch 托管记忆创建智能代理
elasticsearch
三水不滴2 小时前
Elasticsearch 生产环境全栈最佳实践:从架构设计到故障排查一站式落地指南
后端·elasticsearch·搜索引擎
星纬智联技术2 小时前
从 OpenAI Harness Engineering 蒸馏出四个 Skill,Agent 跑了 25 小时
后端
神奇小汤圆2 小时前
横空出世!IDEA最强Spring插件来了,效率翻倍!
后端
aisifang002 小时前
SpringBoot Maven 项目 pom 中的 plugin 插件用法整理
spring boot·后端·maven
我还不赖2 小时前
skill-creator Skill 深度分析
后端
yashuk2 小时前
SpringBoot中自定义Starter
java·spring boot·后端
User_芊芊君子2 小时前
风险可视可防,金仓数据库 SQL 防火墙筑牢企业数据安全防线
后端
别看我只是一直狼2 小时前
Spring Boot + Redis 实战:8 个企业项目最常用代码模板
后端