Elasticsearch(简称 ES)是基于 Apache Lucene 构建的分布式全文搜索与分析引擎,由 Elastic 公司于 2010 年发布。
以 近实时(NRT)搜索、水平扩展、RESTful API 为核心特性,广泛用于日志分析(ELK Stack)、全文检索、商品搜索等场景。
⚠️ 版本说明:本文以 Elasticsearch 8.x 为基准。7.x 与 8.x 在安全配置、API 上有差异,差异处会注明。
目录
| 章节 |
说明 |
| 核心概念 |
索引、文档、分片、倒排索引、搜索术语、Elastic Stack 生态 |
| 安装与启动 |
本地安装、Docker 启动、Kibana |
| 索引管理 |
创建索引、Mapping、Settings、别名 |
| 字段类型底层数据结构 |
各字段类型对应的 Lucene 存储实现 |
| 文档操作 |
增删改查(CRUD)、搜索响应字段解析 |
| 搜索查询 |
Query DSL、全文搜索、精确查询、复合查询 |
| 聚合分析 |
Metric、Bucket、Pipeline 聚合 |
| 分词器 |
内置分词器、中文分词(IK)、自定义分词器 |
| 集群与分片 |
节点角色、分片策略、副本、集群健康 |
| 快速参考卡 |
Query 类型速查、常用 API 速查 |
核心概念
与关系型数据库对比
ES 7.x 废弃、8.x 彻底移除了 Type 概念,下表以 8.x 现代对应关系为准。
| 关系型数据库 |
Elasticsearch 8.x |
说明 |
| 数据库实例 |
ES 集群 |
多个 Index 的集合,无完全对等概念 |
| Table |
Index(索引) |
Type 废弃后,Index 成为文档的直接容器,一个业务对象建一个 Index |
| Row |
Document(文档) |
一条数据,JSON 格式 |
| Column |
Field(字段) |
文档中的一个属性 |
| Schema |
Mapping |
字段类型定义 |
| 索引(B+ 树) |
Inverted Index(倒排索引) |
两者名字相同但概念不同,ES 的 Index 指倒排索引结构 |
| SQL |
Query DSL |
查询语言 |
Type 的历史:旧版 ES 中一个 Index 可含多个 Type(类似一个库多张表),7.x 废弃、8.x 移除。移除后 Index 直接承担 Table 的角色,现代实践中"一个业务对象 = 一个 Index"。
搜索专业术语
| 术语 |
说明 |
对应数据库概念 |
| 字段(Field) |
文档的组成单元,包含字段名称、属性和内容 |
列 |
| 字段属性(Attributes) |
描述字段的元数据,包括类型、是否建索引、是否分词等 |
VARCHAR(16) / index / primary_key 等 |
| 文档(Document) |
可搜索的结构化数据单元,JSON 格式,由多个字段组成 |
行记录 |
| 索引(Index) |
多个文档的集合 |
表 |
| 正排 |
文档 → 字段值的映射链表(doc1 → id, type, time…)。分两层:① _source(原始 JSON,默认存储,用于返回结果)② doc_values(列式存储,用于排序/聚合)。对所有支持的字段类型默认开启,仅 text/annotated_text 不支持(需用 keyword 子字段替代) |
行记录 |
| 倒排 |
词 → 文档列表的映射链表(term1 → doc1, doc2, doc3),字段设置 index=true 时构建。 ⚠️ 不设置 index=true 时该字段无法搜索(MySQL 不设索引仍可查,ES 不设则返回空) |
B+ 树索引 |
| 召回 |
对查询词分词后,通过倒排索引定位到文档的过程 |
查询过程 |
| 召回量 |
召回得到的文档数,即 hits.total.value |
查询返回结果数 |
| 分片(Shard) |
索引的子集,每个分片具备完整的索引结构 |
无对应概念 |
| 段(Segment) |
分片的组成单元,检索的基本单元,所有查询/更新基于段执行 |
— |
| 段合并(Merge) |
Lucene 删除是标记删除,更新是先删后增,段积累后需合并以清除无效数据、提升查询性能 |
— |
倒排索引(核心原理)
正排索引(传统):文档 → 词
倒排索引(ES) :词 → 文档列表
示例:
文档1:"苹果手机价格"
文档2:"苹果电脑优惠"
文档3:"手机价格对比"
倒排索引:
苹果 → [文档1, 文档2]
手机 → [文档1, 文档3]
价格 → [文档1, 文档3]
电脑 → [文档2]
搜索"苹果手机"时,先分词得到 ["苹果", "手机"],再查倒排索引:
- 默认(OR):取并集 → 文档1、文档2、文档3 均返回(
match 默认行为)
- AND 语义:取交集 → 只返回文档1(需指定
"operator": "and")
无论 OR 还是 AND,都只需查词表后做集合运算,速度极快,无需全文扫描。
Elastic Stack 生态
ES 的能力通过 Elastic Stack(ELK Stack)发挥最大价值,四大组件各司其职:
graph LR
A["数据源<br/>(日志/指标/事件)"] --> B["Beats<br/>轻量采集器"]
A --> C["Logstash<br/>数据处理管道"]
B --> C
B --> D["Elasticsearch<br/>存储 & 搜索 & 分析"]
C --> D
D --> E["Kibana<br/>可视化 & 管理"]
| 组件 |
定位 |
说明 |
| Elasticsearch |
核心引擎 |
分布式搜索与分析,所有数据的存储和检索中心 |
| Kibana |
可视化层 |
柱状图、折线图、饼图等可视化,实时呈现 ES 聚合数据 |
| Logstash |
数据处理管道 |
从多来源采集数据,支持丰富过滤器将非结构化数据转为结构化数据后写入 ES |
| Beats |
轻量采集器 |
轻量级单一用途采集器,直接将数据发送给 ES 或 Logstash |
Beats 采集器列表:
| Beats |
采集内容 |
| Filebeat |
日志文件 |
| Metricbeat |
系统/服务指标 |
| Packetbeat |
网络数据包 |
| Winlogbeat |
Windows 事件日志 |
| Auditbeat |
审计数据 |
| Heartbeat |
服务运行时间监控 |
| Functionbeat |
无服务器(Serverless)函数采集 |
Beats vs Logstash:Beats 轻量简单,适合大量节点部署;Logstash 功能更强,支持复杂的数据转换和过滤,两者常配合使用。
分片(Shard)
| 概念 |
说明 |
| 主分片(Primary Shard) |
数据的实际存储单元,创建索引时指定,不可更改 |
| 副本分片(Replica Shard) |
主分片的拷贝,提供高可用和读扩展,可动态调整 |
| 分片数建议 |
单分片不超过 50GB,节点数 × 1~3 倍为宜 |
安装与启动
Docker 快速启动(推荐开发环境)
# 启动单节点 ES(8.x 默认开启安全认证)
docker run -d \
--name elasticsearch \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "xpack.security.enabled=false" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
elasticsearch:8.13.0
# 验证
curl http://localhost:9200
# 返回集群信息 JSON 即成功
# 同时启动 Kibana(可视化管理界面)
docker run -d \
--name kibana \
-p 5601:5601 \
-e "ELASTICSEARCH_HOSTS=http://elasticsearch:9200" \
--link elasticsearch \
kibana:8.13.0
# 访问 http://localhost:5601
macOS 本地安装
brew tap elastic/tap
brew install elastic/tap/elasticsearch-full
# 启动
brew services start elastic/tap/elasticsearch-full
# 验证
curl http://localhost:9200
索引管理
创建索引(含 Mapping 和 Settings)
# 创建商品索引
curl -X PUT "localhost:9200/products" -H 'Content-Type: application/json' -d '
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"my_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"id": { "type": "long" },
"name": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" },
"description": { "type": "text", "analyzer": "ik_max_word" },
"price": { "type": "scaled_float", "scaling_factor": 100 },
"category": { "type": "keyword" },
"tags": { "type": "keyword" },
"status": { "type": "byte" },
"stock": { "type": "integer" },
"created_at": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||epoch_millis" },
"location": { "type": "geo_point" }
}
}
}'
常用字段类型
| 类型 |
说明 |
适用场景 |
text |
分词后索引,支持全文搜索 |
标题、描述、正文 |
keyword |
不分词,精确匹配、排序、聚合 |
状态、分类、标签、ID |
long / integer |
整数 |
数量、ID |
scaled_float |
缩放浮点(推荐替代 float) |
价格、评分 |
date |
日期时间 |
时间戳、日期 |
boolean |
布尔值 |
开关状态 |
geo_point |
地理坐标 |
LBS 位置 |
nested |
嵌套对象(保持对象间关系) |
订单明细、评论列表 |
object |
普通对象(扁平化存储) |
简单嵌套结构 |
字段类型底层数据结构
ES 写入一个文档时,会根据字段类型同时构建多套数据结构,各司其职:
| 数据结构 |
作用 |
存储位置 |
| 倒排索引 |
词项 → 文档列表,用于搜索(match、term) |
Lucene segment 文件 |
| BKD 树 |
数值/日期/地理范围查询(range、geo_distance) |
Lucene segment 文件 |
| Doc Values |
列存,用于排序(sort)、聚合(aggs)、脚本 |
磁盘列文件,OS 缓存 |
_source |
原始 JSON,用于返回文档内容 |
独立压缩存储 |
字段类型 → 底层结构对照
| 字段类型 |
倒排索引 |
BKD 树 |
Doc Values |
说明 |
text |
✅(分词后) |
❌ |
❌ |
只支持全文搜索,不能排序/聚合(需加 keyword 子字段) |
keyword |
✅(整体作 term) |
❌ |
✅ |
精确匹配 + 排序 + 聚合 |
integer / long |
❌ |
✅ |
✅ |
范围查询走 BKD 树,排序/聚合走 Doc Values |
float / double |
❌ |
✅ |
✅ |
同上 |
date |
❌ |
✅(date 转 long) |
✅ |
日期范围查询走 BKD 树 |
boolean |
✅ |
❌ |
✅ |
true/false 精确匹配 |
geo_point |
❌ |
✅(二维 BKD) |
✅ |
地理范围/距离查询 |
nested |
✅ |
视子字段 |
视子字段 |
每个 nested 对象独立建索引 |
一个字段同时拥有多套结构
字段 price: 8999.00(scaled_float)
写入时同时构建:
├─ BKD 树 → 支持 price >= 1000 AND price <= 10000 的范围查询
├─ Doc Values → 支持 ORDER BY price、AVG(price) 聚合
└─ _source → 支持返回原始值
字段 category: "手机"(keyword)
写入时同时构建:
├─ 倒排索引 → 支持 term: { category: "手机" } 精确匹配
├─ Doc Values → 支持 terms 聚合(按分类统计数量)
└─ _source → 支持返回原始值
text 字段为什么不能直接排序/聚合
字段 name: "苹果手机"(text,分词后)
倒排索引中存的是:
苹果 → [doc1, doc2]
手机 → [doc1, doc3]
没有 Doc Values(分词后无法列存),无法知道 doc1 的 name 原始值是什么
→ 排序/聚合需要完整字段值,text 做不到
解决方案:定义 fields 子字段,同一数据建两套索引:
"name": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": { "type": "keyword" }
}
}
name:分词,支持全文搜索
name.keyword:不分词,支持排序和聚合
BKD 树 vs 倒排索引的选择
ES 5.0 之前数值字段也用倒排索引(把数字转为特殊 term),范围查询需扫描大量 term。5.0 后换成 BKD 树:
| 场景 |
倒排索引 |
BKD 树 |
term: { status: 1 } 精确匹配 |
✅ 快 |
— |
range: { price: { gte: 100 } } 范围查询 |
慢(扫描大量 term) |
✅ 快(O(log N)) |
| 地理距离查询 |
无法支持 |
✅ 支持(多维 BKD) |
索引管理操作
# 查看索引信息
curl "localhost:9200/products"
# 查看 Mapping
curl "localhost:9200/products/_mapping"
# 查看 Settings
curl "localhost:9200/products/_settings"
# 新增字段(Mapping 只能新增,不能修改已有字段类型)
curl -X PUT "localhost:9200/products/_mapping" -H 'Content-Type: application/json' -d '
{
"properties": {
"brand": { "type": "keyword" }
}
}'
# 删除索引
curl -X DELETE "localhost:9200/products"
# 索引别名(零停机切换索引的关键)
curl -X POST "localhost:9200/_aliases" -H 'Content-Type: application/json' -d '
{
"actions": [
{ "add": { "index": "products_v2", "alias": "products" } },
{ "remove": { "index": "products_v1", "alias": "products" } }
]
}'
文档操作

增删改查
# 新增文档(指定 ID)
curl -X PUT "localhost:9200/products/_doc/1" -H 'Content-Type: application/json' -d '
{
"id": 1,
"name": "iPhone 15 Pro",
"description": "苹果旗舰手机,A17 Pro 芯片",
"price": 8999.00,
"category": "手机",
"tags": ["苹果", "旗舰", "5G"],
"status": 1,
"stock": 100,
"created_at": "2024-01-01 00:00:00"
}'
# 新增文档(自动生成 ID)
curl -X POST "localhost:9200/products/_doc" -H 'Content-Type: application/json' -d '
{ "name": "MacBook Pro", "price": 14999.00, "category": "电脑" }'
# 查询文档
curl "localhost:9200/products/_doc/1"
# 全量更新(替换整个文档)
curl -X PUT "localhost:9200/products/_doc/1" -H 'Content-Type: application/json' -d '
{ "name": "iPhone 15 Pro Max", "price": 9999.00, "category": "手机" }'
# 部分更新(只更新指定字段)
curl -X POST "localhost:9200/products/_update/1" -H 'Content-Type: application/json' -d '
{
"doc": {
"price": 8499.00,
"stock": 80
}
}'
# 脚本更新(动态修改)
curl -X POST "localhost:9200/products/_update/1" -H 'Content-Type: application/json' -d '
{
"script": {
"source": "ctx._source.stock -= params.count",
"params": { "count": 10 }
}
}'
# 删除文档
curl -X DELETE "localhost:9200/products/_doc/1"
# 批量操作(bulk API,生产环境推荐,减少网络开销)
curl -X POST "localhost:9200/_bulk" -H 'Content-Type: application/json' -d '
{ "index": { "_index": "products", "_id": "2" } }
{ "name": "iPad Pro", "price": 6999.00, "category": "平板" }
{ "index": { "_index": "products", "_id": "3" } }
{ "name": "AirPods Pro", "price": 1899.00, "category": "耳机" }
{ "delete": { "_index": "products", "_id": "99" } }
'
搜索响应字段解析
ES _search 返回的响应体各字段含义:
| 字段 |
含义 |
备注 |
took |
查询耗时(ms) |
第一次查询较慢,数据需从磁盘 load |
timed_out |
是否超时(boolean) |
未指定 timeout 参数时默认 false |
_shards.total |
应遍历的分片数 |
— |
_shards.successful |
成功返回数据的分片数 |
— |
_shards.failed |
查询失败的分片数 |
— |
_shards.skipped |
跳过的分片数 |
超时时可能跳过部分分片 |
hits.total.value |
命中文档数 |
ES 5.x 后不保证精确,仅为估算 |
hits.total.relation |
eq:value 即准确总数;gte:实际总数 ≥ value |
大数据量时为 gte |
hits.max_score |
最高相关性得分 |
null=不追踪;0=不计算相关分 |
hits.hits[]._score |
该文档与 query 的相关性得分 |
分数越高相关性越强 |
hits.hits[]._source |
文档原始内容 |
可通过 _source 字段指定返回哪些字段 |
搜索查询
全文搜索(match)
# match:对搜索词分词后查询(最常用)
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"match": {
"name": "苹果手机"
}
}
}'
# match_phrase:短语匹配,词序一致且相邻
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"match_phrase": {
"description": "旗舰手机"
}
}
}'
# multi_match:多字段搜索
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"multi_match": {
"query": "苹果手机",
"fields": ["name^3", "description"],
"type": "best_fields"
}
}
}'
精确查询
# term:精确匹配 keyword 字段(不分词)
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"term": { "category": "手机" }
}
}'
# terms:多值精确匹配(类似 SQL IN)
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"terms": { "category": ["手机", "平板"] }
}
}'
# range:范围查询
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"range": {
"price": { "gte": 1000, "lte": 5000 }
}
}
}'
# exists:字段存在查询
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"exists": { "field": "brand" }
}
}'
复合查询(bool)
# bool 查询:must(AND)、should(OR)、must_not(NOT)、filter(不计分)
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '
{
"query": {
"bool": {
"must": [
{ "match": { "name": "苹果" } }
],
"filter": [
{ "term": { "status": 1 } },
{ "range": { "price": { "lte": 10000 } } }
],
"must_not": [
{ "term": { "category": "配件" } }
],
"should": [
{ "term": { "tags": "旗舰" } }
],
"minimum_should_match": 0
}
},
"sort": [
{ "price": { "order": "asc" } },
"_score"
],
"from": 0,
"size": 10,
"_source": ["id", "name", "price", "category"]
}'
must vs filter:must 参与相关性评分(_score),filter 不计分但会被缓存,纯过滤条件优先用 filter。
聚合分析
# 组合查询 + 聚合:各分类商品数量及平均价格
curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d '
{
"size": 0,
"query": {
"term": { "status": 1 }
},
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 10,
"order": { "_count": "desc" }
},
"aggs": {
"avg_price": {
"avg": { "field": "price" }
},
"max_price": {
"max": { "field": "price" }
},
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 1000 },
{ "from": 1000, "to": 5000 },
{ "from": 5000 }
]
}
}
}
},
"total_value": {
"sum": { "field": "price" }
}
}
}'
聚合类型速查
| 类型 |
说明 |
示例 |
terms |
按字段值分组(类似 GROUP BY) |
按分类统计数量 |
date_histogram |
按时间区间分组 |
按天/月统计订单 |
range |
按数值区间分组 |
价格区间分布 |
avg / sum / min / max |
数值统计 |
平均价格 |
cardinality |
去重计数(近似) |
独立用户数 |
top_hits |
每组取 top N 文档 |
每分类取最贵商品 |
nested |
嵌套对象聚合 |
订单明细统计 |
分词器
内置分词器
| 分词器 |
说明 |
示例 |
standard |
默认,按 Unicode 分词,小写化 |
"Hello World" → [hello, world] |
whitespace |
按空白分词,不小写化 |
"Hello World" → [Hello, World] |
keyword |
不分词,整体作为一个 token |
"iPhone 15" → [iPhone 15] |
simple |
按非字母分词,小写化 |
"hello-world" → [hello, world] |
english |
英文词干提取(stemming) |
"running" → [run] |
中文分词(IK 插件)
# 安装 IK 分词器(版本需与 ES 一致)
# Docker 方式:--batch 自动跳过权限确认交互,避免 TTY 报错
docker exec elasticsearch \
bin/elasticsearch-plugin install --batch \
https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.13.0.zip
# 安装后重启容器生效
# docker restart elasticsearch
# ik_max_word:最细粒度分词(索引时用)
# "苹果手机价格" → [苹果手机, 苹果, 手机, 价格]
# ik_smart:智能分词(搜索时用,减少无意义匹配)
# "苹果手机价格" → [苹果, 手机, 价格]
# 测试分词效果
curl -X POST "localhost:9200/_analyze" -H 'Content-Type: application/json' -d '
{
"analyzer": "ik_max_word",
"text": "苹果手机价格对比"
}'
集群与分片

集群健康状态
# 查看集群健康
curl "localhost:9200/_cluster/health?pretty"
# 查看节点信息
curl "localhost:9200/_cat/nodes?v"
# 查看索引分片分布
curl "localhost:9200/_cat/shards?v"
# 查看索引列表(含文档数、存储大小)
curl "localhost:9200/_cat/indices?v&s=index"
| 颜色 |
含义 |
| 🟢 Green |
所有主分片和副本分片均正常 |
| 🟡 Yellow |
所有主分片正常,部分副本分片未分配(单节点时常见) |
| 🔴 Red |
部分主分片未分配,数据不完整,影响读写 |
分片策略建议
| 场景 |
建议 |
| 单索引数据量 < 10GB |
1 主分片,1 副本 |
| 单索引数据量 10~100GB |
3~5 主分片,1 副本 |
| 日志类(按天/月建索引) |
每个索引 1~2 主分片,使用 ILM 自动管理 |
| 分片大小 |
建议 20~50GB,不超过 50GB |
快速参考卡
Query 类型速查
| Query |
类型 |
说明 |
match |
全文 |
分词后查询,最常用 |
match_phrase |
全文 |
短语匹配,词序一致 |
multi_match |
全文 |
多字段全文搜索 |
term |
精确 |
keyword 字段精确匹配 |
terms |
精确 |
多值匹配(IN) |
range |
精确 |
范围查询(gt/gte/lt/lte) |
exists |
精确 |
字段是否存在 |
wildcard |
精确 |
通配符(*/?),性能差慎用 |
bool |
复合 |
must/should/must_not/filter 组合 |
ids |
精确 |
按文档 ID 查询 |
常用 API 速查
| 操作 |
方法 |
URL |
| 集群健康 |
GET |
/_cluster/health |
| 索引列表 |
GET |
/_cat/indices?v |
| 创建索引 |
PUT |
/{index} |
| 删除索引 |
DELETE |
/{index} |
| 查看 Mapping |
GET |
/{index}/_mapping |
| 新增文档 |
POST |
/{index}/_doc |
| 指定 ID 写入 |
PUT |
/{index}/_doc/{id} |
| 查询文档 |
GET |
/{index}/_doc/{id} |
| 部分更新 |
POST |
/{index}/_update/{id} |
| 删除文档 |
DELETE |
/{index}/_doc/{id} |
| 搜索 |
GET/POST |
/{index}/_search |
| 批量操作 |
POST |
/_bulk |
| 分析分词 |
POST |
/_analyze |
| 刷新索引 |
POST |
/{index}/_refresh |
参考资料
评论 (0)
发表评论