学一下ES,另外这篇博客Cursor写的
Elasticsearch学习指南
目录概览
本指南分为五个主要部分,逐步深入Elasticsearch的学习:
- 基础入门 - ES核心概念与基础操作
- 查询分析 - 高级查询与数据分析技术
- 系统优化 - 性能优化与集群运维
- 高级搜索 - 人性化搜索功能与企业级应用
- 核心原理 - 底层机制与性能原理深度剖析
第一部分:基础入门
什么是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
POST http://localhost:9200/students/_search
|
补充一些特殊的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
| curl -X GET "localhost:9200/students/_search"
curl -X GET "localhost:9200/students/_doc/1"
curl -X POST "localhost:9200/students/_search" \ -H "Content-Type: application/json" \ -d '{ "query": { "match": {"name": "张三"} } }'
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": [], "filter": [], "should": [], "must_not": [] } } }
|
实战示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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
| 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
| 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
| 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 } }
|
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:
- 先分析业务场景和数据特征
- 再根据读写比例确定优化重点
- 最后通过监控验证优化效果
性能优化策略:
- 存储优化: “禁用不需要的功能(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
| -Xms4g -Xmx4g -XX:+UseG1GC
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
| 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; private String entityType; 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": syncProductToES(message.getEntityId()); break; case "DELETE": 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
hash("doc1") % 3 = 1 hash("doc2") % 3 = 0 hash("doc3") % 3 = 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的核心价值:不仅是数据存储,更是高效的搜索引擎。
实战总结
最佳实践
索引设计原则
- 合理选择字段类型:text用于搜索,keyword用于过滤和聚合
- 优化mapping配置:禁用不需要的功能(doc_values、norms等)
- 设计合适的分片:单分片20-50GB,分片数=数据量÷30GB
- 使用别名管理:便于索引重建和版本切换
查询优化技巧
- 优先使用filter:不需要评分的条件用filter,性能更好
- 合理设置分页:大数据量使用search_after替代from/size
- 控制聚合粒度:避免高基数字段的大量聚合
- 启用查询缓存:重复查询使用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从入门到精通的完整知识体系,建议收藏备用,在实际项目中遇到问题时可随时查阅。