系统设计搜索引擎倒排索引Elasticsearchsystemdesign面试

系统设计 Deep Dive:设计搜索引擎

本文基于真实候选人面经整理系统设计面试全流程。还原面试题目、解题思路与技术考察重点,覆盖Elasticsearch、Go、算法、系统设计,附详细准备策略助你高效备战。

Sam · · 12 分钟阅读

面试官真实提问

“请设计一个搜索引擎,类似 Google 或 Elasticsearch。支持全文搜索、分页、排序和高亮。”

“如何从十亿篇文档中快速找到相关结果?搜索延迟怎么控制在 100ms 以内?”

这道题考察倒排索引、分词、排序算法和分布式搜索的综合设计能力,是系统设计面试中的高频经典题目


需求澄清清单

功能需求

Must-have:

  • 全文搜索(关键词匹配)
  • 相关性排序(最相关的排在前面)
  • 分页(结果太多时分页展示)
  • 高亮(匹配关键词高亮显示)

Nice-to-have:

  • 搜索建议(Autocomplete)
  • 拼写纠错(Did you mean…)
  • 过滤与聚合(按类别/时间过滤)
  • 同义词扩展

规模估算

指标估算值
文档数10 亿篇
索引大小10TB
QPS10 万次/秒
搜索延迟P99 < 100ms
索引更新近实时(秒级)

非功能需求

  • 可用性: 99.99%
  • 延迟: < 100ms
  • 准确性: 高召回率 + 高精度排序

第 1 步:高层设计

┌─────────┐
│ 客户端   │
└────┬────┘

┌────▼──────┐
│ API GW    │
└────┬──────┘

┌────▼────────────┐
│ Query           │  查询协调器
│ Coordinator     │
└────┬────────────┘
     │ 并行查询
   ┌─┴──────────────────┐
┌──▼──┐ ┌───▼──┐ ┌───▼──┐
│Shard│ │ Shard│ │ Shard│
│  1  │ │   2  │ │   3  │  节点 A / B / C
└─────┘ └──────┘ └──────┘
   │         │         │
┌──▼──┐ ┌───▼──┐ ┌───▼──┐
│Shard│ │ Shard│ │ Shard│
│  4  │ │   5  │ │   6  │  节点 A / B / C
└─────┘ └──────┘ └──────┘

索引管道:
文档 → 分词 → 过滤 → 倒排索引构建 → 存储

核心组件:

  • Query Coordinator:接收查询、分片路由、结果合并
  • Shard:索引的分片,每个分片包含倒排索引
  • 索引管道:文档 → 分词 → 过滤 → 倒排索引

第 2 步:核心组件设计

★ 倒排索引(核心考点)

正排索引 vs 倒排索引:

正排索引(Document → Terms):
  Doc1: "the cat sat on the mat"
  Doc2: "the dog sat on the log"
  Doc3: "the cat chased the dog"

倒排索引(Term → Documents):
  "the"   → [Doc1, Doc2, Doc3]
  "cat"   → [Doc1, Doc3]
  "sat"   → [Doc1, Doc2]
  "dog"   → [Doc2, Doc3]
  "chased"→ [Doc3]

倒排索引结构:

Term


Posting List
  ├── DocID          (文档 ID)
  ├── TF             (词频)
  ├── Position       (位置,短语搜索)
  └── Payloads       (额外信息)

示例:

"cat" → [
  {doc_id: 1, tf: 1, positions: [2], payload: {}},
  {doc_id: 3, tf: 1, positions: [1], payload: {}}
]

倒排索引优化:

1. 压缩 Posting List(Varint、Skip List)
2. 字典树(Trie)存储词表
3. 内存缓存热点词
4. 磁盘存储使用 LSM-Tree 或 B-Tree

分词(Tokenization)

英文分词:

"Hello, world!" → ["hello", "world"]
"can't" → ["can", "t"] 或 ["cannot"]

中文分词:

"我喜欢机器学习" → ["我", "喜欢", "机器", "学习"]
或 → ["我", "喜欢", "机器学习"]

分词工具:
  - jieba(中文)
  - IK Analyzer
  - HanLP
  - Elastic Chinese Analysis Plugin

分词策略:

- Standard:按单词边界分词
- Whitespace:按空格分词
- Keyword:整个字符串作为一个 term
- N-gram:生成连续字符子串

排序算法

1. TF-IDF(Term Frequency - Inverse Document Frequency)

TF(t, d) = 词 t 在文档 d 中出现的次数 / 文档 d 的总词数

IDF(t) = log(总文档数 / 包含词 t 的文档数)

TF-IDF(t, d) = TF(t, d) × IDF(t)

示例:
文档总数 = 1000
词 "cat" 出现在 10 篇文档中
IDF("cat") = log(1000/10) = log(100) ≈ 4.6

文档 d 中 "cat" 出现 2 次,文档总词数 100
TF("cat", d) = 2/100 = 0.02

TF-IDF("cat", d) = 0.02 × 4.6 = 0.092

2. BM25(Best Matching 25)

BM25 是 TF-IDF 的改进版,考虑文档长度和词频饱和

BM25(d, q) = Σ IDF(q_i) × (TF(q_i, d) × (k1 + 1)) / (TF(q_i, d) + k1 × (1 - b + b × |d| / avgdl))

参数:
  k1 = 1.2 - 2.0(词频饱和参数)
  b = 0.75(文档长度归一化参数)
  |d| = 文档 d 的长度
  avgdl = 平均文档长度

优点:
  - 词频饱和(词出现多次,分数增长递减)
  - 文档长度归一化(短文档更容易获得高分)
使用 Embedding 模型将文档和查询转换为向量
计算余弦相似度:

Sim(query, doc) = (q · d) / (||q|| × ||d||)

优点:
  - 语义匹配("猫" 匹配 "猫咪")
  - 支持多语言

缺点:
  - 计算成本高
  - 需要 ANN 索引(Faiss、HNSW)

查询处理流程

1. 用户输入查询 → "机器学习教程"
2. 分词 → ["机器学习", "教程"]
3. 查询倒排索引 → 找到包含这些词的文档
4. 计算相关性分数(BM25)
5. 排序 → 返回 Top 10 结果
6. 高亮 → 标记匹配的关键词

查询扩展:

同义词:
  "机器学习" → ["机器学习", "ML", "machine learning"]

拼写纠错:
  "masine learning" → "machine learning"

查询建议:
  "机器" → ["机器学习", "机器人", "机器学习教程"]

第 3 步:扩展性与优化

[重点] 分布式搜索

分片策略:

文档

  ▼ 按文档 ID 哈希
哈希函数

  ├── Doc1 → Shard 1
  ├── Doc2 → Shard 2
  └── Doc3 → Shard 3

好处:负载均衡
问题:查询需要扫描所有分片

查询路由:

┌── Query Coordinator ──────────────────┐
│                                       │
│ 1. 接收查询                           │
│      │                               │
│      ▼                               │
│ 2. 并行查询所有分片                   │
│      │                               │
│      ▼                               │
│ 3. 合并结果 (Top-K merge)            │
│      │                               │
│      ▼                               │
│ 4. 排序、分页                        │
│      │                               │
│      ▼                               │
│ 5. 返回最终结果                      │
│                                       │
└───────────────────────────────────────┘

副本策略:

┌── 副本策略(每个分片 3 个副本)─────┐
│                                     │
│ Shard 1 → Node A, Node B, Node C    │
│ Shard 2 → Node B, Node C, Node A    │
│ Shard 3 → Node C, Node A, Node B    │
│                                     │
└─────────────────────────────────────┘

好处:高可用、负载均衡
问题:索引延迟(主分片 → 副本)

索引更新

近实时索引:

新文档 → 内存缓冲 → 每秒刷新到磁盘 → 搜索可见

好处:近实时(秒级)
问题:频繁刷新影响性能

批量索引:

新文档 → Kafka → 批量处理 → 合并到索引

好处:高性能
问题:延迟高(分钟级)

容量估算

指标计算结果
索引大小10 亿文档 × 10KB10TB
分片数10TB / 50GB per shard200 个分片
节点数200 分片 / 10 per node20 个节点
QPS10 万次/秒每节点 5000 QPS

[问答] 面试官常问 Trade-offs 与实战问答

[问] Q1:你选择 BM25 还是 TF-IDF?

候选人回答:

“我选择 BM25。它是 TF-IDF 的改进版,考虑了文档长度归一化词频饱和。TF-IDF 有一个问题:词出现多次,分数线性增长,但实际中词出现 2 次和 20 次的区别不大。BM25 解决了这个问题,而且短文档更容易获得高分,更符合用户预期。”

面试官追问:

“如果我想支持语义搜索(如’猫’匹配’猫咪’)怎么办?”

候选人回答:

“使用 Embedding + 向量搜索。将文档和查询转换为向量,计算余弦相似度。可以结合 BM25 和向量搜索,用混合排序:BM25 负责关键词匹配,向量搜索负责语义匹配。“


[问] Q2:搜索延迟怎么控制在 100ms 以内?

候选人回答:

多级优化。1)倒排索引压缩(Varint、Skip List),减少磁盘 IO;2)热点词内存缓存;3)查询并行扫描所有分片;4)结果Top-K merge,避免全量合并;5)查询缓存(相同查询直接返回缓存结果)。”

面试官追问:

“如果查询很复杂(如多条件过滤 + 聚合)怎么办?”

候选人回答:

“复杂查询会超时。我会设置查询超时时间(如 500ms),超时返回部分结果或降级。同时会预计算聚合结果,或者将复杂查询拆分为多个简单查询。“


[问] Q3:索引更新怎么实现近实时?

候选人回答:

内存缓冲 + 定期刷新。新文档先写入内存缓冲,每秒刷新到磁盘,构建倒排索引。这样搜索延迟在秒级。对于批量索引,使用 Kafka 异步处理,延迟在分钟级。”

面试官追问:

“如果索引更新频繁(如每秒 10 万次)怎么办?”

候选人回答:

批量合并。内存缓冲积累到一定大小(如 1000 条)再刷新到磁盘。同时使用 LSM-Tree 存储结构,写入是追加操作,定期合并。Elasticsearch 的 Translog 就是这样设计的。“


[问] Q4:如何处理中文分词?

候选人回答:

“使用 jiebaIK Analyzer。jieba 基于词典和统计,适合通用场景。IK Analyzer 是 Elasticsearch 的中文分词插件,支持自定义词典。对于专业领域(如医疗、法律),需要自定义词典领域分词模型。”

面试官追问:

“如果分词错误(如’机器学习’分成’机器’+‘学习’)怎么办?”

候选人回答:

自定义词典将’机器学习’作为一个词。同时使用 N-gram 分词,生成’机器学习’、‘机器学’、‘器学习’等子串,提高召回率。对于重要领域,训练领域分词模型。“


[问] Q5:搜索建议(Autocomplete)怎么实现?

候选人回答:

“使用 Trie 树N-gram 索引。Trie 树支持前缀匹配,查询速度快。N-gram 索引支持模糊匹配。同时会缓存热门建议,减少计算量。对于实时建议,使用流处理(如 Flink)实时更新建议索引。”

面试官追问:

“如果建议太多(如 100 万个可能)怎么办?”

候选人回答:

限制返回数量(如 Top 10)。使用热门度排序(搜索次数、点击率)。对于实时建议,使用近似算法(如 MinHash、SimHash)快速找到相似建议。“


进阶扩展方向

  • 语义搜索: Embedding + 向量搜索(Faiss、HNSW)
  • 个性化排序: 用户画像 + 点击率预估
  • 实时索引: Kafka + Flink 流处理
  • 多模态搜索: 文本 + 图片 + 视频

[注意] 常见踩坑点

#踩坑点解决方案
1分词错误:中文分词不准确自定义词典 + N-gram
2索引膨胀:删除文档后索引不缩小定期段合并(Segment Merge)
3查询超时:复杂查询处理慢查询超时 + 降级策略
4分片不均:某些分片负载高自定义分片路由 + 重平衡

总结

搜索引擎设计考察:

能力考察点
倒排索引核心数据结构,必须深入理解
排序算法TF-IDF、BM25、向量排序
分词中文分词、自定义词典
分布式搜索分片、副本、查询路由

[重点] 面试提示: 先讲清楚倒排索引的原理,然后讨论分词、排序、查询处理。面试官通常会追问”中文分词怎么处理”、“搜索延迟怎么优化”——准备好具体的技术方案。


推荐阅读


💡 需要面试辅导?

如果你对准备技术面试感到迷茫,或者想要个性化的面试指导和简历优化,欢迎联系 Interview Coach Pro 获取一对一辅导服务。

👉 联系我们 获取专属面试准备方案

准备好拿下下一次面试了吗?

获取针对你的目标岗位和公司的个性化辅导方案。

联系我们