Elasticsearch学习指南

学一下ES,另外这篇博客Cursor写的

Elasticsearch学习指南

目录概览

本指南分为五个主要部分,逐步深入Elasticsearch的学习:

  1. 基础入门 - ES核心概念与基础操作
  2. 查询分析 - 高级查询与数据分析技术
  3. 系统优化 - 性能优化与集群运维
  4. 高级搜索 - 人性化搜索功能与企业级应用
  5. 核心原理 - 底层机制与性能原理深度剖析

第一部分:基础入门

什么是Elasticsearch?

Elasticsearch是一个基于Apache Lucene构建的分布式搜索和分析引擎,专门用于处理大量数据的实时搜索、分析和可视化。

核心特性

1
2
3
4
5
6
7
{
"分布式架构": "多节点集群,自动分片和复制",
"RESTful API": "HTTP请求操作,简单易用",
"JSON文档": "灵活的数据结构",
"近实时搜索": "数据写入后几乎立即可搜索",
"强大分析": "支持复杂的聚合查询"
}

应用场景

场景 具体应用 典型案例
搜索引擎 全文搜索、商品搜索 淘宝商品搜索、知乎文章搜索
日志分析 系统监控、错误分析 ELK日志分析平台
实时分析 用户行为、业务指标 网站访问统计、销售数据分析
地理搜索 位置服务、配送范围 外卖应用、地图服务

核心概念

基本术语对比

Elasticsearch 关系数据库 说明
Index Database 索引,类似数据库
Document Row 文档,JSON格式的数据记录
Field Column 字段,文档中的属性
Mapping Schema 映射,定义字段类型和属性

索引与文档的关系

1
2
3
4
5
索引(Index) = 一个业务领域的数据集合
└── 文档(Document) = 具体的数据记录
├── 字段1: 值1
├── 字段2: 值2
└── 字段3: 值3

为什么ES中的“数据库”被称为”索引”?

ES中的”索引”概念继承自搜索引擎领域,具有双重含义:

  • 数据容器:类似关系数据库的Database,存储相关数据的逻辑集合
  • 搜索索引:基于倒排索引的快速检索数据结构

这种命名体现了ES作为搜索引擎的本质特征。详细原理请参考第五部分。

Mapping映射详解

Mapping是ES的核心概念,定义了文档的结构和字段属性。

基本语法

1
2
3
4
5
6
7
8
9
10
{
"mappings": { // 固定关键字:映射定义
"properties": { // 固定关键字:字段容器
"字段名": {
"type": "数据类型",
"其他属性": "值"
}
}
}
}

常用数据类型

1
2
3
4
5
6
7
8
9
10
11
12
{
"mappings": {
"properties": {
"title": {"type": "text"}, // 全文搜索
"status": {"type": "keyword"}, // 精确匹配
"price": {"type": "float"}, // 浮点数
"count": {"type": "integer"}, // 整数
"published": {"type": "date"}, // 日期
"active": {"type": "boolean"} // 布尔值
}
}
}

text vs keyword的区别

核心差异:

  • text: 会分词,支持模糊搜索
  • keyword: 不分词,只支持精确匹配
1
2
3
4
5
6
7
{
"示例对比": {
"原文": "计算机科学",
"text处理": ["计算机", "科学"], // 分词后可搜索"计算机"
"keyword处理": ["计算机科学"] // 必须完整匹配"计算机科学"
}
}

基础操作实战

RESTful API路径规范

Elasticsearch遵循RESTful设计,所有操作都通过HTTP请求完成。理解API路径规律是掌握ES的关键。

基本路径结构

1
2
3
http://host:port/{index_name}/{api_type}/{document_id}
↑ ↑ ↑
索引名 API类型 文档ID(可选)

常用API路径一览

操作类型 HTTP方法 路径格式 说明
索引管理
创建索引 PUT /{index_name} 创建新索引
删除索引 DELETE /{index_name} 删除整个索引
查看索引 GET /{index_name} 获取索引信息
文档操作
添加文档(指定ID) PUT /{index_name}/_doc/{doc_id} 指定文档ID
添加文档(自动ID) POST /{index_name}/_doc 系统自动生成ID
更新文档(全量) PUT /{index_name}/_doc/{doc_id} 完全替换文档
更新文档(部分) POST /{index_name}/_update/{doc_id} 部分字段更新
删除文档 DELETE /{index_name}/_doc/{doc_id} 删除指定文档
查询操作
根据ID查询 GET /{index_name}/_doc/{doc_id} 精确ID查询
简单搜索 GET /{index_name}/_search 基础搜索
复杂搜索 POST /{index_name}/_search 复杂查询条件
全库搜索 GET /_search 搜索所有索引
批量操作
批量处理 POST /_bulk 批量增删改

路径参数示例

1
2
3
4
5
6
7
# 示例解析
PUT http://localhost:9200/students/_doc/1
# ↑ ↑ ↑ ↑
# 协议://主机:端口 索引名 API类型 文档ID
POST http://localhost:9200/students/_search
# ↑ ↑
# 索引名 搜索API

补充一些特殊的API路径

1
2
3
4
5
6
7
8
9
10
11
12
# 集群管理
GET /_cluster/health # 集群健康状态
GET /_cat/indices # 查看所有索引
GET /_cat/nodes # 查看所有节点

# 索引模板和别名
GET /_template # 查看索引模板
GET /_alias # 查看索引别名

# 统计信息
GET /_stats # 全局统计
GET /students/_stats # 特定索引统计

创建索引demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建学生信息索引
curl -X PUT "localhost:9200/students" \
-H "Content-Type: application/json" \
-d '{
"mappings": {
"properties": {
"name": {"type": "text"},
"age": {"type": "integer"},
"major": {"type": "keyword"},
"gpa": {"type": "float"},
"created_at": {"type": "date"}
}
}
}'

添加文档demo

1
2
3
4
5
6
7
8
9
10
# 添加学生记录
curl -X PUT "localhost:9200/students/_doc/1" \
-H "Content-Type: application/json" \
-d '{
"name": "张三",
"age": 20,
"major": "计算机科学",
"gpa": 3.8,
"created_at": "2024-01-15"
}'

基础查询demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 查询所有文档
curl -X GET "localhost:9200/students/_search"

# 2. 根据ID查询
curl -X GET "localhost:9200/students/_doc/1"

# 3. 按名字搜索
curl -X POST "localhost:9200/students/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {
"match": {"name": "张三"}
}
}'

# 4. 范围查询(GPA > 3.5)
curl -X POST "localhost:9200/students/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {
"range": {"gpa": {"gte": 3.5}}
}
}'

第二部分:查询分析

复合查询(Bool Query)

Bool查询是ES最强大的查询类型,可以组合多个查询条件。

四种查询条件

1
2
3
4
5
6
7
8
9
10
{
"query": {
"bool": {
"must": [], // 必须匹配(AND),参与评分
"filter": [], // 必须匹配(AND),不参与评分,性能更好
"should": [], // 应该匹配(OR)
"must_not": [] // 必须不匹配(NOT)
}
}
}

实战示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 复杂组合查询:年龄20-25岁,专业为"计算机科学"或"数据科学",GPA不低于3.0
curl -X POST "localhost:9200/students/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {
"bool": {
"filter": [
{"range": {"age": {"gte": 20, "lte": 25}}}
],
"should": [
{"term": {"major": "计算机科学"}},
{"term": {"major": "数据科学"}}
],
"must_not": [
{"range": {"gpa": {"lt": 3.0}}}
],
"minimum_should_match": 1
}
}
}'

聚合分析

聚合查询用于数据统计和分析,提供了强大的数据洞察能力。

聚合分为指标聚合(计算数值)和桶聚合(分组统计),可以灵活组合使用。详细原理请参考第五部分。

指标聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 计算平均GPA和多项统计
curl -X POST "localhost:9200/students/_search" \
-H "Content-Type: application/json" \
-d '{
"aggs": {
"avg_gpa": {
"avg": {"field": "gpa"}
},
"gpa_stats": {
"stats": {"field": "gpa"}
}
},
"size": 0
}'

桶聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 按专业分组统计,并计算每组平均GPA
curl -X POST "localhost:9200/students/_search" \
-H "Content-Type: application/json" \
-d '{
"aggs": {
"major_analysis": {
"terms": {"field": "major"},
"aggs": {
"avg_gpa": {"avg": {"field": "gpa"}}
}
}
},
"size": 0
}'

排序与分页

多字段排序

1
2
3
4
5
6
7
8
9
10
# 按GPA降序,年龄升序排列
curl -X POST "localhost:9200/students/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {"match_all": {}},
"sort": [
{"gpa": {"order": "desc"}},
{"age": {"order": "asc"}}
]
}'

分页策略

ES提供两种分页方式:from/size适合小数据量,search_after适合大数据量。深分页会导致性能问题,建议大数据量场景使用search_after。详细原理请参考第五部分。

小数据量:from/size分页

1
2
3
4
5
6
7
8
curl -X POST "localhost:9200/students/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {"match_all": {}},
"from": 10,
"size": 10,
"sort": [{"gpa": {"order": "desc"}}]
}'

大数据量:search_after分页(推荐)

1
2
3
4
5
6
7
8
9
10
11
curl -X POST "localhost:9200/students/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {"match_all": {}},
"size": 10,
"sort": [
{"gpa": {"order": "desc"}},
{"_id": {"order": "asc"}}
],
"search_after": [3.8, "student_id_123"]
}'

高亮搜索

高亮功能可以在搜索结果中突出显示匹配的关键词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 搜索并高亮姓名字段
curl -X POST "localhost:9200/students/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {
"match": {"name": "张"}
},
"highlight": {
"pre_tags": ["<mark>"],
"post_tags": ["</mark>"],
"fields": {
"name": {}
}
}
}'

第三部分:系统优化

核心参数详解

掌握ES的核心参数是进行高级优化的基础,按功能分为以下几类:

核心参数默认值一览

了解参数默认值有助于理解ES的默认行为和优化方向:

字段级别参数默认值

参数名 默认值 数据类型适用 说明
index true 所有类型 字段可被搜索
store false 所有类型 不独立存储字段值
doc_values true keyword, numeric, date等 支持排序和聚合
norms true text字段 启用长度归一化(评分用)
eager_global_ordinals false keyword字段 不预计算聚合
fielddata false text字段 禁用内存聚合
ignore_above 256 keyword字段 超长字符串截断长度
null_value null 所有类型 空值不索引
coerce true numeric字段 自动类型转换
boost 1.0 所有类型 字段权重系数

索引级别参数默认值

参数名 默认值 说明
refresh_interval 1s 数据刷新间隔
number_of_shards 1 主分片数量
number_of_replicas 1 副本分片数量
max_result_window 10000 from+size最大值
max_rescore_window 10000 重评分窗口大小

集群级别参数默认值

参数名 默认值 说明
cluster.routing.allocation.disk.watermark.low 85% 磁盘使用率低水位线
cluster.routing.allocation.disk.watermark.high 90% 磁盘使用率高水位线
indices.memory.index_buffer_size 10% 索引缓冲区大小
thread_pool.write.queue_size 200 写入队列大小

分析器默认值

参数名 默认值 说明
analyzer standard 默认分析器
search_analyzer 同analyzer 搜索时分析器
normalizer keyword字段标准化器

字段索引控制

index参数 - 控制字段是否可搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"mappings": {
"properties": {
"title": {
"type": "text",
"index": true // 可搜索
},
"internal_data": {
"type": "text",
"index": false // 只存储,不可搜索,节省空间
}
}
}
}

store参数 - 控制字段独立存储

1
2
3
4
5
6
{
"summary": {
"type": "text",
"store": true // 独立存储,快速访问
}
}

分析器配置

analyzer和search_analyzer - 分别控制索引和搜索时的分词器

1
2
3
4
5
6
7
{
"product_name": {
"type": "text",
"analyzer": "ik_max_word", // 索引时精细分词
"search_analyzer": "ik_smart" // 搜索时智能分词
}
}

多字段映射

fields参数 - 一个字段多种用途

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"title": {
"type": "text", // 主字段:全文搜索
"fields": {
"keyword": { // 子字段:精确匹配
"type": "keyword",
"ignore_above": 256
},
"suggest": { // 子字段:自动补全
"type": "completion"
}
}
}
}

性能优化参数

doc_values - 控制列式存储(用于排序、聚合)

1
2
3
4
5
6
{
"user_id": {
"type": "keyword",
"doc_values": false // 纯ID字段,禁用节省空间
}
}

eager_global_ordinals - 预计算聚合性能

1
2
3
4
5
6
{
"category": {
"type": "keyword",
"eager_global_ordinals": true // 经常聚合的字段启用
}
}

索引优化

批量索引配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 临时优化设置(批量导入时)
curl -X PUT "localhost:9200/my_index/_settings" \
-H "Content-Type: application/json" \
-d '{
"refresh_interval": -1, // 禁用自动刷新
"number_of_replicas": 0 // 临时移除副本
}'

# 批量导入后恢复设置
curl -X PUT "localhost:9200/my_index/_settings" \
-H "Content-Type: application/json" \
-d '{
"refresh_interval": "1s",
"number_of_replicas": 1
}'

查询优化

filter查询比must查询性能更好,因为filter不计算评分且支持查询缓存。过滤场景优先使用filter,搜索排序场景使用must。详细性能原理请参考第五部分。

使用filter替代must(性能更好)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"优化前": {
"query": {
"bool": {
"must": [
{"range": {"price": {"gte": 100}}},
{"term": {"status": "active"}}
]
}
}
},
"优化后": {
"query": {
"bool": {
"filter": [
{"range": {"price": {"gte": 100}}},
{"term": {"status": "active"}}
]
}
}
}
}

性能优化总结

ES性能优化SOP:

  1. 先分析业务场景和数据特征
  2. 再根据读写比例确定优化重点
  3. 最后通过监控验证优化效果

性能优化策略:

  • 存储优化: “禁用不需要的功能(index、doc_values、norms、store)
  • 查询优化”: “预计算高频操作(eager_global_ordinals)+ 合理缓存
  • 写入优化”: “批量操作时临时调整refresh_interval和副本数
  • 集群优化”: “合理分片 + 充足资源 + 监控水位线

ES参数汇总表:

1、存储空间优化参数

参数名 级别 默认值 优化建议 性能收益 使用场景
index 字段级 true 不需要搜索的字段设为false 减少20-50%存储空间 纯展示字段、内部ID
store 字段级 false 高频访问字段设为true 减少30-70%IO开销 大文档的部分字段查询
doc_values 字段级 true 不聚合排序的字段设为false 减少10-30%存储空间 纯搜索字段、过滤字段
norms 字段级 true 不需要评分的text字段设为false 减少评分计算开销 keyword化的text字段
ignore_above 字段级 256 根据实际数据调整 避免超长字符串索引 URL、描述等长文本字段
_source 索引级 enabled 大文档可禁用或过滤 减少50-80%存储空间 仅搜索不返回原文档

2、查询性能优化参数

参数名 级别 默认值 优化建议 性能收益 使用场景
eager_global_ordinals 字段级 false 高频聚合字段设为true 减少50-90%聚合响应时间 分类、标签等高频聚合字段
fielddata 字段级 false 谨慎启用,优先使用keyword 支持text字段聚合 无法改为keyword的遗留字段
boost 字段级 1.0 设置字段相关性权重 提升搜索精度 多字段搜索的权重调优
refresh_interval 索引级 1s 批量导入时设为-1 提升2-5倍写入性能 批量数据导入场景
max_result_window 索引级 10000 根据分页需求调整 控制内存消耗 深分页业务需求

3、集群资源优化参数

参数名 级别 默认值 优化建议 性能收益 使用场景
number_of_shards 索引级 1 数据量(GB) ÷ 30GB 提升并行查询性能 大数据量索引
number_of_replicas 索引级 1 根据可用性需求调整 平衡性能与可用性 高可用vs性能权衡
indices.memory.index_buffer_size 集群级 10% 写入密集型调至15-20% 提升写入性能 高并发写入场景
thread_pool.write.queue_size 集群级 200 写入压力大时增加至1000 减少写入拒绝 写入峰值处理
cluster.routing.allocation.disk.watermark.low/high 集群级 85%/90% 根据磁盘容量规划调整 避免分片重分布 磁盘容量管理

4、分析器性能参数

参数名 级别 默认值 优化建议 性能收益 使用场景
analyzer 字段级 standard 中文使用ik_max_word 提升中文搜索效果 中文内容搜索
search_analyzer 字段级 同analyzer 索引用ik_max_word,搜索用ik_smart 平衡索引大小与搜索精度 中文搜索优化
normalizer 字段级 keyword字段标准化处理 提升精确匹配准确性 大小写不敏感匹配

集群管理

节点角色配置

1
2
3
4
5
6
7
8
9
10
11
# 主节点 - 负责集群管理
node.roles: [master]
node.name: master-node-1

# 数据节点 - 负责数据存储和查询
node.roles: [data, data_content, data_hot, data_warm]
node.name: data-node-1

# 协调节点 - 负责查询路由和结果聚合
node.roles: []
node.name: coordinating-node-1

内存配置原则

1
2
3
4
5
6
7
8
# JVM内存设置
-Xms4g
-Xmx4g # 堆内存不超过系统内存50%,最大32GB
-XX:+UseG1GC # 推荐G1垃圾收集器

# ES配置
bootstrap.memory_lock: true # 锁定内存,避免交换
indices.memory.index_buffer_size: 10% # 索引缓冲区大小

分片策略

1
2
3
4
5
6
7
8
{
"分片设置原则": {
"分片大小": "单个分片20-50GB最佳",
"分片数量": "数据量(GB) ÷ 30GB",
"副本数量": "至少1个副本保证高可用",
"扩展规划": "主分片数确定后无法修改,需预留空间"
}
}

监控与运维

关键监控指标

1
2
3
4
5
6
7
8
# 集群健康状态
curl -X GET "localhost:9200/_cluster/health?pretty"

# 节点统计信息
curl -X GET "localhost:9200/_nodes/stats?pretty"

# 索引统计信息
curl -X GET "localhost:9200/_stats?pretty"

故障排查

1
2
3
4
5
6
7
8
# 查看未分配分片原因
curl -X GET "localhost:9200/_cluster/allocation/explain?pretty"

# 查看慢查询日志
tail -f /var/log/elasticsearch/slowlog.log

# 查看热点线程
curl -X GET "localhost:9200/_nodes/hot_threads"

第四部分:高级搜索

人性化搜索功能

多字段智能搜索

企业级搜索通常需要在多个字段中智能匹配用户输入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 智能产品搜索 - 不同字段不同权重
curl -X POST "localhost:9200/products/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {
"multi_match": {
"query": "苹果手机",
"fields": [
"title^3", // 标题权重最高
"brand^2", // 品牌权重高
"description", // 描述标准权重
"tags^0.5" // 标签权重低
],
"type": "best_fields",
"minimum_should_match": "75%"
}
},
"highlight": {
"fields": {
"title": {},
"description": {"fragment_size": 100}
}
}
}'

容错搜索

容错搜索基于编辑距离算法,自动纠正用户的拼写错误,提升搜索体验。详细算法原理请参考第五部分:高级搜索原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 模糊匹配 - 处理拼写错误
curl -X POST "localhost:9200/products/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {
"multi_match": {
"query": "iphne", // 故意拼错
"fields": ["title", "brand"],
"fuzziness": "AUTO", // 自动容错
"prefix_length": 1,
"max_expansions": 50
}
}
}'

地理位置搜索

地理搜索在O2O、外卖、地图应用中非常重要。

地理数据建模

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建餐厅索引
curl -X PUT "localhost:9200/restaurants" \
-H "Content-Type: application/json" \
-d '{
"mappings": {
"properties": {
"name": {"type": "text"},
"location": {"type": "geo_point"}, // 地理坐标点
"area": {"type": "geo_shape"}, // 地理形状
"cuisine_type": {"type": "keyword"},
"rating": {"type": "float"}
}
}
}'

距离搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 搜索1公里内的餐厅,按距离排序
curl -X POST "localhost:9200/restaurants/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {
"bool": {
"filter": {
"geo_distance": {
"distance": "1km",
"location": {"lat": 39.9042, "lon": 116.4074}
}
}
}
},
"sort": [
{
"_geo_distance": {
"location": {"lat": 39.9042, "lon": 116.4074},
"order": "asc",
"unit": "m"
}
}
]
}'

智能补全

提供优秀搜索体验的关键功能。ES使用FST(有限状态转换器)数据结构实现高效的前缀匹配和补全建议。详细数据结构原理请参考第五部分:高级搜索原理。

补全索引设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
curl -X PUT "localhost:9200/products_v2" \
-H "Content-Type: application/json" \
-d '{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"suggest": {
"type": "completion", // 补全字段
"analyzer": "ik_max_word"
}
}
}
}
}
}'

补全查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 自动补全查询
curl -X POST "localhost:9200/products_v2/_search" \
-H "Content-Type: application/json" \
-d '{
"suggest": {
"title_suggest": {
"prefix": "手", // 用户输入的前缀
"completion": {
"field": "title.suggest",
"size": 5,
"skip_duplicates": true
}
}
}
}'

数据同步方案

企业级应用需要保持主数据库与ES的同步。

同步方案选择

方案 优点 缺点 适用场景
定时全量 简单可靠 实时性差,资源消耗大 小数据量,实时性要求不高
MQ异步 解耦,高可用 架构复杂,一致性问题 微服务架构,高并发
CDC捕获 实时性最高,无侵入 技术门槛高,运维复杂 企业级,实时性要求极高

MQ异步同步实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 数据同步消息
@Data
public class DataSyncMessage {
private String operation; // CREATE, UPDATE, DELETE
private String entityType; // PRODUCT, ORDER, USER
private String entityId;
private Object data;
private Long timestamp;
}

// 同步监听器
@Component
@RabbitListener(queues = "product.sync.queue")
@Slf4j
public class ProductSyncListener {

@Autowired
private ProductSearchService searchService;

@RabbitHandler
public void handleProductSync(DataSyncMessage message) {
try {
log.info("接收同步消息: {}", message);

switch (message.getOperation()) {
case "CREATE":
case "UPDATE":
// 从数据库获取最新数据并同步到ES
syncProductToES(message.getEntityId());
break;
case "DELETE":
// 从ES删除文档
searchService.deleteProduct(message.getEntityId());
break;
}

} catch (Exception e) {
log.error("同步失败: {}", message, e);
throw new SyncException("数据同步失败", e);
}
}
}

第五部分:核心原理

理解Elasticsearch的底层原理是掌握高级优化和故障排查的关键。本部分深入剖析ES的核心机制。

ES技术架构与JVM

核心技术栈

Elasticsearch的技术基础:

1
2
3
4
5
6
7
{
"开发语言": "Java",
"核心引擎": "Apache Lucene 9.x",
"运行环境": "JVM (Java Virtual Machine)",
"最低Java版本": "JDK 11+",
"推荐Java版本": "OpenJDK 17 或 Oracle JDK 17"
}

为什么选择Java?

Java技术栈的优势:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"技术优势": {
"成熟稳定": "Java生态成熟,大量企业级应用验证",
"性能优秀": "JIT编译器优化,长时间运行性能强劲",
"内存管理": "自动垃圾回收,减少内存泄漏风险",
"跨平台": "一次编写,到处运行,支持多种操作系统"
},
"Lucene继承": {
"历史原因": "基于Apache Lucene,自然选择Java",
"技术积累": "继承Lucene的搜索算法和优化经验",
"社区支持": "Java搜索引擎生态完善"
}
}

倒排索引原理

什么是倒排索引?

为什么叫”倒排”索引?
传统数据库是”正向”的:文档ID → 包含的词汇
倒排索引是”反向”的:词汇 → 包含该词的文档ID列表

原始的数据会被ES进行分词处理,分词后,词为key,原文件地址为value,这样就形成了反向的映射。

倒排索引结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"原始文档": {
"doc1": "我爱北京天安门",
"doc2": "北京欢迎你",
"doc3": "我爱祖国"
},
"倒排索引": {
"我": ["doc1", "doc3"],
"爱": ["doc1", "doc3"],
"北京": ["doc1", "doc2"],
"天安门": ["doc1"],
"欢迎": ["doc2"],
"你": ["doc2"],
"祖国": ["doc3"]
}
}

倒排索引的优势:

  • 快速定位:通过词汇直接找到相关文档
  • 高效组合:多个词汇的交集、并集运算
  • 相关性评分:基于词频、逆文档频率等计算相关性

倒排索引存储结构

完整的倒排索引包含:

1
2
3
4
5
6
7
8
9
10
11
12
{
"词汇字典": {
"作用": "存储所有不重复的词汇",
"结构": "排序的词汇列表,支持快速查找",
"优化": "前缀压缩、增量编码"
},
"倒排列表": {
"作用": "存储每个词汇对应的文档列表",
"内容": ["文档ID", "词频TF", "位置信息"],
"压缩": "差值编码、变长编码"
}
}

分片与副本原理

分片设计目的

分片(Shard)解决的核心问题:

1
2
3
4
5
6
7
8
9
10
11
{
"分片解决的问题": {
"水平扩展": "单机存储容量限制",
"并行处理": "多分片并行查询提高性能",
"故障隔离": "单分片故障不影响整体服务"
},
"分片类型": {
"主分片": "Primary Shard - 负责写入和读取",
"副本分片": "Replica Shard - 主分片的完整副本"
}
}

数据分布机制

文档路由算法:

1
2
3
4
5
6
7
# 文档路由公式
shard_id = hash(document_id) % number_of_primary_shards

# 示例:3个主分片的路由
hash("doc1") % 3 = 1 # 文档分配到分片1
hash("doc2") % 3 = 0 # 文档分配到分片0
hash("doc3") % 3 = 2 # 文档分配到分片2

为什么主分片数创建后不能修改?
因为文档路由依赖主分片数的哈希计算,修改分片数会导致所有文档路由错乱,必须重建索引。

近实时搜索原理

传统数据库 vs ES的实时性

特性 传统数据库 Elasticsearch
写入方式 立即写磁盘 先写内存,定期刷盘
搜索延迟 无延迟 1秒内(可配置)
性能权衡 写入慢,查询快 写入快,微延迟搜索

ES实时搜索流程

三个关键概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Buffer": {
"作用": "内存缓冲区,临时存储新文档",
"特点": "写入速度快,但不可搜索"
},
"Refresh": {
"作用": "将Buffer内容刷新为可搜索的Segment",
"频率": "默认1秒,可配置",
"结果": "数据变为可搜索状态"
},
"Flush": {
"作用": "将Segment持久化到磁盘",
"频率": "默认30分钟或Translog达到512MB",
"结果": "数据永久保存"
}
}

完整的写入和搜索流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"写入流程": {
"步骤1": "文档写入内存Buffer",
"步骤2": "同时写入Translog(事务日志)",
"步骤3": "每1秒refresh,Buffer → Segment(可搜索)",
"步骤4": "定期flush,Segment → 磁盘(持久化)"
},
"搜索流程": {
"步骤1": "查询所有可搜索的Segment",
"步骤2": "合并多个Segment的结果",
"步骤3": "排序并返回最终结果"
}
}

分词器原理

分词器组成架构

分词器(Analyzer)三层结构:两个filter,肉夹馍结构

1
2
3
4
5
6
7
{
"分词器结构": {
"字符过滤器": "Character Filter - 预处理文本(去HTML标签等)",
"分词器": "Tokenizer - 将文本拆分为词汇单元",
"词汇过滤器": "Token Filter - 后处理词汇(小写、停词等)"
}
}

标准分词器工作流程

处理流程示例:

1
2
3
4
5
6
7
8
9
{
"示例文本": "Hello World! ES-Learning.",
"处理流程": {
"1. 字符过滤": "Hello World! ES-Learning. (无变化,字符过滤的是html标签等内容)",
"2. 分词处理": ["Hello", "World", "ES", "Learning"],
"3. 词汇过滤": ["hello", "world", "es", "learning"],
"4. 最终结果": ["hello", "world", "es", "learning"]
}
}

中文分词特殊性

中文分词的挑战:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"中文分词挑战": {
"无明显分隔符": "中文词汇之间没有空格分隔",
"语义依赖": "需要理解上下文确定词汇边界",
"标准分词器": "按字符拆分,效果差",
"专业分词器": "IK、jieba等,基于词典和算法"
},
"示例对比": {
"原文": "我爱北京天安门",
"标准分词": ["我", "爱", "北", "京", "天", "安", "门"],
"IK分词": ["我", "爱", "北京", "天安门"]
}
}

聚合原理

聚合类型划分

指标聚合(Metric Aggregations)

  • 定义:对一组文档进行数值计算,返回单个或多个数值结果
  • 特点:输入文档组 → 输出数值
  • 类型:单值指标(avg、sum、min、max)、多值指标(stats、percentiles)

桶聚合(Bucket Aggregations)

  • 定义:根据条件将文档分组到不同的”桶”中
  • 特点:输入文档组 → 输出文档桶(可嵌套子聚合)
  • 类型:terms分组、range范围、date_histogram时间分组

聚合执行原理

聚合的执行过程:

1
2
3
4
5
6
7
8
9
{
"聚合执行流程": {
"1. 文档收集": "根据query条件收集匹配的文档",
"2. 桶分组": "按照桶聚合条件将文档分到不同桶中",
"3. 指标计算": "对每个桶内的文档执行指标聚合",
"4. 结果合并": "合并各分片的聚合结果",
"5. 返回数据": "返回最终的聚合统计结果"
}
}

分页原理

深分页问题根本原因

from/size分页的性能问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"问题分析": {
"from_size工作原理": {
"步骤1": "每个分片都查询 from + size 条数据",
"步骤2": "协调节点收集所有分片的结果",
"步骤3": "全局排序后取 from 到 from+size 的数据",
"步骤4": "丢弃前 from 条数据,返回 size 条数据"
},
"性能损耗": {
"内存消耗": "需要在内存中处理 from + size 条数据",
"网络传输": "每个分片传输 from + size 条数据",
"CPU消耗": "大量数据的排序和比较操作"
}
}
}

search_after原理

基于游标的分页机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"search_after原理": {
"工作机制": {
"无需跳过": "不需要跳过前面的数据",
"游标定位": "基于上一页最后一条记录的排序值定位",
"恒定性能": "每次查询性能恒定,不随页数增加而降低"
},
"必要条件": {
"必须排序": "query中必须包含sort字段",
"唯一性保证": "排序字段组合必须能唯一标识文档",
"常用方案": "业务字段 + _id 组合排序"
}
}
}

查询优化原理

filter vs must 性能差异

性能差异的根本原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"性能差异分析": {
"must查询特点": {
"相关性评分": "每个匹配的文档都需要计算_score",
"TF-IDF算法": "计算词频、逆文档频率、字段长度标准化",
"排序影响": "评分结果影响文档排序",
"无法缓存": "评分是动态计算的,结果难以缓存"
},
"filter查询特点": {
"不计算评分": "_score始终为0,节省大量计算",
"布尔判断": "只判断匹配/不匹配,二进制结果",
"查询缓存": "结果可以缓存,重复查询极快",
"位运算优化": "内部使用BitSet进行快速位运算"
}
}
}

查询缓存机制

filter查询的缓存机制:

1
2
3
4
5
6
7
8
9
{
"filter缓存机制": {
"缓存对象": "BitSet位图 - 每个文档一个bit标识匹配状态",
"缓存条件": "查询频率高、结果相对稳定的filter",
"缓存淘汰": "LRU算法,内存不足时淘汰最少使用的缓存",
"缓存命中": "相同filter查询直接返回缓存结果,无需重新计算",
"缓存大小": "默认最多缓存10000个filter查询"
}
}

容错搜索原理

模糊查询(Fuzzy Query)算法基础

编辑距离(Levenshtein Distance)算法:

1
2
3
4
5
6
7
8
9
10
11
{
"编辑距离定义": {
"概念": "将一个字符串转换为另一个字符串所需的最少编辑操作次数",
"操作类型": ["插入字符", "删除字符", "替换字符"],
"示例": {
"cat → bat": "1次操作(替换c为b)",
"cat → cats": "1次操作(插入s)",
"cat → ca": "1次操作(删除t)"
}
}
}

ES模糊查询的工作流程:

1
2
3
4
5
6
7
8
9
{
"模糊查询流程": {
"步骤1": "用户输入查询词(如:'iphne')",
"步骤2": "ES根据fuzziness参数确定最大编辑距离",
"步骤3": "在倒排索引中查找编辑距离范围内的所有词汇",
"步骤4": "计算每个匹配词的相似度得分",
"步骤5": "返回按相似度排序的结果"
}
}

fuzziness参数详解

AUTO模式的智能判断:

1
2
3
4
5
6
7
8
9
10
11
12
{
"AUTO模式规则": {
"0-2个字符": "不允许编辑(fuzziness=0)",
"3-5个字符": "允许1次编辑(fuzziness=1)",
"6+个字符": "允许2次编辑(fuzziness=2)"
},
"示例应用": {
"输入'go'": "fuzziness=0,只匹配精确的'go'",
"输入'cat'": "fuzziness=1,可匹配'bat', 'car', 'cats'等",
"输入'iphone'": "fuzziness=2,可匹配'iphne', 'ipone', 'iphone'等"
}
}

前缀长度和扩展限制

prefix_length参数作用:

1
2
3
4
5
6
7
8
9
10
{
"前缀长度优化": {
"目的": "提高查询性能,减少候选词汇数量",
"原理": "要求前N个字符必须精确匹配",
"示例": {
"prefix_length=2": "'iphone' → 只考虑以'ip'开头的词汇",
"性能提升": "大幅减少需要计算编辑距离的词汇数量"
}
}
}

max_expansions参数控制:

1
2
3
4
5
6
7
8
{
"扩展数量限制": {
"目的": "防止查询过于消耗资源",
"默认值": "50个候选词",
"工作机制": "找到50个匹配词后停止搜索",
"权衡考虑": "较小值提升性能,较大值提高召回率"
}
}

容错搜索的性能优化

性能影响因素:

1
2
3
4
5
6
7
8
9
10
11
{
"性能考量": {
"计算复杂度": "O(m*n),m和n分别为两个字符串长度",
"索引影响": "需要遍历词汇字典计算编辑距离",
"优化策略": {
"使用prefix_length": "减少候选词数量",
"控制max_expansions": "限制计算量",
"合理设置fuzziness": "避免过度宽松的匹配"
}
}
}

智能补全原理

Completion Suggester核心机制

Finite State Transducer (FST) 数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"FST数据结构": {
"定义": "有限状态转换器,专门为前缀匹配优化的数据结构",
"优势": {
"空间效率": "相比Trie树节省50-80%内存",
"查询速度": "O(k)复杂度,k为前缀长度",
"共享前缀": "相同前缀的词汇共享存储路径"
},
"构建过程": {
"词汇排序": "按字典序排列所有补全词汇",
"路径共享": "相同前缀合并为同一路径",
"状态压缩": "最小化状态数量"
}
}
}

FST vs 传统Trie树对比:

特性 Trie树 FST
内存使用 高(每个节点独立) 低(路径共享压缩)
查询速度 O(k) O(k)
构建复杂度 简单 复杂(需排序和最小化)
适用场景 小数据集 大数据集、生产环境

补全查询的执行流程

查询处理步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"补全查询流程": {
"1. 前缀匹配": {
"输入": "用户输入前缀(如:'手')",
"处理": "在FST中查找以该前缀开始的所有路径",
"结果": "获得候选词汇列表"
},
"2. 权重排序": {
"输入": "候选词汇列表",
"处理": "根据每个词汇的权重(weight)进行排序",
"结果": "按相关性/热度排序的建议列表"
},
"3. 结果过滤": {
"输入": "排序后的建议列表",
"处理": "应用size参数限制和skip_duplicates去重",
"结果": "最终的补全建议"
}
}
}

权重计算策略

权重设置的常见策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"权重策略": {
"搜索热度": {
"数据来源": "历史搜索统计",
"计算方式": "搜索次数的对数值",
"示例": "weight = log(search_count + 1)"
},
"业务重要性": {
"数据来源": "业务规则定义",
"计算方式": "分类权重 × 基础权重",
"示例": "热销商品权重 × 2"
},
"时间衰减": {
"数据来源": "数据的新鲜度",
"计算方式": "基础权重 × 时间衰减因子",
"示例": "weight = base_weight × 0.9^days_old"
}
}
}

补全性能优化

内存优化策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"内存优化": {
"FST加载": {
"机制": "FST存储在堆外内存(off-heap)",
"优势": "不占用JVM堆内存,不影响GC",
"注意": "重启节点时需要重新加载"
},
"数据压缩": {
"前缀共享": "相同前缀的词汇共享存储",
"后缀最小化": "相同后缀也会合并",
"压缩比": "通常可达到50-80%的空间节省"
}
}
}

查询性能优化:

1
2
3
4
5
6
7
8
{
"查询优化": {
"分片级并行": "每个分片并行处理补全查询",
"结果合并": "协调节点合并各分片结果并重新排序",
"缓存策略": "频繁查询的前缀结果可被缓存",
"性能指标": "通常可达到<10ms的响应时间"
}
}

多语言补全支持

中文补全的特殊处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"中文补全挑战": {
"拼音支持": {
"需求": "支持拼音首字母和全拼输入",
"实现": "使用拼音分析器预处理",
"示例": "'手机' → ['sj', 'shouji', 'shou', 'ji']"
},
"多输入法": {
"全拼": "shouji → 手机",
"简拼": "sj → 手机",
"混合": "shou机 → 手机"
},
"同音词处理": {
"问题": "拼音相同的词汇竞争",
"解决": "结合权重和上下文进行排序"
}
}
}

术语概念深度解析

为什么ES被称为”索引”?

这是很多初学者的疑惑,让我们深入理解ES术语体系的历史演进:

多重含义解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"ES中索引的三重含义": {
"数据容器层面": {
"定义": "Index = Database,存储相关数据的逻辑集合",
"例子": "创建students索引存储学生信息",
"类比": "MySQL中的database概念"
},
"搜索引擎层面": {
"定义": "Index = 倒排索引,快速检索的数据结构",
"例子": "为每个字段构建倒排索引加速搜索",
"类比": "图书馆的索引目录"
},
"历史传承层面": {
"定义": "继承Lucene搜索引擎的术语体系",
"原因": "ES基于Apache Lucene构建",
"影响": "保持与搜索引擎生态的术语一致性"
}
}
}

术语对比澄清:

ES术语 数据库术语 搜索引擎术语 实际含义
Index Database Inverted Index 数据集合 + 搜索索引
Document Row Document JSON格式的数据记录
Field Column Field 文档中的属性字段
Mapping Schema Mapping 字段类型和属性定义

设计哲学体现:

1
2
3
4
5
6
7
8
{
"ES设计理念": {
"搜索为王": "一切设计都围绕快速搜索展开",
"倒排优先": "每个字段都默认构建倒排索引",
"近实时": "数据写入后1秒内即可搜索",
"分布式": "天然支持水平扩展和高可用"
}
}

这种命名方式强调了ES的核心价值:不仅是数据存储,更是高效的搜索引擎


实战总结

最佳实践

索引设计原则

  1. 合理选择字段类型:text用于搜索,keyword用于过滤和聚合
  2. 优化mapping配置:禁用不需要的功能(doc_values、norms等)
  3. 设计合适的分片:单分片20-50GB,分片数=数据量÷30GB
  4. 使用别名管理:便于索引重建和版本切换

查询优化技巧

  1. 优先使用filter:不需要评分的条件用filter,性能更好
  2. 合理设置分页:大数据量使用search_after替代from/size
  3. 控制聚合粒度:避免高基数字段的大量聚合
  4. 启用查询缓存:重复查询使用request_cache

生产环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 关键配置建议
cluster.name: production-cluster
node.name: es-node-1

# 内存设置
bootstrap.memory_lock: true
-Xms16g
-Xmx16g

# 性能优化
indices.memory.index_buffer_size: 10%
thread_pool.write.queue_size: 1000
cluster.routing.allocation.disk.watermark.low: 85%
cluster.routing.allocation.disk.watermark.high: 90%

常见问题解决

集群状态异常

1
2
3
4
5
# 诊断分片分配问题
curl -X GET "localhost:9200/_cluster/allocation/explain?pretty"

# 查看未分配分片
curl -X GET "localhost:9200/_cat/shards?h=index,shard,state,unassigned.reason&v"

性能问题排查

1
2
3
4
5
6
7
8
# 查看慢查询
tail -f /var/log/elasticsearch/slowlog.log

# 分析热点线程
curl -X GET "localhost:9200/_nodes/hot_threads"

# 检查队列状态
curl -X GET "localhost:9200/_nodes/stats/thread_pool?pretty"

本指南涵盖了Elasticsearch从入门到精通的完整知识体系,建议收藏备用,在实际项目中遇到问题时可随时查阅。