一、搜索系统概述:高并发搜索的挑战、搜索引擎工作原理、倒排索引基础
1.1 高并发搜索到底难在哪?
做搜索系统这么多年,我经常被问到同一个问题:「不就是个搜索框吗?能有多难?」
嗯,这话听着简单。但你想想看,当用户敲下回车的那一瞬间,系统要在几毫秒内从几十亿条数据里找到最相关的结果。而且,同一秒可能有几万个人同时在敲回车。
这就是高并发搜索的挑战。说白了,三个核心矛盾:
- 数据量大——动不动几十亿条,全量扫描是不可能的
- 响应要快——用户等不了,超过500ms用户就流失了
- 并发高——双十一、大促期间,QPS轻松破万
核心矛盾: 数据量越大,查询越慢;并发越高,资源越吃紧。搜索引擎要在这三者之间找到平衡点。
我在项目中遇到过最极端的情况:某电商大促期间,搜索QPS飙到8万,结果倒排索引的合并操作直接把CPU打满,响应时间从20ms暴涨到2秒。嗯,那天晚上我基本没睡。
1.2 搜索引擎工作原理——别把它想得太神秘
搜索引擎到底是怎么工作的?我习惯把它拆成两个阶段:建索引和查索引。
1.2.1 建索引阶段
你想想看,如果让你从一本没目录的书里找一句话,你得一页页翻。搜索引擎也一样,它不能每次查询都去翻原始数据。所以它提前建好一个「目录」——这就是索引。
建索引的流程大致如下:
- 爬取/采集——拿到原始文档(网页、商品、文章等)
- 分词——把文档拆成一个个词条
- 去停用词——去掉「的」「了」「是」这类高频无意义词
- 构建倒排索引——记录每个词出现在哪些文档里
- 写入磁盘——持久化,供查询使用
我的经验: 分词这一步特别关键。中文分词比英文复杂得多。我曾经因为分词词典没更新,导致「苹果手机」被分成了「苹果」和「手机」,结果用户搜「iPhone」完全搜不到。后来我加了一个同义词映射表,才解决这个问题。
1.2.2 查询阶段
用户输入关键词后,系统做这几件事:
- 对查询词做同样的分词处理
- 去倒排索引里找到对应的文档ID列表
- 合并多个词的查询结果(比如搜「高并发 搜索」,要取交集)
- 按相关性打分排序
- 返回Top N结果
整个过程必须在几十毫秒内完成。你想想看,这背后有多少优化要做。
1.3 倒排索引基础——搜索引擎的「灵魂」
倒排索引,说白了就是「词 → 文档」的映射。跟书的目录一个道理。
举个例子,假设有三篇文档:
| 文档ID | 内容 |
|---|---|
| 1 | 高并发搜索系统设计 |
| 2 | 搜索引擎工作原理 |
| 3 | 倒排索引实战 |
经过分词后,倒排索引长这样:
| 词条 | 文档ID列表 |
|---|---|
| 高并发 | [1] |
| 搜索 | [1, 2] |
| 搜索引擎 | [2] |
| 倒排索引 | [3] |
| 实战 | [3] |
当用户搜「搜索」时,系统直接去倒排索引里查,发现文档1和文档2都包含这个词。返回这两个文档即可。不需要扫描全部数据。
关键点: 倒排索引把「文档→词」的正向关系,变成了「词→文档」的反向关系。查询时直接定位,时间复杂度从O(n)降到了O(1)。这就是搜索引擎能秒级响应的根本原因。
1.3.1 倒排索引的存储结构
实际工程中,倒排索引不是简单存个列表就完事了。我习惯把它分成两部分:
- 词典(Term Dictionary)——所有词条的集合,通常用跳表或哈希表存储
- 倒排列表(Posting List)——每个词对应的文档ID列表,用压缩算法存储
为什么要压缩?你想想看,几十亿条数据,每个词对应的文档ID可能上千万。不压缩的话,内存根本扛不住。
避坑指南: 我曾经在项目里直接用int存文档ID,结果索引文件比原始数据还大。后来改用差分编码+变长编码,压缩率达到了1:5。嗯,内存一下子就省下来了。
1.3.2 倒排索引的合并操作
多词查询时,需要合并多个倒排列表。比如搜「高并发 搜索」,要取两个列表的交集。
最简单的做法是双指针遍历:
// 伪代码:两个有序列表求交集
List<Integer> intersect(List<Integer> list1, List<Integer> list2) {
int i = 0, j = 0;
List<Integer> result = new ArrayList<>();
while (i < list1.size() && j < list2.size()) {
if (list1.get(i) == list2.get(j)) {
result.add(list1.get(i));
i++; j++;
} else if (list1.get(i) < list2.get(j)) {
i++;
} else {
j++;
}
}
return result;
}
这个算法的时间复杂度是O(m+n)。但实际场景中,两个列表可能一个很大一个很小。我习惯先拿小的列表去大的列表里做二分查找,效率更高。
注意: 倒排列表的合并是高并发场景下的性能瓶颈。如果合并策略选不好,QPI一上来就崩。我建议用跳表(Skip List)来加速合并,实测性能提升3倍以上。
1.4 高并发搜索的常见坑
做搜索系统这些年,我踩过的坑不少。挑几个典型的说说:
- 缓存穿透——热门词大家都在搜,缓存扛不住。我后来加了布隆过滤器,先过滤掉不存在的词。
- 索引更新延迟——新数据写进去了,但索引还没更新,用户搜不到。我建议用近实时索引,秒级可见。
- 排序计算太慢——相关性打分涉及TF-IDF、BM25等算法,计算量很大。我习惯把打分结果提前算好,存到索引里。
嗯,这些坑后面章节会详细讲。今天先有个概念就行。
1.5 小结
这一章我们聊了高并发搜索的三大挑战、搜索引擎的建索引和查询流程,以及倒排索引的核心原理。说白了,搜索引擎就是「空间换时间」的典型代表——提前建好索引,查询时直接定位。
下一章,我会带你手写一个迷你搜索引擎,把今天讲的理论落地成代码。到时候你就知道,倒排索引到底是怎么工作的了。
一句话总结: 倒排索引是搜索引擎的基石。理解了它,你就理解了搜索系统的一半。