Skip to content

Elasticsearch ​

Java 高级 · 存储与搜索

Elasticsearch 是分布式全文检索与分析引擎,构建在 Lucene 之上。它不只是"能搜",核心是倒排索引 + 分布式分片 + 近实时(NRT)段机制。本文把索引原理、分片副本、写入链路(refresh/flush/translog)、DSL 与聚合讲透,让你在搜索与日志分析场景用得对、用得快。

一、倒排索引:为什么 match 比 like 快 ​

1.1 正排 vs 倒排 ​

  • 正排(Doc → Terms):文档存着它包含的词。查"手机"要遍历所有文档,即 LIKE '%手机%' 全表扫描。
  • 倒排(Term → Docs):建立"词 → 文档列表"的映射:
text
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 去重计数)。

json
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" }
    }
  }
}
json
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 抖动。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 用倒排索引原理解释"为什么 match 查询比 LIKE '%关键词%' 快",并说明 tf/positions 的作用。
  2. 设计一个商品搜索索引的 Mapping,区分 text/keyword,写出带 filter 与聚合的 DSL。
  3. 深度分页(第 1000 页)为什么 from/size 不行?search_after 如何解决?给出改写示例。
  4. 说明一次文档写入从 buffer 到落盘的完整链路(refresh/translog/flush/merge),以及"近实时"的含义。

本节小结

ES 的高性能源于"倒排索引(Term→Docs 直接定位)+ 分布式分片(并行 + 冗余)+ 近实时段机制(refresh 进缓存即可查、translog 防丢、flush 落盘、merge 合并)"。用好它要把握三点:Mapping 类型精准(text 检索 / keyword 聚合排序)、查询用 filter 减负、写入靠 bulk 与 refresh 调优。分片大小与滚动索引是集群长治久安的关键。