第 32 章 / 共 57 章 · 约 3 分钟
关键技术:Elasticsearch
Elasticsearch 是建立在 Lucene 之上的分布式搜索引擎。它的核心是倒排索引:先把文本切成词,再记录每个词出现在哪些文档里,所以“包含这些词、按相关性排序”的查询很快。系统设计里一旦出现全文搜索、多条件筛选加排序、日志检索,基本都会把它放在主数据库旁边,作为一份可以重建的搜索副本。
1. 核心模型
- 文档和索引:数据是 JSON 文档,存放在索引(index)里;mapping 定义每个字段怎么分词、怎么索引。
- 倒排索引和分析器:分析器(analyzer)负责分词、转小写、词干还原、同义词。中文需要专门的分词器。
- 相关性打分:默认用 BM25,结合词频和逆文档频率给文档打分,可以叠加业务权重(销量、时间衰减)。
- 分片和副本:一个索引拆成若干主分片,每个主分片有副本。主分片数在建索引时确定,之后要改只能 split/shrink 或重建索引。
- 近实时:写入的文档要等下一次 refresh(默认 1 秒)才能被搜到,所以写完立刻搜可能找不到。
- 聚合:terms、range、date histogram 等,用来做分面筛选(facet)和统计。
2. 什么时候用
- 全文搜索:商品、帖子、文档的关键词搜索,要求模糊匹配、同义词和相关性排序。
- 多条件筛选加排序:“价格 100–300、四星以上、按销量排序”,再加每个筛选项的计数。
- 自动补全:edge n-gram 或 completion suggester;规模很大时也会自己预计算前缀结果,见搜索自动补全案例。
- 日志和可观测性:按时间写入大量日志,按关键字和字段检索、聚合。
- 地理搜索:
geo_distance查询附近的店铺或活动。
3. 什么时候不适合
- 当唯一的数据来源:没有多文档事务,写入后不是立刻可见,集群出问题时要能从主库重建。订单、账户这类数据的权威副本应该放在 PostgreSQL 之类的数据库里。
- 简单的按 key 读取:按 id 查用户,数据库或缓存更快更便宜。
- 同一文档的高频更新:每次更新都是标记删除旧文档、写入新文档,计数器这类字段频繁更新会产生大量合并开销。
- 强一致读:刚写入就必须读到的场景,要么读主库,要么在写入时强制 refresh(代价高)。
4. 关键限制和数量级
| 维度 | 要记住的事 |
|---|---|
| 分片大小 | 常见建议是单个分片 10–50 GB;分片太多会让集群元数据和查询开销变大 |
| 可见延迟 | refresh 默认 1 秒;批量导入时可以调大,换取写入吞吐 |
| 深分页 | from + size 默认最多 10000 条(index.max_result_window);更深的翻页用 search_after |
| 查询延迟 | 常见查询几十毫秒;复杂聚合和通配符查询会慢很多 |
| 写入 | 用 bulk API 批量写;单条写入吞吐低很多 |
5. 面试中常被追问的点
- 索引和主库怎么保持同步? 不要在业务代码里“写库再写 ES”双写,两步中间失败就会不一致。更稳的做法是 CDC:数据库变更日志 → Kafka → 索引服务批量写入 ES;索引服务可以重放,出问题还能全量重建。
- 为什么刚发的帖子搜不到? 近实时,要等 refresh;产品上可以把新内容先直接展示在作者自己的结果里。
- 数据量增长后分片不够了怎么办? 用别名(alias)指向索引,按时间滚动建新索引(日志场景),或者建一个分片更多的新索引,重建完成后切换别名。
- 相关性怎么调? 字段加权(标题比正文重要)、function score 叠加销量和新鲜度、离线评估点击率。
- 热点和慢查询? 避免前置通配符、限制聚合的桶数、对高频查询做结果缓存。
6. 和其他技术的取舍
| 选项 | 适合 | 代价 |
|---|---|---|
| Elasticsearch / OpenSearch | 全文搜索、分面、日志分析 | 不是权威数据源;集群运维和容量规划要花精力 |
| PostgreSQL 全文检索 | 数据量不大、搜索需求简单,少一个组件 | 相关性和扩展性有限 |
| 预计算的前缀索引 / Trie | 超高 QPS 的自动补全 | 只适合固定的查询形态 |
| 向量数据库 / kNN | 语义相似搜索 | 与关键词搜索互补,常常两者混合使用 |
7. 本站案例
- 案例 12:票务抢购:活动搜索走 Elasticsearch,订座走数据库。
- 案例 11:搜索自动补全:自己设计预计算的“前缀 → Top-K”映射,并和 Elasticsearch 现成的 completion suggester 对比。