Skip to content

Elasticsearch 商业场景训练营

Elasticsearch 不能只学会写几个 _search。商业项目里真正要会的是:能把 MySQL 表设计成 ES 搜索文档,能解释为什么 ES 查询快,能解释为什么写入成功后不是立刻可搜,能处理 MySQL 和 ES 数据不一致,能在 ES 更新失败时补偿,能重建索引并平滑切换,能排查搜不到、搜不准、查询慢和写入慢。

训练目标:用商品搜索、订单检索、医疗资产搜索、MySQL 同步 ES、索引重建、查询排查这些场景,把 ES 原理变成可执行的工程能力。

训练总流程

mermaid
flowchart TD
    A["确定搜索场景"] --> B["设计搜索文档"]
    B --> C["设计 Mapping"]
    C --> D["写入测试数据"]
    D --> E["编写 Query DSL"]
    E --> F["解释倒排索引和 doc values"]
    F --> G["处理 MySQL 同步和补偿"]
    G --> H["重建索引和别名切换"]
    H --> I["排查搜不到/搜不准/查询慢"]

训练一:商品搜索文档设计

场景

商品搜索页要支持关键词搜索、品牌筛选、类目筛选、价格排序、销量排序、标签检索和高亮。MySQL 里这些数据可能分散在多张表,但 ES 查询时不适合临时 Join,所以要设计成面向搜索的一份文档。

文档 Demo

json
{
  "id": 1001,
  "productName": "无线蓝牙降噪耳机 Pro",
  "brandId": 20,
  "brandName": "SoundMax",
  "categoryId": 300,
  "categoryName": "耳机",
  "tags": ["蓝牙耳机", "主动降噪", "官方旗舰"],
  "price": 29900,
  "stockStatus": "IN_STOCK",
  "saleCount": 5821,
  "updatedAt": "2026-07-01T10:00:00"
}

为什么不能照搬 MySQL 表

MySQL 思路ES 思路原因
查询时 Join 品牌表写入文档时冗余 brandNameES 不擅长运行时 Join
商品名一个字段即可productNametext,必要时加 .keyword既要全文搜索,也可能排序/聚合
状态用字符串随便查stockStatuskeyword精确过滤不需要分词
价格金额直接文本price 用数字类型排序和范围过滤需要数值索引

训练二:Mapping 设计

bash
curl -X PUT "http://localhost:9200/product_search_v1" \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1,
      "refresh_interval": "1s"
    },
    "mappings": {
      "properties": {
        "id": { "type": "long" },
        "productName": {
          "type": "text",
          "analyzer": "standard",
          "fields": {
            "keyword": { "type": "keyword" }
          }
        },
        "brandId": { "type": "long" },
        "brandName": { "type": "keyword" },
        "categoryId": { "type": "long" },
        "categoryName": { "type": "keyword" },
        "tags": { "type": "keyword" },
        "price": { "type": "long" },
        "stockStatus": { "type": "keyword" },
        "saleCount": { "type": "long" },
        "updatedAt": { "type": "date" }
      }
    }
  }'

原理解释

text 会分词,用于全文检索;keyword 不分词,用于精确过滤、聚合和排序;数字和日期字段使用专门的数据结构,适合范围查询和排序。

mermaid
flowchart TD
    A["字段进入 ES"] --> B{"字段类型"}
    B -- "text" --> C["Analyzer 分词"]
    C --> D["建立倒排索引"]
    B -- "keyword" --> E["整体作为一个词项"]
    E --> F["精确过滤/聚合"]
    B -- "number/date" --> G["范围查询和排序结构"]

不这样会怎样

错误后果
productName 只用 keyword搜“蓝牙耳机”可能搜不到包含部分词的商品
brandNametext 聚合分词后聚合结果异常
金额用字符串范围查询和排序可能不符合数值语义
Mapping 错了再改字段类型通常需要重建索引

训练三:写入和近实时搜索

写入文档

bash
curl -X POST "http://localhost:9200/product_search_v1/_doc/1001" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 1001,
    "productName": "无线蓝牙降噪耳机 Pro",
    "brandId": 20,
    "brandName": "SoundMax",
    "categoryId": 300,
    "categoryName": "耳机",
    "tags": ["蓝牙耳机", "主动降噪"],
    "price": 29900,
    "stockStatus": "IN_STOCK",
    "saleCount": 5821,
    "updatedAt": "2026-07-01T10:00:00"
  }'

马上搜索:

bash
curl -X GET "http://localhost:9200/product_search_v1/_search" \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "match": {
        "productName": "降噪耳机"
      }
    }
  }'

为什么叫近实时

ES 写入成功后,文档先进入内存 buffer 和 translog。只有 refresh 后,新的 segment 被打开用于搜索,文档才对搜索可见。默认 refresh_interval 常见为 1 秒,所以 ES 是 Near Real Time,不是强实时。

mermaid
flowchart TD
    A["写入文档"] --> B["写内存 buffer"]
    B --> C["写 translog"]
    C --> D["返回写入成功"]
    D --> E["refresh"]
    E --> F["生成可搜索 segment"]
    F --> G["搜索可见"]

业务后果

支付、库存、订单状态不能依赖 ES 立即可见。搜索列表可以接受短暂延迟;关键交易必须回源 MySQL 或业务服务校验。

训练四:为什么 ES 查询快

关键词查询

bash
curl -X GET "http://localhost:9200/product_search_v1/_search" \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "bool": {
        "must": [
          { "match": { "productName": "蓝牙降噪" } }
        ],
        "filter": [
          { "term": { "brandName": "SoundMax" } },
          { "range": { "price": { "lte": 50000 } } }
        ]
      }
    },
    "sort": [
      { "saleCount": "desc" }
    ],
    "from": 0,
    "size": 20
  }'

原理

ES 快不是因为“比 MySQL 高级”,而是搜索模型不同:

  1. 全文检索用倒排索引,从词项快速找到文档 ID。
  2. filter 不参与评分,适合缓存和快速过滤。
  3. keyword/date/number 有适合过滤、排序、聚合的数据结构。
  4. 分片并行查询,协调节点合并 TopN。
  5. Lucene segment 不可变,读取结构更稳定。
mermaid
flowchart TD
    A["关键词 蓝牙"] --> B["倒排索引"]
    B --> C["找到候选 docId"]
    C --> D["filter 过滤品牌和价格"]
    D --> E["每个分片计算 TopN"]
    E --> F["协调节点合并排序"]
    F --> G["Fetch 原文返回"]

训练五:term 和 match 为什么经常用错

错误示例

json
{
  "query": {
    "term": {
      "productName": "蓝牙耳机"
    }
  }
}

如果 productNametext,索引时已经被分词,term 不会对查询词再分词,可能匹配不到。

正确理解

查询是否分词适合字段
match会分析查询文本text
term不分析,精确匹配keyword、数字、枚举
range范围数字、日期
bool filter不评分过滤状态、品牌、价格

训练六:MySQL 同步 ES

推荐链路

MySQL 是事实源,ES 是搜索视图。常用同步方式包括 Outbox、本地消息表、MQ、binlog CDC。

mermaid
flowchart TD
    A["业务写 MySQL"] --> B["事务提交"]
    B --> C["Outbox / Binlog / MQ"]
    C --> D["同步服务"]
    D --> E["组装 ES 文档"]
    E --> F["写入 ES"]
    F --> G{"成功"}
    G -- "是" --> H["记录同步成功"]
    G -- "否" --> I["重试 / 异常表 / 死信"]

同步消息 Demo

json
{
  "eventId": "evt-product-1001-17",
  "entityType": "PRODUCT",
  "entityId": 1001,
  "version": 17,
  "eventType": "PRODUCT_UPDATED",
  "occurredAt": "2026-07-06T10:00:00"
}

同步服务不要完全相信消息里的商品内容,可靠做法是根据 entityId 回查 MySQL,组装最新搜索文档,再写 ES。这样可以减少消息乱序导致旧数据覆盖新数据。

训练七:ES 更新失败怎么办

错误做法

业务更新 MySQL 成功,调用 ES 失败,然后直接忽略。

后果:搜索页长期展示旧数据,用户点进去发现详情不一致。

正确补偿

mermaid
flowchart TD
    A["ES 写入失败"] --> B["记录失败事件"]
    B --> C["进入重试队列"]
    C --> D{"重试成功"}
    D -- "是" --> E["标记成功"]
    D -- "否" --> F["进入异常表/死信"]
    F --> G["定时补偿从 MySQL 重建文档"]
    G --> H["人工告警和对账"]

异常表 Demo

sql
create table es_sync_fail_event (
  id bigint primary key auto_increment,
  event_id varchar(64) not null,
  entity_type varchar(32) not null,
  entity_id bigint not null,
  version bigint not null,
  error_message varchar(1024) not null,
  retry_count int not null default 0,
  next_retry_time datetime not null,
  status varchar(20) not null,
  created_at datetime not null,
  updated_at datetime not null,
  unique key uk_event_id (event_id),
  key idx_status_retry_time (status, next_retry_time)
);

标准回答

text
ES 更新失败不能回滚已经提交的 MySQL,因为 MySQL 是事实源,ES 是搜索视图。正确做法是记录失败事件,进入重试或死信,后续补偿任务从 MySQL 重新查询最新数据并写入 ES。必要时做版本号幂等,避免旧消息覆盖新文档,并通过对账任务发现 MySQL 和 ES 不一致。

训练八:乱序和版本幂等

问题

商品先更新到版本 18,又因为网络抖动收到了版本 17 的旧消息。如果直接写 ES,旧数据会覆盖新数据。

解决思路

文档中保存业务版本:

json
{
  "id": 1001,
  "productName": "无线蓝牙降噪耳机 Pro",
  "version": 18,
  "updatedAt": "2026-07-06T10:00:00"
}

同步时只允许新版本覆盖旧版本。工程上可以:

  1. 同步服务回查 MySQL 最新数据再写。
  2. 使用外部版本控制。
  3. 在业务侧记录同步版本。
  4. 对账发现旧版本文档并重刷。
mermaid
flowchart TD
    A["收到同步事件"] --> B["读取 event version"]
    B --> C["查询 MySQL 最新 version"]
    C --> D{"事件是否过期"}
    D -- "是" --> E["丢弃旧事件"]
    D -- "否" --> F["组装最新文档写 ES"]

训练九:重建索引和别名切换

Mapping 字段类型错了,通常不能直接把已有字段从 text 改成 keyword 或从字符串改成数字,需要重建索引。

别名流程

mermaid
flowchart TD
    A["当前别名 product_search"] --> B["指向 product_search_v1"]
    B --> C["创建 product_search_v2"]
    C --> D["全量重建数据"]
    D --> E["增量同步双写或追平"]
    E --> F["校验数量和抽样"]
    F --> G["别名切到 v2"]
    G --> H["观察后删除 v1"]

命令示例

bash
curl -X POST "http://localhost:9200/_aliases" \
  -H "Content-Type: application/json" \
  -d '{
    "actions": [
      { "remove": { "index": "product_search_v1", "alias": "product_search" } },
      { "add": { "index": "product_search_v2", "alias": "product_search" } }
    ]
  }'

应用访问别名 product_search,不要直接写死物理索引 product_search_v1

训练十:查询慢排查

常见慢查询

问题表现优化
深分页from 很大search_after
通配符前缀*abc改索引设计、ngram
聚合范围大CPU/内存高缩小时间范围、预聚合
高亮字段大Fetch 慢控制字段长度
分片太多协调开销大合理分片
排序字段无 doc values排序慢Mapping 修正并重建

排查流程

mermaid
flowchart TD
    A["查询慢"] --> B["看 DSL"]
    B --> C["看 took 和 profile"]
    C --> D{"Query 慢还是 Fetch 慢"}
    D -- "Query 慢" --> E["看分词、filter、聚合、分片"]
    D -- "Fetch 慢" --> F["看返回字段、高亮、大文档"]
    E --> G["看集群 CPU/GC/磁盘"]
    F --> G
    G --> H["调整 Mapping / DSL / 分片 / 业务分页"]

最终验收清单

做完这页后,你应该能回答:

  1. ES 为什么不是 MySQL 的替代品?
  2. 搜索文档为什么不能照搬 MySQL 表?
  3. textkeyword 怎么选?
  4. ES 为什么叫近实时搜索?
  5. ES 为什么查询快?
  6. termtext 为什么可能搜不到?
  7. MySQL 到 ES 同步链路怎么设计?
  8. ES 更新失败怎么办?
  9. 乱序消息怎么避免旧数据覆盖新数据?
  10. 为什么 Mapping 错了常要重建索引?
  11. 别名切换为什么能平滑重建索引?
  12. 深分页、聚合、高亮、Fetch 慢分别怎么排查?

关联知识点

知识点入口
ES 主线从零到生产级掌握
写入原理写入、Refresh 与 Segment 全过程
查询原理查询 Query 与 Fetch 全过程
倒排索引和评分倒排索引、分词与 BM25
Mapping 和查询Mapping 与查询
同步与重建同步与重建索引
MySQL 与 ES 一致性MySQL 与 ES 数据一致性
性能排查性能优化与排查
面试ES 面试题