数据库/协同工具类型
Elasticsearch 深度指南
Elasticsearch 的作用定位、何时该用、核心机制(倒排索引/text vs keyword/分片/近实时)、常见问题排查(中文分词、term 查不到、深分页、映射爆炸、OOM)、难点解决方案(相关性调优、聚合、MySQL 同步、集群容量)与生产要点。
- Elasticsearch
- 全文检索
这是 Elasticsearch(ES)的深度参考文档。上手实操见 Nest 课程第二十一章。
一、基础入门(5 分钟上手)
这一节带你把 ES 单机跑起来、用 HTTP 接口存一条文档再搜一下;Nest 课程那篇讲的是在应用里连 ES 做搜索——两篇不重复。
1. 用 Docker 起一个 ES(单机 / 关安全)
docker run -d \
--name es \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "xpack.security.enabled=false" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
docker.elastic.co/elasticsearch/elasticsearch:8.17.0
discovery.type=single-node:单机模式,不用组集群。xpack.security.enabled=false:关掉安全(入门玩够用,生产必须开)。-Xms/-Xmx:限制 JVM 内存,避免吃光。docker logs -f es看到started就好了。
2. 连上它(就是发 HTTP 请求)
curl http://localhost:9200
返回一段 JSON(cluster_name、version 等)说明 ES 活着。
3. 最基础的操作(用 curl)
# 1) 存一条文档:索引 products,文档 id=1
curl -X POST http://localhost:9200/products/_doc/1 -H 'Content-Type: application/json' -d '
{ "name": "智能手机", "price": 2999, "desc": "大屏长续航" }'
# → {"_index":"products","_id":"1","result":"created"} ← result=created 说明新建成功
# 2) 再存一条
curl -X POST http://localhost:9200/products/_doc/2 -H 'Content-Type: application/json' -d '
{ "name": "智能手表", "price": 999 }'
# → {"_index":"products","_id":"2","result":"created"}
# 3) 全文检索:查带"智能"的,命中 2 条,按相关性打分排序
curl "http://localhost:9200/products/_search?q=智能"
# → {"hits":{"total":{"value":2},"hits":[ {"_id":"1","_score":...}, {"_id":"2",...} ]}}
# 4) 看某条文档(_source 就是当初存进去的内容)
curl http://localhost:9200/products/_doc/1
# → {"_index":"products","_id":"1","_source":{"name":"智能手机","price":2999,"desc":"大屏长续航"}}
没装中文分词时,上面的
q=智能是按默认分词匹配的(检索质量一般);装 IK 分词后效果才好,见第五节。
4. 字段数据类型(mapping)
ES 的「数据类型」在 mapping 里定义,常用的:
| 类型 | 用途 |
|---|---|
text | 长文本,会分词,用于全文检索(标题、正文) |
keyword | 不分词,用于精确匹配/排序/聚合(标签、状态、id) |
integer/long/float/double | 数值 |
date | 日期 |
boolean | 布尔 |
ip | IP 地址 |
object | 嵌套对象 |
nested | 对象数组(要单独查每个元素时用) |
新手最大的坑:
text和keyword搞混。全文搜用text,精确等值/排序/聚合用keyword(常设text+ 子字段.keyword两个都用)。
5. 索引与查询 DSL
建索引 + 定义 mapping(显式指定类型):
curl -X PUT http://localhost:9200/products -H 'Content-Type: application/json' -d '{
"mappings": {
"properties": {
"name": { "type": "text" },
"brand": { "type": "keyword" },
"price": { "type": "double" },
"created_at": { "type": "date" }
}
}
}'
文档增删改查:
# 写入/更新(指定 id)
curl -X POST http://localhost:9200/products/_doc/1 -H 'Content-Type: application/json' -d '{ "name": "手机", "brand": "apple", "price": 5999 }'
# 取一条
curl http://localhost:9200/products/_doc/1
# 更新部分字段
curl -X POST http://localhost:9200/products/_update/1 -H 'Content-Type: application/json' -d '{ "doc": { "price": 1999 } }'
# 删
curl -X DELETE http://localhost:9200/products/_doc/1
查询 DSL(Query JSON,掘金《重学 ES 系列(三)》):
# match:全文检索(会分词),text 字段用
curl -X POST http://localhost:9200/products/_search -H 'Content-Type: application/json' -d '{
"query": { "match": { "name": "智能 手表" } }
}'
# term:精确匹配,keyword 字段用(别拿 term 查 text!)
{ "query": { "term": { "brand": "apple" } } }
# range:范围
{ "query": { "range": { "price": { "gte": 100, "lte": 5000 } } } }
# bool:组合(must=且、should=或、filter=且不算分可缓存、must_not=非)
{ "query": { "bool": {
"must": [ { "match": { "name": "手机" } } ],
"filter": [ { "range": { "price": { "lte": 3000 } } } ]
} } }
6. 基本使用规则
text还是keyword:要分词全文检索 →text;要精确/排序/聚合 →keyword。- 中文要装 IK 分词器(版本严格对齐 ES),否则默认逐字切,检索质量差。
- 显式定义 mapping、关掉动态 mapping(
"dynamic": false),防字段数失控。 - 深分页别用
from + size(上限 10000),大数据用search_after。 - 写入后约 1 秒才搜得到(近实时 NRT),强实时场景手动
refresh或直接读 MySQL。 - MySQL 是真相源,ES 是可重建的搜索副本——别拿 ES 当主库。
7. 核心词汇速记
| 术语 | 一句话 |
|---|---|
| index | 索引,≈ MySQL 的库 + 表 |
| document | 文档,≈ 一行记录,是 JSON |
| mapping | 字段类型定义,≈ 表结构 |
| 倒排索引 | 先把文档分词建「词 → 文档」,搜得快的关键 |
| shard / replica | 分片 / 副本,分片水平拆、副本做高可用 |
| 近实时(NRT) | 写入后约 1 秒才能搜到,不是强实时 |
二、作用与定位
Elasticsearch 是全文检索 + 分布式分析引擎,基于 Lucene。核心价值:
- 全文检索快:基于倒排索引,按词查文档 O(1),比 MySQL
LIKE快几个量级。 - 相关性排序:按 BM25 算「这个文档和查询有多相关」,搜得「准」。
- 聚合分析:统计、分组、指标(类似 SQL 的 GROUP BY,但在海量数据上秒级)。
- 分布式:天然分片 + 副本,横向扩展。
一句话:「搜索」「日志检索」「海量数据分析」用 ES,结构化强事务数据还是 MySQL。
三、何时该用 / 何时用 MySQL
| 场景 | 选择 |
|---|---|
| 搜索(商品、文章、日志关键字) | ES |
| 日志聚合分析 | ES(+ Kibana = ELK) |
| 精确查询、强事务、关系 | MySQL |
数据量小、LIKE 够用 | MySQL(别为小数据上 ES) |
ES 不替代 MySQL:ES 是「可重建的搜索/分析副本」,MySQL 是真相源,数据写 MySQL 后同步到 ES。
四、核心机制速览
- 倒排索引:建索引时把文档分词,建「词 → 文档列表」的映射。搜索时先查词、直接拿到文档。
textvskeyword:text会分词(全文检索),keyword不分词(精确匹配 / 排序 / 聚合)。新手最常踩的坑就是搞混。- 分词器(analyzer):写时分词、查时分词。中文要装 IK(
ik_max_word细粒度建索引、ik_smart粗粒度查询)。 - 分片(shard)+ 副本(replica):索引水平切成多个主分片,每个主分片有副本。分片数建索引时定、不能改。
- 近实时(NRT):写入后默认 1 秒(
refresh)才搜得到——不是强实时。
一张图看清索引、主分片与副本的分布:索引被水平切成多个主分片(P0/P1),每个主分片又配一份副本(R0/R1),主分片与其副本分散落在不同节点上——某个节点挂了,副本顶上,数据不丢、服务不断。
写入与查询的流程(写入需等所有副本 ack 才算成功,查询走 scatter-gather 由协调节点汇总):
五、常见问题与排查
1. 中文搜不准 / 搜不到
原因:没装 IK 分词,或 analyzer / search_analyzer 配错。ES 自带 standard 对中文逐字切,检索质量差。
解决:装 IK 分词器(版本必须和 ES 完全一致),mapping 里 text 字段指定 ik_max_word(索引)/ ik_smart(查询)。用 _analyze API 验证分词结果。
2. term 查 text 字段查不到
原因:text 入库时已被分词(「智能手机」存成「智能」「手机」),term 精确匹配原始值查不到。
解决:精确匹配用 keyword 类型(或 text + 子字段 .keyword);全文检索用 match。
3. 深分页(from + size 很大)
原因:from + size 必须每个分片都取 from+size 条再合并排序,from 很大时(如 10000)极耗内存,ES 默认上限 from + size ≤ 10000。
解决:深分页用 search_after(游标式,类似 MySQL 的 id 游标分页)或 scroll(批量导出场景)。导大量数据用 scroll,分页展示用 search_after。
4. 映射爆炸(mapping explosion)
原因:动态 mapping 把每个新字段都自动建索引,字段数失控(日志场景几千上万个字段),元数据膨胀、性能崩。
解决:关闭动态 mapping("dynamic": false 或 strict),显式定义字段;或用 nested/flattened 类型;日志场景考虑只索引需要的字段。
5. ES 占内存 / OOM
原因:ES 基于 JVM,吃内存;聚合 / 大查询堆内存爆炸。
解决:JVM heap 设机器内存 50%(不超过 31GB),剩下的留给文件系统缓存(Lucene 靠它加速);避免超大聚合(先按时间范围缩小)。
六、难点与解决方案
1. 相关性调优
搜出来的顺序不对?调相关性:
bool.should+boost:重要字段加权(标题命中boost: 3比正文高)。function_score:用函数算分(按销量、新鲜度、自定义权重叠加)。minimum_should_match:要求至少匹配几个词。
2. 聚合性能
大范围聚合慢:
- 用
filter先缩小范围(不算分、可缓存)。 - 时间序列用
date_histogram+ 按天/小时桶。 - 超大规模分析别用 ES,导到 ClickHouse。
3. MySQL → ES 数据同步
| 方案 | 做法 | 适用 |
|---|---|---|
| 应用层双写 | 写完 MySQL 再写 ES | 简单、量小 |
| 监听 binlog(Canal/Debezium) | 伪装从库解析 binlog 推 ES | 最终一致、业务无感(推荐) |
| 定时全量/增量 | Logstash jdbc input 按 update_time | 对延迟不敏感 |
原则:MySQL 是真相源,ES 丢了能从 MySQL 全量重建。别拿 ES 当主库(不能丢的数据别只存 ES)。
4. 集群容量与分片规划
- 分片数上线时定死、不能改(只能 reindex 重建),规划好:单分片建议 30–50GB,按数据量算分片数。
- 分片别太多:每个分片有开销,小索引别拆一堆分片(默认 1 主分片即可)。
- 节点角色分离:主节点(master)、数据节点(data)、协调节点(coordinating)分开,避免大数据节点抢 master。
七、生产环境要点
- heap 设 50%(≤31GB),开内存锁(
bootstrap.memory_lock)防交换。 - 集群至少 3 节点,
replica ≥ 1,单节点别上生产。 - mapping 用显式定义,关动态 mapping 防爆炸。
- 中文装 IK,版本严格对齐 ES。
- 监控:heap 使用率、磁盘水位(>85% 只读告警)、分片数、慢查询。
- ES 当搜索副本,MySQL 当主,别反过来。
速查:常见问题对照
| 问题 | 原因 | 解决 |
|---|---|---|
| 中文搜不准 | 没装 IK / 分词器错 | 装 IK(版本对齐)、配 analyzer |
term 查不到 | text 被分词 | 用 keyword 或 match |
| 深分页超限 | from+size > 10000 | search_after / scroll |
| 字段数失控 | 动态 mapping | 关动态、显式定义 |
| ES 卡/OOM | heap 不够 / 大聚合 | heap 设 50%、缩小聚合范围 |
| 写入后搜不到 | 近实时延迟 | 等 1s 或手动 refresh |