搜索引擎 架构模板
代表产品:Google、Bing、Elasticsearch、电商 / 站内搜索 一句话定位:把海量文档预先「翻过来」建成倒排索引,让任意关键词都能在毫秒内找到最相关的结果并排好序。
1. 一句话定位
搜索引擎 = 一个离线慢慢「建索引」的世界 + 一个在线毫秒级「查索引」的世界。
它最核心的智慧是一句话:把工作量从「查询时」挪到「索引时」。 用户查询时之所以能在几十毫秒内从十亿文档里找出答案,是因为系统早就离线把数据翻来覆去整理好了。理解搜索引擎,就是理解这种「用预处理换实时性」的交易。
2. 业务本质:它在解决什么问题
人类面对的信息量远超能处理的极限。搜索引擎解决的是「从海量内容里,把最相关的几条快速捞到你眼前」——它把「大海捞针」变成「秒级精准命中」。
钱从哪来:搜索结果旁的广告(搜索意图 = 最值钱的广告场景)、企业 / 站内搜索的授权、电商搜索的导流与排序。
一个反直觉的事实:搜索引擎技术再强,搜不准也等于没用。 它的产品价值有一半在「相关性」这个没有标准答案、需要持续调优的软指标上。
3. 核心需求与约束
功能性需求:
- [ ] 全文检索:输入关键词,找出包含它的文档
- [ ] 相关性排序:最该看的排最前
- [ ] 分词 / 自动补全 / 拼写纠错
- [ ] 过滤与分面(按价格、类别等维度筛选 / 聚合)
- [ ] 索引更新:新内容能被搜到
非功能性需求 / 质量属性:
| 质量属性 | 目标 | 为什么对这类系统重要 |
|---|---|---|
| 查询延迟 | < 几百 ms | 搜索是即时交互,慢了就走 |
| 相关性 | 越准越好 | 产品的灵魂,直接决定好不好用 |
| 索引新鲜度 | 秒级~分钟级 | 新内容多久能被搜到 |
| 规模 | 十亿级文档 | 索引和查询都海量 |
关键约束(不可逾越的边界):
- 🔴 文档海量,查询更海量:两个负载形态完全不同,必须分开设计。
- 🔴 相关性没有标准答案:它是主观的、需要不断用数据调优的,不像「1+1=2」。
- 🔴 不能实时扫描原文:十亿文档逐个
LIKE '%关键词%'扫一遍,等到天黑也出不来——必须预先建索引。
4. 架构全景图
═══════════ 离线:建索引(慢工出细活)═══════════
┌────────┐ ┌──────────────┐ ┌──────────────┐
│ 爬虫 / │──▶│ 文档处理 │──▶│ 索引构建 │
│ 数据源 │ │ 分词/归一化 │ │ 生成倒排索引 │
└────────┘ └──────────────┘ └──────┬───────┘
▼
┌────────────────────────┐
│ 倒排索引(分片 + 副本) │
│ 词 →「含它的文档列表」 │
└────────────┬───────────┘
═══════════ 在线:查索引(快字当头)═══════════ │
┌────────┐ ┌──────────────┐ ┌──────────▼─────────┐
│ 用户 │──▶│ 查询解析 │──▶│ ① 召回:从各分片快速 │
│ 查询 │ │ 分词/纠错/补全 │ │ 捞出候选(广而粗) │
└────────┘ └──────────────┘ │ ② 精排:对候选精细打分│
▲ │ (准而细) │
└──────── 排好序的结果 ◀────┴────────────────────┘灵魂是中间那座倒排索引:它是离线世界「预先组织好的成果」,也是在线世界「毫秒响应的底气」。整个架构就是「离线建它、在线查它」两件事。
5. 组件职责
- 爬虫 / 数据接入:抓取或接收要被搜索的文档。为什么需要:索引的原料来源。
- 文档处理 / 分词:把文档拆成词项、统一大小写 / 词形、去停用词。为什么需要:搜索的基本单位是「词」,必须先把文本切成词。
- 索引构建:把「文档 → 词」翻转成「词 → 文档列表」的倒排索引。为什么需要:这是「把工作挪到索引时」的核心动作。
- 倒排索引存储(分片 + 副本):海量索引切片存储,副本扛查询量。为什么需要:十亿文档一台机器放不下、也扛不住查询。
- 查询服务:解析查询、纠错、补全,扇出到各分片召回,再归并。为什么需要:在线查询的入口与协调者。
- 排序 / 打分:决定结果先后。先用关键词匹配粗排(召回),再用更复杂的模型精排。为什么需要:相关性是产品价值所在。
6. 关键数据流
场景一:建立索引(离线,把工作提前做掉)
1. 爬虫抓到文档「架构思维很重要」
2. 分词 ──▶ [架构, 思维, 重要]
3. 写入倒排索引:
"架构" → [文档7, 文档12, 文档99, ...]
"思维" → [文档3, 文档7, ...]
(注意:存的是「词指向哪些文档」,不是「文档包含哪些词」)场景二:一次查询(在线,召回 + 精排两阶段)
1. 用户搜「架构 思维」──▶ 查询解析、分词、纠错
2. ① 召回:在各分片查倒排表,取「架构」和「思维」文档列表的交集
──▶ 几千个候选(快、广、粗)
3. ② 精排:只对这几千个候选算精细相关性分数(关键词权重、新鲜度、个性化…)
──▶ 排出前 10(慢、准,但只对少量算)
4. 归并各分片结果,返回排好序的前 10 条关键是第 2、3 步的漏斗:绝不可能对十亿文档逐个精排(算不过来),而是「先粗筛出候选,再精细排少量」。
7. 数据模型与存储选择
核心结构:倒排索引(词 → 文档列表 + 位置 + 词频);正排(文档 → 各字段值,用于展示和过滤);原文。
| 数据 | 存储类型 | 为什么 |
|---|---|---|
| 倒排索引 | 专用搜索引擎存储(分片) | 为「按词查文档」这种访问形态量身打造 |
| 正排 / 文档字段 | 列存 / KV | 取回展示字段、做分面聚合 |
| 原始文档 | 对象存储 | 大、不变、按 ID 取回 |
| 热门查询结果 | 缓存 | 头部查询高度集中,缓存收益大 |
教学点:倒排索引就是「为『按词查文档』这个问题,预先把数据组织成最趁手的形状」。 用关系型数据库的
LIKE做全文搜索,等于拿错了工具——它没有为这个问题组织过数据。
8. 关键架构决策与权衡 ⭐
决策 1:倒排索引,还是直接扫描?(搜索的立身之本)⭐
- 顺序扫描(
LIKE '%词%'):无需预处理,但每次查询都要扫全部文档,十亿级根本不可行,且无法做相关性排序。 - 倒排索引:离线把「词 → 文档」建好,查询时直接命中。
- 取向:必然选倒排。代价是索引要占额外存储、且写入(建索引)变重变慢——这正是「拿写时的代价,换读时的飞快」。
决策 2:索引怎么分片?按文档分,还是按词分?
- 按文档分(每个分片存一部分文档的完整索引):查询要扇出到所有分片再归并(scatter-gather),但写入简单、扩展自然。
- 按词分(每个分片存一部分词的全部文档列表):查询可能只问几个分片,但写入和热点词处理复杂。
- 取向:绝大多数选按文档分片,简单、易扩,代价是每次查询都得问所有分片(尾延迟受最慢分片拖累)。
决策 3:召回 + 精排的两阶段架构 ⭐
- 一步到位精排:对所有匹配文档都算复杂分数——量大就算爆了。
- 两阶段:先用轻量方式召回海量候选,再用重模型精排少量。
- 取向:几乎所有大搜索都是两阶段漏斗。这个「先粗后精」的思想,在推荐、风控里同样通用。
决策 4:索引新鲜度——多实时?
- 全量重建:简单,但新内容要等下一轮才搜得到。
- 近实时增量:新文档很快进入索引,但实现复杂、有资源开销。
- 取向:多数场景近实时(秒级~分钟级)足够,别为「绝对实时」付出不成比例的代价。
9. 规模化与瓶颈
- 第一个瓶颈:文档增长,单机索引放不下。 → 破解:索引分片(按文档切),加副本扛读。
- 第二个瓶颈:查询量增长。 → 破解:查询节点水平扩 + 副本 + 缓存头部热门查询。
- 第三个瓶颈:scatter-gather 的尾延迟(查询要等最慢的那个分片)。→ 破解:控制分片数、副本择优、超时降级(少一个分片的结果也先返回)。
- 第四个瓶颈:索引更新和查询争抢资源。 → 破解:读写分离,在副本上查询、在另一处构建,建好再切换。
10. 安全与合规要点
- 爬取合规:尊重 robots、版权与抓取频率,别把别人站爬挂。
- 查询隐私:搜索词极度暴露意图与隐私,要严格保护、脱敏、限制留存。
- 权限过滤(企业搜索关键):用户只能搜到自己有权限的文档——权限过滤要在召回阶段就生效,不能「先搜出来再隐藏」(否则可由结果数推断出存在)。
- 查询注入:对查询语法做转义,防止构造恶意查询拖垮系统或越权。
11. 常见误区 / 反模式
- ❌ 用数据库
LIKE '%x%'做全文搜索 → ✅ 倒排索引,这是全文检索的正道。 - ❌ 索引时和查询时不分离,互相拖累 → ✅ 离线建、在线查,读写分开。
- ❌ 一步到位对所有文档精排 → ✅ 召回 + 精排两阶段漏斗。
- ❌ 追求 100% 实时索引 → ✅ 近实时通常足够,别过度付费。
- ❌ 只堆技术不调相关性 → ✅ 持续用点击 / 反馈数据调排序,搜得准才有用。
- ❌ 企业搜索先搜后隐藏 → ✅ 权限过滤要在召回阶段就介入。
12. 演进路线:MVP → 成长期 → 成熟期
| 阶段 | 规模量级 | 架构长什么样 | 此时该操心什么 |
|---|---|---|---|
| MVP | 百万级文档 | 直接用现成搜索引擎,单机或少量节点,基础分词 + 关键词排序 | 先把「能搜到、还算准」跑起来 |
| 成长期 | 千万~亿级 | 索引分片 + 副本、近实时更新、自动补全 / 纠错、相关性初步调优 | 查询延迟、相关性、索引新鲜度 |
| 成熟期 | 十亿级 + 个性化 | 召回 + 机器学习精排(LTR)、查询理解、个性化、多语言、分布式大规模索引 | 相关性持续优化、规模、成本、体验 |
13. 可复用要点
- 💡 「把工作从查询时挪到写入 / 索引时」是一切读优化的本质。 缓存、物化视图、预计算、倒排索引,都是同一个思想的变体。
- 💡 召回 + 精排的两阶段漏斗,是处理「海量候选里挑少数」的通用范式。 推荐系统、风控、广告投放都用它。
- 💡 特殊的查询形态,需要专门组织的数据结构。 全文检索要倒排索引,正如地理查询要空间索引——别用通用工具硬扛特殊问题。
- 💡 scatter-gather(扇出 - 归并) 是分布式查询的常见骨架,它的代价永远是「等最慢的那个分片」。
🎯 随堂检验
- A查询时实时扫描所有文档
- B离线预先把数据建成倒排索引
- C机器够快
参考原型与延伸阅读
本模板基于以下真实开源项目与官方工程博客整理。
🔧 开源原型(可直接读代码):
- apache/lucene — 全文检索内核(Solr / Elasticsearch / OpenSearch 的底层),倒排索引 + 不可变 segment 的标准实现。
- quickwit-oss/tantivy — 受 Lucene 启发的 Rust 全文检索库,适合理解倒排索引与检索引擎底层。
📖 工程博客:
- Practical BM25 – The BM25 Algorithm (Elastic) — 逐项拆解 BM25 相关性打分(TF / IDF、k1、b)。
- Practical BM25 – How Shards Affect Relevance (Elastic) — 分片如何影响相关性评分(分布式检索的权衡)。
📌 一句话记住搜索引擎:它不是「在线翻遍所有文档」,而是「离线把数据翻来覆去整理好,在线只需查一张早就备好的索引」——所有设计都在回答『怎么用预处理,换来查询时的毫秒级精准』。