搜索功能的架构:从数据库 LIKE 到倒排索引
雾都侦探
5 min read
判断:用 LIKE '%关键词%' 做搜索,在数据和并发都小的时候能用,一旦数据量和查询频率上来,全表扫描会把数据库拖死,且没有任何相关性排序。要支持像样的搜索,必须引入倒排索引,把"在文档里找词"反转为"在词里找文档"。
LIKE 为什么撑不住
LIKE '%手机%' 前缀有通配符,索引用不上,只能逐行扫描并做子串匹配。表到百万级,单次查询可能扫全表,且无法按"匹配度"排序——命中标题和命中正文的权重一样,结果质量差。即便建了全文索引,数据库的全文检索能力也通常弱于专门的搜索引擎,缺乏灵活的相关性调参。
正排与倒排
正排索引:文档 → 包含的词及位置。倒排索引反过来:词 → 出现在哪些文档(及位置、词频)。查询"手机 防水"时,先查两个词各自的文档列表,再取交集,得到同时包含两者的文档,速度不随文档总数线性增长,而随"命中词的项数"增长。
文档1: 防水手机壳
文档2: 手机电池
倒排:
手机 → [1, 2]
防水 → [1]
电池 → [2]
查询"手机 防水" → 交集 {1}
分词决定召回质量
中文没有空格分词,需要分词器把"防水手机壳"切成 [防水, 手机, 壳] 或 [防水, 手机壳]。分词粒度影响召回:切太细,噪声多;切太粗,漏召回。同义词(手机/移动电话)、繁简、大小写归一,都在分词与归一化阶段处理。分词器要支持自定义词典,否则行业专有词会被切碎,召回率暴跌。
相关性排序
倒排索引通常配合 TF-IDF 或 BM25 计算相关性:词在文档中出现越频繁(TF 高)、在整个语料中出现越少(IDF 高),得分越高。这样"手机"出现在标题且全文罕见词的文档排前面,比 LIKE 的"有就行"合理得多。BM25 还对超长文档做了归一,避免长文因词频虚高而霸榜。
索引的构建与更新代价
倒排索引不是免费的:构建需要遍历全量文档,更新需要增量合并。搜索引擎通常区分全量重建和近实时(NRT)增量——新文档在秒级到分钟级内可被搜到,而非实时。这意味着搜索结果可能短暂滞后于数据库,业务上要接受这个延迟窗口。
数据如何同步到索引
业务数据在数据库,搜索在索引引擎,二者需保持同步。常见做法:
- 定时全量/增量重建:简单,但有延迟且全量耗资源。
- 双写:业务写完库,同时写索引。简单但有"写库成功索引失败"的不一致。
- binlog 订阅:数据库变更经 binlog 同步到索引,解耦且可靠,是较优解。
索引本身还需分片(按文档哈希分散到多节点)和副本(保障可用与并发读),规模上来后这是必选项。
过滤、聚合与分片副本
搜索很少只做"匹配关键词",还要按类目、价格区间过滤,再对结果聚合(如各品牌命中数)。这些能力建立在倒排索引之上:过滤等价于在倒排链上做布尔运算,聚合则遍历命中文档统计。复杂度随过滤条件和数据量上升,需要在索引设计阶段就规划好需要过滤的字段是否建了索引。
规模上来后,单节点存不下全量倒排,需要分片:按文档标识哈希把索引分散到多节点,查询时扇出到各分片再归并。副本则保障高可用与读并发,一个分片主节点故障,副本顶上。分片数和副本数要按数据量与查询 QPS 估算,过多分片会带来归并开销和集群管理负担。
搜索与数据库的分工边界
简单的"按名称模糊匹配、数据量小"用数据库 LIKE 或全文索引足够,不必引入搜索引擎。只有当需要相关性排序、同义词、高并发检索时,倒排索引的复杂度才值得。先问清楚查询 QPS 和数据规模,再决定升级,避免为低频需求背上重架构。
踩坑
把搜索当数据库用:索引引擎擅长检索不擅长事务和复杂联表。搜索结果只返回 ID,详情再去数据库或缓存取,别在索引里塞全部业务字段。
索引和数据库长期不同步:同步链路断了没人知道,搜索结果越来越假。必须监控同步延迟并有重建手段。
分词器生产环境和测试不一致:测试用一种切法,上线换了一种,召回率突变。分词配置要纳入版本管理。
一上来就上重型方案:数据量小、查询少时,数据库 LIKE 或简单的全文索引够用,过早引入复杂搜索栈反增运维负担。按数据规模和查询频率决定是否升级。
1 view
0 likes
0 comments