缓存03:Elasticsearch
从倒排索引原理、分片路由、聚合查询到性能调优,拆解 Elasticsearch 在全文检索和分析场景下的核心机制和常见坑点。
Elasticsearch 不是”数据库”,而是”搜索引擎 + 分析引擎”。它的核心场景是全文检索、日志分析、监控指标和聚合查询,支持倒排索引、分片路由、分布式聚合和实时搜索,适合搜索服务、日志平台、监控系统和数据分析场景。
这篇文章围绕四个核心机制:倒排索引、分片路由、聚合查询、性能调优,把 Elasticsearch 的设计逻辑和常见坑点讲清楚。
为什么选 Elasticsearch,而不是 MySQL LIKE
先定位清楚:Elasticsearch 的核心场景是全文检索和聚合分析,支持倒排索引、分词、相关性排序和分布式聚合,适合搜索服务、日志分析、监控指标。
与 MySQL LIKE 查询的差异:
| 维度 | Elasticsearch | MySQL LIKE |
|---|---|---|
| 检索方式 | 倒排索引,直接定位文档 | 全表扫描,逐行匹配 |
| 分词能力 | 内置分词器,支持中文分词 | 不支持分词,模糊匹配 |
| 相关性排序 | TF-IDF / BM25 算法,按相关性排序 | 不支持,按主键排序 |
| 聚合能力 | 分布式聚合,支持多维分析 | GROUP BY,单机聚合 |
| 写入性能 | 批量写入,性能高 | 单条写入,事务开销大 |
| 事务支持 | 不支持事务,最终一致性 | ACID 事务,强一致性 |
简单判断:全文检索、日志分析、多维聚合 → Elasticsearch 合适。事务操作、主键查询、强一致性 → MySQL 合适。
Elasticsearch 不是 MySQL 的替代品,而是 MySQL 的补充:MySQL 存储数据,Elasticsearch 建立搜索索引,两者配合使用。
倒排索引:搜索的核心
Elasticsearch 的检索能力来自倒排索引(Inverted Index),这是搜索引擎的核心数据结构。
正排索引 vs 倒排索引
- 正排索引:文档 ID → 文档内容,适合按 ID 查询文档
- 倒排索引:词(Term)→ 文档 ID 列表,适合按词查询文档
示例:
文档 1:Elasticsearch 是搜索引擎
文档 2:MySQL 是数据库
文档 3:搜索引擎和数据库都是中间件
正排索引:
文档 1 → Elasticsearch, 是, 搜索引擎
文档 2 → MySQL, 是, 数据库
文档 3 → 搜索引擎, 和, 数据库, 都, 是, 中间件
倒排索引:
Elasticsearch → 文档 1
是 → 文档 1, 文档 2, 文档 3
搜索引擎 → 文档 1, 文档 3
MySQL → 文档 2
数据库 → 文档 2, 文档 3
和 → 文档 3
都 → 文档 3
中间件 → 文档 3
搜索”搜索引擎”时,直接从倒排索引找到文档 1 和文档 3,不需要扫描所有文档。
分词:倒排索引的前提
文档写入 Elasticsearch 前,需要分词(Analysis):
- 字符过滤:去除 HTML 标签、特殊字符
- 分词:将文本拆成词(Token),如”搜索引擎”拆成”搜索”、“引擎”
- 词过滤:去除停用词(如”的”、“是”)、小写化、同义词转换
Elasticsearch 内置分词器:
| 分词器 | 说明 | 适用场景 |
|---|---|---|
| standard | 按空格和标点分词,多语言通用 | 通用场景 |
| simple | 按字母分词,去除非字母 | 英文简单分词 |
| whitespace | 按空格分词 | 不需要复杂分词 |
| ik_max_word / ik_smart | 中文分词插件 | 中文场景 |
中文场景推荐安装 IK 分词器,支持中文智能分词。
相关性排序:TF-IDF / BM25
搜索结果按相关性排序,Elasticsearch 使用 TF-IDF(7.x 之前)或 BM25(7.x 之后)算法:
- TF(Term Frequency):词频,词在文档中出现次数越多,相关性越高
- IDF(Inverse Document Frequency):逆文档频率,词在所有文档中出现越少,区分度越高
- BM25:改进的 TF-IDF,避免长文档权重过高
简单理解:关键词在文档中出现次数多、在其他文档中出现少,相关性高。
分片路由:分布式检索的基础
Elasticsearch 是分布式系统,数据分片存储在多个节点上。
分片类型
| 分片类型 | 说明 |
|---|---|
| 主分片(Primary Shard) | 存储数据,参与读写,一个索引可有多个主分片 |
| 副本分片(Replica Shard) | 主分片的副本,分担读请求,提供高可用 |
分片数量在创建索引时指定,后续不能修改(需重建索引):
PUT /my_index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
分片路由规则
文档写入时,根据路由规则分配到主分片:
shard_num = hash(_routing) % num_primary_shards
默认 _routing 是文档 ID,可以自定义路由字段。相同路由字段的文档会分配到同一分片,适合关联数据(如同一订单的日志)。
分片数量怎么定
分片数量影响并发度和资源消耗:
- 分片过少:并发度低,搜索慢
- 分片过多:资源消耗大(每个分片是一个 Lucene 实例),聚合慢
经验值:每个分片大小 10GB - 50GB,每个节点分片数量不超过 20 个。根据数据量估算:
分片数量 = 数据总量 / 单分片大小(如 30GB)
聚合查询:多维分析的核心
Elasticsearch 的聚合能力是它区别于其他搜索引擎的核心能力,支持多维分析、统计和可视化。
聚合类型
| 聚合类型 | 说明 | 示例 |
|---|---|---|
| Metric 聚合 | 计算指标,如 sum、avg、max、min | 订单总金额、平均价格 |
| Bucket 聚合 | 分桶聚合,如 terms、range、histogram | 按用户分组、按时间分桶 |
| Pipeline 聚合 | 对聚合结果再聚合,如 derivative、cumulative_sum | 计算增长率、累计求和 |
示例:按用户分组统计订单金额
GET /orders/_search
{
"size": 0,
"aggs": {
"group_by_user": {
"terms": { "field": "user_id", "size": 10 },
"aggs": {
"total_amount": { "sum": { "field": "amount" } }
}
}
}
}
聚合的坑点
| 坑点 | 说明 | 解决方式 |
|---|---|---|
| 分片聚合 + 合并 | 每个分片先聚合,再合并结果,可能导致 terms 聚合结果不准确 | 增加 shard_size 参数,每个分片返回更多桶 |
| 聚合字段未建立 doc_values | 聚合依赖 doc_values,字段未建立则聚合失败 | 字段映射设置 "doc_values": true |
| text 字段聚合 | text 字段默认不支持聚合,需要 fielddata | 使用 keyword 子字段或启用 fielddata(不推荐) |
性能调优:写入和查询
Elasticsearch 的性能调优围绕写入和查询两个维度。
写入调优
| 优化点 | 说明 |
|---|---|
| 批量写入 | 使用 Bulk API 批量写入,减少网络开销 |
| 刷新间隔 | 增大 refresh_interval(默认 1s),减少刷新频率,提升写入性能 |
| 副本数量 | 写入时设置 number_of_replicas=0,写入完成后恢复,减少副本同步开销 |
| 禁用刷新 | 大批量写入时禁用刷新 refresh=false,写入完成后手动刷新 |
配置示例:
PUT /my_index/_settings
{
"index": {
"refresh_interval": "30s",
"number_of_replicas": 0
}
}
查询调优
| 优化点 | 说明 |
|---|---|
| 避免通配符查询 | *keyword* 会导致全表扫描,性能差 |
| 使用 filter 上下文 | filter 不计算评分,会缓存结果,性能比 query 高 |
| 分页深度 | 深分页(from + size 过大)性能差,使用 search_after 替代 |
| 字段映射优化 | 不需要搜索的字段设置 "index": false,减少索引体积 |
filter 上下文示例:
GET /orders/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "status": "paid" } },
{ "range": { "amount": { "gte": 100 } } }
]
}
}
}
常见误区
| 误区 | 真实情况 |
|---|---|
| Elasticsearch 是数据库 | 它是搜索引擎,不支持事务,不适合作为主存储 |
| 分片越多越好 | 分片过多资源消耗大,聚合慢 |
| text 字段可以直接聚合 | text 字段默认不支持聚合,需要 keyword 子字段 |
| 深分页可以用 from + size | 深分页性能差,需要用 search_after 或 scroll |
项目判断
引入 Elasticsearch 时,先回答三个问题:
- 检索场景:是否需要全文检索、模糊匹配、相关性排序?
- 聚合场景:是否需要多维聚合、统计分析、可视化?
- 数据量:数据量是否达到百万级以上?
如果需要全文检索、多维聚合、数据量大 → Elasticsearch 合适。如果只是简单查询、事务操作、数据量小 → MySQL 合适。
线上排查 Elasticsearch 问题:
- 慢查询:使用
_search?profile=true分析查询耗时,检查是否通配符查询、深分页 - 写入慢:检查刷新间隔、副本数量,使用 Bulk API 批量写入
- 磁盘满:清理旧数据、调整保留策略、扩容磁盘
- 分片不均衡:检查分片路由规则,手动重新分配分片