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 时构建。<br>⚠️ 不设置 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)