Elasticsearch
Java 高级 · 存储与搜索
Elasticsearch 是分布式全文检索与分析引擎,构建在 Lucene 之上。它不只是"能搜",核心是倒排索引 + 分布式分片 + 近实时(NRT)段机制。本文把索引原理、分片副本、写入链路(refresh/flush/translog)、DSL 与聚合讲透,让你在搜索与日志分析场景用得对、用得快。
一、倒排索引:为什么 match 比 like 快
1.1 正排 vs 倒排
- 正排(Doc → Terms):文档存着它包含的词。查"手机"要遍历所有文档,即
LIKE '%手机%'全表扫描。 - 倒排(Term → Docs):建立"词 → 文档列表"的映射:
term: docs (docId, 词频 tf, 位置 positions):
手机 → [doc1(tf=2,pos=3,9), doc5(tf=1,pos=2)]
华为 → [doc1, doc9]查询"手机"时直接查倒排表拿到 doc1, doc5,无需扫全表。这就是全文检索快的根本。
1.2 分词(Analysis)
写入时,文本经 Analyzer(分词器) 拆词:字符过滤(html_strip)→ 分词(Tokenizer,如 ik_max_word 中文细粒度)→ 词元过滤(小写、同义词)。查询时用相同 Analyzer 分词,保证写入与查询词对齐。中文必须用 ik 等中文分词器,否则默认 standard 按字切(每字一个 term,召回差)。
TIP
倒排索引还存 词频(tf) 和 位置(positions):tf 用于 BM25 打分排序,positions 用于短语查询("手机 华为" 紧邻)。text 字段才分词建倒排;keyword 字段整体作为一个 term 存入(不分词),用于精确匹配、聚合、排序。
二、分布式模型:Index / Shard / Replica
- Index(索引):类似库,由 Mapping(字段类型/分词)定义结构。
- Shard(主分片):Index 被切成多个主分片分布到节点,实现水平扩展 + 并行查询。分片数在创建索引时定,不能轻易改(改要 reindex)。
- Replica(副本):每个主分片的拷贝,提供冗余(高可用)+ 读扩展(副本也能承载查询)。
number_of_replicas可动态调整。
写入路由:hash(routing=docId) % 主分片数 决定落哪个主分片,副本异步复制。查询时协调节点把请求广播到所有相关分片,收集结果归并(归并是客户端无感的)。
三、写入链路:refresh / flush / translog(近实时真相)
ES 不是"写一条即刻可查",而是近实时(NRT,默认 1s 可见)。一次写入的完整链路:
- refresh(默认 1s):把内存 buffer 生成一个新 segment(Lucene 段,不可变),进入文件系统缓存。此刻文档可被搜索——这就是"近实时"的来源。频繁 refresh 提高实时性但增加 segment 数、拖慢性能。
- translog(事务日志):每次写先 append 到 translog(落盘),防止 refresh 前宕机丢数据。它是 ES 的"redo log"。
- flush(默认 30s):把文件系统缓存中的 segment 真正
fsync落盘,并清空 translog。之后数据安全持久化。 - merge(后台):小 segment 太多查询慢,ES 后台把多个小 segment 合并(merge) 成大的(Lucene
TieredMergePolicy),合并时物理删除被标记删除的文档(.del文件)。
WARNING
keyword当text用 / 反过来:精确匹配、聚合、排序必须用keyword;全文检索才用text。类型选错导致聚合失败或检索不准。- 深度分页用 from/size:
from+size超 1 万报错且性能差(每个分片都要取 top N 再协调节点归并)。应改search_after(游标)或scroll(快照,适合导出)。 - Mapping 频繁改字段类型:ES 字段类型不可直接改,需
reindex重建索引,设计期就要定好。 - 写入不批量化:单条写入 QPS 极低,应
bulk+ 调大refresh_interval(灌数据时可设 -1)提吞吐。
四、DSL 查询与聚合
4.1 查询类型
term:精确匹配(不分词,对keyword/数值),最快。match:分词全文匹配(对text),按 BM25 打分。bool:组合must(算分)/should(或)/filter(过滤,不算分、可缓存)/must_not。range:范围。constant_score包filter可避免算分提性能。
4.2 聚合(Aggregation)
ES 的强项是"搜索 + 分析一体"。聚合分桶(bucket:terms/group、date_histogram)和指标(metric:avg/sum/cardinality 去重计数)。
PUT /orders
{
"settings": { "number_of_shards": 3, "number_of_replicas": 1 },
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "ik_max_word" },
"status": { "type": "keyword" },
"amount": { "type": "scaled_float", "scaling_factor": 100 },
"created": { "type": "date" }
}
}
}GET /orders/_search
{
"query": {
"bool": {
"must": [{ "match": { "title": "手机" } }],
"filter": [
{ "term": { "status": "PAID" } },
{ "range": { "created": { "gte": "2026-01-01" } } }
]
}
},
"aggs": { "by_status": { "terms": { "field": "status" } } },
"sort": [{ "created": "desc" }],
"size": 20
}TIP
filter vs must:filter 子句只做"是否命中"判断、不算相关性分、结果可缓存,性能远好于 must。查询里能放进 filter 的条件(如 status=PAID、时间范围)务必放 filter,减少算分开销。这是 ES 查询性能调优的第一原则。
五、生产调优与监控
- 索引按时间滚动(如
logs-2026.09),热数据多副本、冷数据降副本/冻结;控制 shard 大小(建议 10-50GB,过大归并慢、过小分片多)。 - 写入调优:
bulk批量(5-15MB/批)、refresh_interval调大(如 30s)、副本先 0 灌数据再补。 - 查询把过滤条件放
filter;深度分页用search_after。 - 监控:堆内存(<50%,ES 吃堆做缓存)、
search/index延迟、未合并 segment 数、JVM GC 抖动。
📚 官方文档与参考资料
- Elasticsearch 官方指南:https://www.elastic.co/guide/index.html
- Elastic 中文文档(社区):https://www.elastic.co/cn/
- Elasticsearch 近实时搜索:https://www.elastic.co/guide/en/elasticsearch/reference/current/near-real-time.html
✏️ 练习与自测
- 用倒排索引原理解释"为什么 match 查询比
LIKE '%关键词%'快",并说明 tf/positions 的作用。 - 设计一个商品搜索索引的 Mapping,区分 text/keyword,写出带 filter 与聚合的 DSL。
- 深度分页(第 1000 页)为什么
from/size不行?search_after如何解决?给出改写示例。 - 说明一次文档写入从 buffer 到落盘的完整链路(refresh/translog/flush/merge),以及"近实时"的含义。
本节小结
ES 的高性能源于"倒排索引(Term→Docs 直接定位)+ 分布式分片(并行 + 冗余)+ 近实时段机制(refresh 进缓存即可查、translog 防丢、flush 落盘、merge 合并)"。用好它要把握三点:Mapping 类型精准(text 检索 / keyword 聚合排序)、查询用 filter 减负、写入靠 bulk 与 refresh 调优。分片大小与滚动索引是集群长治久安的关键。