# 搜索引擎 架构模板 > **代表产品**: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(扇出 - 归并)** 是分布式查询的常见骨架,它的代价永远是「等最慢的那个分片」。 ## 🎯 随堂检验 --- ## 参考原型与延伸阅读 > 本模板基于以下**真实开源项目**与**官方工程博客**整理。 **🔧 开源原型(可直接读代码):** - [apache/lucene](https://github.com/apache/lucene) — 全文检索内核(Solr / Elasticsearch / OpenSearch 的底层),倒排索引 + 不可变 segment 的标准实现。 - [quickwit-oss/tantivy](https://github.com/quickwit-oss/tantivy) — 受 Lucene 启发的 Rust 全文检索库,适合理解倒排索引与检索引擎底层。 **📖 工程博客:** - [Practical BM25 – The BM25 Algorithm (Elastic)](https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables) — 逐项拆解 BM25 相关性打分(TF / IDF、k1、b)。 - [Practical BM25 – How Shards Affect Relevance (Elastic)](https://www.elastic.co/blog/practical-bm25-part-1-how-shards-affect-relevance-scoring-in-elasticsearch) — 分片如何影响相关性评分(分布式检索的权衡)。 --- > 📌 一句话记住搜索引擎:**它不是「在线翻遍所有文档」,而是「离线把数据翻来覆去整理好,在线只需查一张早就备好的索引」——所有设计都在回答『怎么用预处理,换来查询时的毫秒级精准』。**