跳转到主要内容

数据库/协同工具类型

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_nameversion 等)说明 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布尔
ipIP 地址
object嵌套对象
nested对象数组(要单独查每个元素时用)

新手最大的坑textkeyword 搞混。全文搜用 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。

四、核心机制速览

  • 倒排索引:建索引时把文档分词,建「词 → 文档列表」的映射。搜索时先查词、直接拿到文档。
  • text vs keywordtext 会分词(全文检索),keyword 不分词(精确匹配 / 排序 / 聚合)。新手最常踩的坑就是搞混。
  • 分词器(analyzer):写时分词、查时分词。中文要装 IKik_max_word 细粒度建索引、ik_smart 粗粒度查询)。
  • 分片(shard)+ 副本(replica):索引水平切成多个主分片,每个主分片有副本。分片数建索引时定、不能改。
  • 近实时(NRT):写入后默认 1 秒(refresh)才搜得到——不是强实时。

一张图看清索引、主分片与副本的分布:索引被水平切成多个主分片(P0/P1),每个主分片又配一份副本(R0/R1),主分片与其副本分散落在不同节点上——某个节点挂了,副本顶上,数据不丢、服务不断。

flowchart TB IDX["索引 products"] subgraph n1["节点 Node1"] P0["主分片 P0"] R1["副本 R1"] end subgraph n2["节点 Node2"] P1["主分片 P1"] R0["副本 R0"] end IDX -->|"切分"| P0 IDX -->|"切分"| P1 P0 -->|"同步"| R0 P1 -->|"同步"| R1

写入与查询的流程(写入需等所有副本 ack 才算成功,查询走 scatter-gather 由协调节点汇总):

sequenceDiagram autonumber participant C as 客户端 participant K as 协调节点 participant P as 主分片 participant R as 副本分片 Note over C,R: 写入流程 C->>K: 写入请求 K->>P: 路由到主分片 P->>R: 同步到副本 R-->>P: 副本成功 P-->>K: 主分片完成 K-->>C: 全部成功才 ack Note over C,R: 查询 scatter-gather C->>K: 搜索请求 K->>P: 分发到分片 K->>R: 分发到副本 P-->>K: 局部结果 R-->>K: 局部结果 K-->>C: 汇总排序返回

五、常见问题与排查

1. 中文搜不准 / 搜不到

原因:没装 IK 分词,或 analyzer / search_analyzer 配错。ES 自带 standard 对中文逐字切,检索质量差。

解决:装 IK 分词器(版本必须和 ES 完全一致),mapping 里 text 字段指定 ik_max_word(索引)/ ik_smart(查询)。用 _analyze API 验证分词结果。

2. termtext 字段查不到

原因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": falsestrict),显式定义字段;或用 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 > 10000search_after / scroll
字段数失控动态 mapping关动态、显式定义
ES 卡/OOMheap 不够 / 大聚合heap 设 50%、缩小聚合范围
写入后搜不到近实时延迟等 1s 或手动 refresh