大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
搜索与 Elasticsearch 概览
这一课你将能做到
- 说清楚「数据库 LIKE 查询」在搜索场景下的三个硬伤,以及倒排索引如何解决
- 用 Docker 在本地启动一个 Elasticsearch 8.x 单节点,并通过 curl 完成第一次写入和搜索
- 说出 Elasticsearch、Kibana、Logstash、Beats 各自的角色,以及索引、文档、分片三个核心词的含义
为什么要学
本站题库将来要支持两个搜索入口:学员在搜索框里输入「Spring 事务失效」,要从几千道题的题干、选项、解析里找出相关题目;输入「倒排索引」,要能同时搜到相关题目和对应课时。这类需求用 MySQL 写就是:
SELECT * FROM question WHERE stem LIKE '%Spring 事务失效%' OR analysis LIKE '%Spring 事务失效%';
这条 SQL 有三个硬伤:① 前置通配符 %xxx 走不了 B+ 树索引,几千行还行,几十万行就是全表扫描;② 搜「事务失效」能中,搜「失效 事务」「事务不生效」就不中,用户不可能记住原文措辞;③ 结果没有相关性排序,命中和没命中一样平铺。
人话:数据库是为「精确存取」设计的——按主键取一行、按外键关联一批;Elasticsearch(下称 ES)是为「按相关性找内容」设计的——给你一段文字,它按「有多匹配」排序返回。两者不是替代关系,是分工关系。本站题库数据仍以 MySQL 为准,ES 只承担搜索,不做数据库同步(不接 binlog/CDC),题目通过应用层写入和批量导入进入 ES。
先建立图像
ES 的底层叫倒排索引(Inverted Index)。正向索引是「文档 → 词」:
文档1(题干): Spring 事务 失效 的 场景
文档2(题干): Redis 缓存 失效 与 数据库 一致性
倒排索引反过来,是「词 → 文档列表」:
词 文档列表 频率
spring → 文档1
事务 → 文档1
失效 → 文档1, 文档2
redis → 文档2
缓存 → 文档2
一致性 → 文档2
搜「缓存 失效」时,ES 拿两个词的文档列表求交集,文档 2 两个词都中、文档 1 中一个,按命中程度打分排序。这就是相关性的来源,也是搜索能做到毫秒级的原因——查表而不是逐行翻文本。
ELK 全家桶的分工:
Beats/Logstash(采集、加工)→ Elasticsearch(存储、检索、聚合)→ Kibana(界面、图表、告警)
跟着做
约定:本课全程使用 ES 8.x。8.x 默认开启安全认证(HTTPS + 账号密码),下文 curl 统一用 -k(跳过自签名证书校验)和 -u elastic:你的密码,请把密码替换成你自己的;Windows PowerShell 下 curl 如有别名问题,用 curl.exe。
步骤 1:Docker 启动单节点 ES
docker network create es-net
docker run -d --name es01 --net es-net \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "ELASTIC_PASSWORD=abcd1234" \
-e "xpack.security.http.ssl.enabled=false" \
docker.elastic.co/elasticsearch/elasticsearch:8.11.0
说明:discovery.type=single-node 表示单节点模式(免去集群发现配置);ELASTIC_PASSWORD 设置 elastic 超级用户密码;学习环境里我们关闭了 HTTP 层 SSL,所以下面用 http://。生产环境不要关 SSL。
等约 30 秒,验证:
curl -u elastic:abcd1234 http://localhost:9200
返回集群名、版本号 number: 8.11.0 即成功。
步骤 2:写入第一道题目
ES 8.x 中索引(index)相当于数据库里的「表」,文档(document)相当于「一行」,而且不再有 type 的概念。直接写入:
curl -u elastic:abcd1234 -X PUT "http://localhost:9200/questions/_doc/1" -H "Content-Type: application/json" -d'
{
"stem": "Spring 事务在什么情况下会失效?",
"answer": "自调用、异常被捕获、非 public 方法等场景",
"tags": ["spring", "事务"],
"difficulty": 2,
"lessonTitle": "Spring 事务管理",
"updatedAt": "2026-10-01"
}'
返回 "result":"created" 表示写入成功。文档 ID 我们手动指定为 1,与题库题目 ID 保持一致。
步骤 3:做第一次全文搜索
curl -u elastic:abcd1234 -X GET "http://localhost:9200/questions/_search" -H "Content-Type: application/json" -d'
{
"query": {
"match": {
"stem": "事务失效"
}
}
}'
注意:搜的是「事务失效」,原文是「事务在什么情况下会失效」,ES 依然命中——因为写入和搜索时都会经过分词,「事务失效」被拆成「事务」「失效」两个词去倒排索引里查。响应里 hits.total.value 是命中数,每条 hit 的 _score 是相关性得分。
步骤 4:观察「近实时」而不是「实时」
立刻再 PUT 一道新题,马上搜,可能搜不到;等约 1 秒再搜就能搜到。ES 写入先进内存缓冲,每秒 refresh 一次生成新的只读段(segment)后才可被搜索,这叫近实时(NRT)。所以 ES 不适合「写入后立刻要求读到」的强一致扣库存场景。
讲清楚要点
- 倒排索引是 ES 的全部根基:分词建词表,搜索查词表求交集并打分。后面学的 mapping、analyzer、query、向量,本质都是在控制「倒排索引怎么建、怎么查」。
- ES 是面向文档的:数据单元是 JSON 文档,写入即解析 JSON、建立索引;没有外键、没有 JOIN(有受限的 join 类型,几乎不用),冗余字段是常态。题目里冗余
lessonTitle就是故意的,避免搜题时再关联课时。 - 8.x 的关键变化:默认开启安全(HTTPS、认证、授权);彻底移除 type;新增原生向量搜索 kNN;推荐用新版 Java API Client 替代已停止维护的 RestHighLevelClient。
- ES 不替代数据库:它不擅长频繁更新单字段(每次更新是整文档标记删除+重建)、不擅长事务。本站策略是 MySQL 为权威数据源,ES 只做搜索,通过应用层双写和批量导入维护,不做数据库同步链路。
你可能会懵的地方
-
ES 和 Solr 有什么区别?选哪个? 二者都基于 Lucene。ES 开箱即用、分布式扩展、生态(Kibana)完善,是当前主流;Solr 在传统站内搜索、与 Hadoop 结合上有历史积累。新项目默认选 ES,不需要纠结。
-
Lucene 和 ES 又是什么关系? Lucene 是 Apache 的单机搜索库(jar 包),提供倒排索引内核,但用起来很底层。ES 是建在 Lucene 之上的分布式服务:分片、副本、REST API、集群管理都是 ES 加的层。一个 ES 分片底层就是一个 Lucene 实例。
-
索引到底是名词还是动词? 两个都是。名词 index 是文档的集合(类似表);动词 indexing 是「把文档写入并建立倒排索引」这个动作。中文资料里两种用法都常见,靠上下文区分。
-
为什么写入成功了立刻搜不到? refresh 机制,默认每 1 秒一次。写入后立即按 ID 用 GET 是能读到的(GET 走 translog 实时读),但搜索要等 refresh。也可手动
POST /questions/_refresh。 -
数据不做数据库同步,两边不一致怎么办? 本站数据量小、变更低频,题目在后台保存时应用层同步写 ES;再用每日凌晨的批量任务按
updatedAt增量重建兜底。不引入 binlog/CDC 是有意控制复杂度,第 12 课会给出完整方案。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 用 ES 当主库存订单、余额 | 无事务、更新昂贵、可能丢数据 | MySQL 存权威数据,ES 只做搜索副本 |
用 LIKE '%词%' 的思路要求 ES 子串匹配 | match 查的是分词结果,不是任意子串 | 子串需求用 ngram,精确匹配用 keyword |
| 学习版就关认证暴露 9200 到公网 | 历史上大量 ES 被拖库、勒索 | 至少绑定内网,生产必须开安全功能 |
| 拿 7.x 教程命令在 8.x 上用 | SSL、type、客户端都变了 | 认准 8.x 文档,curl 带认证 |
| 期待写入后毫秒级可搜 | refresh 默认 1 秒 | 接受近实时,或必要时手动 refresh |
卡点急救
- Docker 容器反复重启或退出:ES 吃内存,Docker Desktop 默认内存太小。在 Docker 设置里把内存调到 4GB 以上;或加
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"限制堆内存。 - curl 报
Connection refused:容器没起来或还在启动中。docker logs es01看日志,看到started字样再访问;Windows 上确认端口映射和防火墙。 - curl 报 401 Unauthorized:密码错或没带
-u。确认ELASTIC_PASSWORD设置;若创建容器时没设密码,可用docker exec -it es01 bin/elasticsearch-reset-password -u elastic重置。
面试常问
-
为什么不用 MySQL 直接做搜索? 三点:前置通配符无法利用索引导致全表扫描;无法对文本分词,换个词序或同义词就搜不到;没有相关性打分。ES 基于倒排索引解决这三点,还天然支持分布式水平扩展。可以补一句:简单前缀检索(如搜索框联想)MySQL 全文索引也能做,数据量小不必上 ES,技术选型看规模。
-
说一下什么是倒排索引。 正向索引记录每个文档包含哪些词;倒排索引反过来记录每个词出现在哪些文档中,通常包含词项字典、文档 ID 列表、词频、位置等信息。搜索时先查词项字典拿到文档列表,求交集并按 BM25 等模型算相关性得分,所以不需要扫描全部文档。
-
ES 写入后为什么不是实时可搜? 写入先进内存缓冲和 translog,默认每 1 秒 refresh,缓冲数据写成一个新的 Lucene 段后才可搜索,因此称为近实时(NRT)。refresh 越频繁实时性越好但段越多、开销越大,这是实时性与吞吐的权衡。
自测三问
- 倒排索引中「词 → 文档列表」的结构,怎样解释搜「缓存 失效」时的相关性排序?
- 索引、文档、分片三个词分别对应 MySQL 世界里的什么概念?哪个其实没有严格对应?
- 本站题库为什么让 ES 与 MySQL 并存,而不是把题目只存 ES?
小结一句话
ES 是建在 Lucene 倒排索引之上的分布式搜索服务,解决数据库「搜不快、搜不准、无排序」的问题,与 MySQL 分工并存而不是互相替代。
延伸
- Elasticsearch Reference 8.x
- Install Elasticsearch with Docker
- 下一课:集群架构——节点角色、主分片与副本、脑裂是怎么被防住的。