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_index、product-2026、log_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 | 嵌套对象数组类型,专门用于处理对象数组,避免数组扁平化导致的查询错误 | 订单明细、商品的多规格列表等一对多的嵌套数组场景 |
新手高频踩坑提示:
text和keyword是最容易用错的类型。需要模糊搜索的字段用text,需要精确匹配、排序、聚合的字段用keyword,如果把需要精确匹配的字段设为text,会导致 term 查询无法匹配到结果。
2.2.4 分片与副本(Shard & Replica)
ES 的分布式能力,核心是通过分片与副本实现的,两者的定义与作用如下:
- 主分片(Primary Shard) :一个索引的数据会被拆分为多个主分片,分散存储在集群的不同节点上,实现数据的水平拆分。主分片的数量必须在创建索引时指定,创建后不可修改,如需调整只能重建索引。
- 副本分片(Replica Shard) :主分片的备份副本,与主分片存储完全一致的数据,用于实现故障转移(主分片所在节点宕机时,副本分片可升级为主分片,保证数据不丢失、服务不中断),同时可分担查询请求,提升检索性能。副本分片的数量可动态调整,无需重启服务。
分片与副本的核心优势:
- 水平扩展:数据分散到多个节点,突破单节点的存储、性能瓶颈,可通过新增节点无缝扩容
- 高可用:副本分片提供故障转移能力,单个 / 多个节点宕机不影响集群正常服务
- 性能提升:查询请求可并行在多个分片上执行,大幅提升海量数据的检索效率
踩坑提示:单节点测试环境下,建议将副本数设置为 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 堆内存,初始值与最大值建议保持一致,避免堆内存动态调整的性能损耗,本地测试建议不小于 512m9200端口: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 通过from和size两个参数实现分页,类似 MySQL 的limit offset, size:
from:起始偏移量,从 0 开始,默认值为 0size:每页返回的文档数量,默认值为 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_tags和post_tags参数自定义高亮标签,比如前端使用 Vue,可自定义为<span class="highlight">和</span>。
六、Mapping 映射全解析:定义数据的存储与检索规则
Mapping 是 ES 索引设计的核心,直接决定了数据的存储方式、检索效果与查询性能,生产环境中必须提前设计好 Mapping,避免使用动态映射导致的线上问题。
6.1 什么是 Mapping
Mapping 定义了索引中字段的以下核心属性:
- 字段的数据类型(text、keyword、integer、date 等)
- 字段是否被索引(是否可被检索,默认 true)
- 字段是否被存储(是否单独存储,默认 false)
- text 类型字段的分词器规则
- 日期类型的格式规则
- 字段的其他高级属性(比如是否忽略大小写、是否支持聚合等)
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"
}
}
}
}
关键说明:
analyzer:指定 text 类型字段的分词器,ik_max_word是 IK 中文分词器的细粒度分词模式,适合中文全文检索,需提前安装与 ES 版本一致的 IK 分词器插件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 不支持修改已有字段的类型或核心属性。原因:字段类型决定了数据的索引方式、分词规则、倒排索引的构建方式,一旦字段已有数据,修改类型会导致已有的倒排索引失效,无法正常检索。
如果必须修改已有字段的类型,唯一的解决方案是:
- 创建一个新的索引,定义正确的 Mapping
- 使用 reindex API 将原索引的数据迁移到新索引
- 业务切换到新索引,删除原索引
七、实战案例:从零搭建用户搜索系统
前面我们已经掌握了 ES 的核心概念、基础操作与 DSL 语法,接下来我们通过一个完整的用户搜索系统实战,将所有知识点串联落地,所有代码可直接复制复用。
7.1 需求梳理
我们需要实现一个用户管理系统的搜索功能,核心需求如下:
- 支持按用户昵称进行全文模糊搜索
- 支持按城市、性别、兴趣标签进行精确过滤
- 支持按年龄范围进行过滤
- 支持按创建时间倒序排序
- 支持分页查询
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 入门的全流程核心知识点:
- 我们明确了 ES 的核心定位、特性与适用场景,理清了 ES 与 MySQL 的差异与互补关系
- 通过与 MySQL 类比,快速掌握了索引、文档、映射、分片等 ES 核心概念,避开了新手高频踩坑点
- 完成了 ES+Kibana 的环境搭建,提供了 Docker 一键部署与本地安装两种方案
- 掌握了索引、文档的全量 CRUD 操作,以及批量操作的高效用法
- 打通了 DSL 搜索语法的核心知识点,从单条件查询到复合布尔查询,覆盖分页、排序、高亮全场景
- 学会了 Mapping 映射的设计规则与最佳实践,明确了生产环境的索引设计规范
- 通过用户搜索系统的完整实战,将所有知识点落地,实现了可直接复用的业务检索能力
系列预告
在下一篇文章中,我们将进入 Java 开发实战环节,详细讲解如何在 Spring Boot 项目中集成 Elasticsearch,使用 RestHighLevelClient 实现本文中的所有索引操作、文档 CRUD、DSL 复杂检索,将 ES 能力落地到实际的 Java 业务项目中,同时提供完整的项目代码与最佳实践,欢迎关注系列更新。