当产品需要真正的搜索——容错、按相关性排序、在百万级文档上飞快——关系型的 WHERE text LIKE '%query%' 就崩了,因为它要扫每一行、还没法排序。Elasticsearch(构建在 Apache Lucene 之上)用一种根本不同的数据结构——倒排索引(inverted index)——加上一层分布式引擎来扩展,解决了这个问题。它支撑了全文搜索、日志分析(ELK 技术栈里的 "E")和可观测性后端。理解它,基本就是理解倒排索引和相关性打分。

⚡ 速览要点
  • 倒排索引把每个词项(term)映射到包含它的文档列表,所以搜索是一次快速查表,而不是全表扫描。
  • 分析器(analyzer)在索引查询两端把文本切成词项(小写化、词干提取、去停用词)——这就是为什么搜 "Running" 能命中 "run"。
  • 不只是匹配,而是相关性——结果用 BM25 打分并排序,最匹配的排在前面。
  • 文档 → 索引 → 分片 → 副本——JSON 文档放在 index 里,切成分片(shard)散布到节点,每个分片再做副本(replica)。
  • 近实时,不是即时——新建索引的文档要等一次 refresh(默认约 1 秒)后才可搜。
  • filter 与 query——filter 是"是/否"且可缓存(快);query 计算相关性分数。
  • 它不是你的主存储——它是放在真实存储旁边的搜索索引,不是替代品。
tldr

Elasticsearch 把数据模型反了过来:不是"对每篇文档扫它的文本",而是建一个倒排索引("对每个词项,哪些文档有它?"),让全文搜索变成一次查表。分析器对文本分词并归一化,使匹配更灵活;BM25 按相关性排序。数据分片并跨节点做副本,一次查询扇出到所有分片再汇总。它是近实时的,区分可缓存的 filter 与计分的 query,而且是补充而非替代你的主数据库。

倒排索引

普通数据库索引(或暴力扫描)是按文档组织的:给一行,找它的文本。全文搜索需要反过来:给一个,找所有包含它的文档。倒排索引正是干这个——它是从每个不同词项到一个倒排列表(posting list)(该词出现的文档及位置)的映射。

倒排索引:词项 → 文档
文档:  1="the quick fox"   2="quick brown dog"   3="lazy fox"

倒排索引:
   "quick" → [1, 2]
   "fox"   → [1, 3]
   "brown" → [2]
   "lazy"  → [3]

搜 "quick fox" → 求交集 [1,2] ∩ [1,3] = 文档 1 排最高
                 (两词都中);文档 2、3 各中一词

搜 "quick fox" 会查每个词项的倒排列表再合并——瞬间完成,与语料规模无关,因为不需要扫文档正文。这与我们 自动补全(typeahead) 里的 trie 是同一个思路,只是推广到了文档中任意位置的任意词。

分析:分词与分析器

要让搜索"聪明",原始文本在被索引前必须归一化。分析器跑一条流水线:分词器(tokenizer)把文本切成词项,然后词项过滤器对它们变换——小写化("Fox" → "fox")、词干提取(stemming)("running"/"ran" → "run")、去停用词("the"、"a")等等。关键是,索引时和查询时跑同一个分析器,所以查询被归一化到与被索引词项一致。这就是为什么搜 "Running" 能找到含 "ran" 的文档——两者都归到词干 "run"。挑选分析器(按语言、加同义词等)是做好搜索质量的大部分功夫。

相关性打分

搜索不只是"哪些文档匹配",而是"哪个匹配得最好"。Elasticsearch 给每个匹配文档打分并按排名返回。默认算法 BM25(TF-IDF 的改进)奖励查询词出现频繁的文档(词频),但折扣那些在整个语料里都很常见的词(逆文档频率),并对文档长度做归一化。结果:用户搜的一个稀有、具体的词比常见词更值钱,一篇短小却密集含该词的文档会排在只提一次该词的长文之前。相关性排序正是搜索引擎区别于数据库过滤的特性。

文档、索引、分片与副本

数据模型是分层的。文档(document)是一个 JSON 对象。索引(index)是一组同类文档(像一张表)。因为一个索引可能很大,它被切成分片(shard)——每个分片是一个自包含的 Lucene 索引,持有一部分文档——分片分布到各节点。每个分片有一个主分片(primary)加一个或多个副本(replica),用于容错和读扩展。

跨分片 scatter-gather
索引 "logs"  →  分片 0 | 分片 1 | 分片 2   (各在一节点,+ 副本)

查询 → 协调节点(coordinating node)
       ├─ 扇出到分片 0、1、2  (并行搜索)
       ├─ 每个返回各自的本地 top-k
       └─ 汇总 + 合并 + 重排 → 全局 top-k → 客户端

一次搜索是 scatter-gather:协调节点把查询发给每个分片,各分片找出本地最佳结果,协调节点把它们合并成全局排名。注意一个关键取舍:分片数基本在建索引时就定死(改分片数意味着重建索引),所以你要根据预期数据量提前规划分片大小。

近实时搜索

Elasticsearch 是近实时(near-real-time),不是即时。被索引的文档先写入内存缓冲;一次周期性的 refresh(默认约 1 秒)把缓冲变成一个可搜的 Lucene 段(segment)。所以你刚索引的文档大约 1 秒后才可搜,而不是立刻。段是不可变的,后台会周期性合并;持久性由 translog(预写日志)提供。这个 refresh 延迟是索引效率的代价,而且可调(refresh 越快开销越大)。

query 与 filter

一个关键的性能区别:query 问"匹配得多好?"并计算相关性分数;filter 问"匹配吗,是或否?"且不打分。filter 更便宜、结果可缓存,所以结构化约束(status = "active"、日期范围、分类)应当用 filter,而自由文本那部分用计分的 query。

方面query(must)filter
问题"多相关?"(计分)"匹配吗?是/否"(不计分)
可缓存
用于自由文本相关性精确约束(状态、范围、标签)

除搜索外,Elasticsearch 还做聚合(aggregation)——对匹配文档做快速的分组/指标统计(按分类计数、按时间直方图)——这让它成为面向仪表盘和日志的实时分析引擎。

用来干什么

Elasticsearch 对比其他方案

对比关系型数据库:SQL 库是你的事实来源,有事务和连接;Elasticsearch 是一个反规范化、最终一致的搜索索引,你该库灌进去(常通过 CDC 变更数据捕获)。对比向量数据库:经典 Elasticsearch 做的是词法搜索(匹配词项),而向量库做语义搜索(用 embedding 匹配含义)——现代 Elasticsearch 也支持向量/kNN 搜索,把两者结合的混合搜索(hybrid search)越来越常见。

常见坑

总结

Elasticsearch 就是把倒排索引做成了分布式且带排序。倒排索引把搜索变成查表;分析器让匹配灵活;BM25 按相关性排序;分片/副本加 scatter-gather 让它能扩展且高可用。把它当作从主数据库灌入的搜索/分析层——精确约束用 filter,相关性用 query,并记住它是近实时的。

🎯 面试速答

为什么不直接用 SQL LIKE?LIKE 要扫每一行且无法排序;倒排索引(词项→文档)把搜索变成查表,BM25 按相关性排序。
分析器干什么?在索引和查询两端对文本分词并归一化(小写、词干、停用词),所以 "Running" 能命中 "ran"。
query 对比 filter?query 计算相关性分数(不可缓存);filter 是是/否匹配(可缓存)。精确约束用 filter,自由文本用 query。
分布式搜索怎么跑?scatter-gather:协调节点并行查询每个分片,各返回本地 top-k,协调节点合并成全局排名。
它是实时的吗?近实时——新文档在一次 refresh(约 1 秒)后可搜,不是即时;而且它是搜索索引,不是主存储。

← 上一篇
对象存储与 S3