Skip to content

ES(Elasticsearch)学习笔记 ​

黑马 SpringCloud 微服务课程(黑马商城)Elasticsearch 部分完整笔记。ES 是分布式搜索引擎,核心是倒排索引,用于商品搜索、日志检索等"搜索"场景(MySQL 的 LIKE 在大数据量下很慢)。

1. 初识ES-介绍与倒排索引 ​

【重点要点】

  • ES 是什么:基于 Lucene 的分布式搜索引擎,对外提供 RESTful API,部署简单、扩展容易,搜索快(毫秒级)。
  • 核心原理 倒排索引:
    • 正排索引(MySQL 思路):文档 → 词条(查某词要遍历所有文档)。
    • 倒排索引(ES 思路):词条 → 文档,先对文档内容分词建立索引,查"手机"直接找到包含它的文档(像字典的拼音索引)。
  • 与 MySQL 对比:索引 Index ↔ 数据库;类型(7.x 已废弃)↔ 表;文档 Document ↔ 行;字段 Field ↔ 列。
  • 安装:Docker 跑 ES(注意内存 -e ES_JAVA_OPTS=-Xms512m -Xmx512m)+ Kibana(可视化控制台,Dev Tools 里写 DSL)。
  • 易错点:ES 默认占用大量内存,开发机要限制;7.x 以上没有"类型"概念,索引下直接是文档;Kibana 与 ES 版本要一致。
bash
# Docker 启动 ES + Kibana(版本一致,如 7.12.1)
docker run -d --name es -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  elasticsearch:7.12.1
docker run -d --name kibana -p 5601:5601 \
  -e "ELASTICSEARCH_HOSTS=http://<宿主机IP>:9200" kibana:7.12.1

2. IK分词器与自定义分词 ​

【重点要点】

  • 分词(analysis):ES 建立倒排索引前把文本拆成词条(token)。默认 Standard 分词器对中文按单字拆("黑马程序员" 拆成 黑/马/程/序/员,搜索体验差)。
  • IK 分词器:中文分词插件,两种模式:
    • ik_max_word:最细粒度拆分("中华人民共和国" → 拆出所有可能的词),建立索引用。
    • ik_smart:最粗粒度(只拆成最合理的词),搜索时用(字段配 search_analyzer)。
  • 安装:下载对应 ES 版本的 ik 插件 zip,放插件目录解压,重启 ES。
  • 自定义词典:IK 支持扩展词典(IKAnalyzer.cfg.xml 配置 ext_dict,如"黑马"、"奥力给"这些新词,不配置会被错误拆分)。
  • 易错点:IK 插件版本必须与 ES 版本严格一致(否则装不上/闪退);先装插件再建索引;分词器要配在 Mapping 的 analyzer(索引用)与 search_analyzer(搜索用)。
json
// 分词测试(Kibana Dev Tools)
POST /_analyze
{
  "analyzer": "ik_max_word",
  "text": "黑马程序员学习Elasticsearch"
}
// 结果会把"黑马/程序员/学习/elasticsearch"拆成词条

3. 索引库操作-Mapping映射 ​

【重点要点】

  • Mapping(映射):定义索引的字段类型和分词器,相当于建表语句——一旦创建不能随意改类型(只能加字段、或重建索引)。
  • 常用字段类型:
    • 字符串:text(可分词,搜索用) / keyword(不分词,作精确匹配/排序/聚合用,如品牌、订单号)
    • 数值:long/integer/double
    • 时间:date
    • 布尔:boolean
    • 地理:geo_point(经纬度,用于附近搜索)
  • 常用属性:index(是否索引,false 不参与搜索)、analyzer(分词器)、copy_to(把多个字段合并到一个字段,跨字段搜索)。
  • 索引操作 API:PUT /索引名(创建)、GET /索引名(查看)、PUT /索引名/_mapping(追加字段)、DELETE /索引名(删除)。
  • 易错点:text 和 keyword 别混——想精确匹配/排序/聚合必须 keyword;复杂对象用 properties 嵌套;copy_to 字段本身要设 index: false(只作拷贝源)。
json
// 创建索引 + Mapping
PUT /item
{
  "mappings": {
    "properties": {
      "id":     { "type": "keyword" },
      "name":   { "type": "text", "analyzer": "ik_max_word",
                  "search_analyzer": "ik_smart" },
      "brand":  { "type": "keyword" },
      "price":  { "type": "double" },
      "image":  { "type": "keyword", "index": false },
      "location": { "type": "geo_point" }
    }
  }
}

4. 文档操作-增删改查 ​

【重点要点】

  • ES 文档用 RESTful API 操作,文档 id 需要自己指定(通常用 MySQL 主键,方便数据同步对应)。
  • 新增:PUT /索引名/_doc/{id}(指定 id,重复会覆盖)或 POST /索引名/_doc(自动生成 id,少用)。
  • 查询:GET /索引名/_doc/{id} 按 id 查。
  • 修改:POST /索引名/_update/{id} 局部修改(只改给定字段);PUT .../_doc/{id} 整体覆盖。
  • 删除:DELETE /索引名/_doc/{id}。
  • 批量操作:POST /索引名/_bulk,一次多条(_index/_delete),数据同步时用(性能好)。
  • 易错点:PUT 整篇覆盖会丢失未提交的字段(要用 _update 局部改);文档不存在时 PUT 变新增;批量语法每两行一组(操作行 + 数据行)。
json
// 新增文档(id = mysql 主键)
PUT /item/_doc/1001
{
  "name": "华为Mate60手机",
  "brand": "华为",
  "price": 5999.00,
  "image": "http://image.com/huawei.png"
}

// 局部修改
POST /item/_update/1001
{ "doc": { "price": 5499.00 } }

// 批量新增
POST /item/_bulk
{"index": {"_id": 1002}}
{"name": "小米14", "brand": "小米", "price": 3999.00}
{"index": {"_id": 1003}}
{"name": "苹果iPhone15", "brand": "苹果", "price": 7999.00}

5. DSL查询-全文检索与精准查询 ​

【重点要点】

  • ES 查询语法叫 DSL(Domain Specific Language),GET /索引名/_search + body 里写查询条件。
  • 全文检索类(先分词再匹配,text 字段用):
    • match:单字段分词匹配("华为手机"会按词匹配)。
    • multi_match:多字段同时匹配(fields: ["name","brand"])。
    • match_all:查所有文档(配合排序分页做"全量列表")。
  • 精准查询类(不分词,keyword/数值字段用):
    • term:按词条精确匹配(品牌/商品编号)。
    • range:范围查询(价格 gte/lte、时间)。
  • 易错点:text 字段用 term 会查不到(text 已被分词,term 找的是完整词条);ES 查询结果在 hits.hits 里(_source 是原文档)。
json
// 全文检索:name 字段分词匹配"手机"
GET /item/_search
{
  "query": { "match": { "name": "手机" } }
}

// 精准查询:品牌(keyword)精确 = 华为
GET /item/_search
{
  "query": { "term": { "brand": "华为" } }
}

// 范围查询:价格 1000-5000
GET /item/_search
{
  "query": { "range": { "price": { "gte": 1000, "lte": 5000 } } }
}

6. DSL查询-复合查询与排序分页 ​

【重点要点】

  • bool 复合查询:多个条件的组合(最常用):
    • must:必须满足(参与算分),类似 AND。
    • should:满足其一(加分),类似 OR。
    • filter:必须满足(不算分,更快),反例:范围过滤、品牌过滤放 filter。
    • must_not:必须不满足。
  • function_score(相关性算分):在原始算分上加权——黑马商城用"是否新品(isAD)加权"让广告/新品排前面。
  • 排序:sort 按字段(数值/时间);地理按 _geo_distance 距离排序。
  • 分页:from+size(默认 from=0, size=10,深度分页性能差,别翻太深);大数据深分页用 search_after(配合 sort 唯一值)。
  • 易错点:filter 里的条件不进算分、性能更好,能放 filter 的别放 must;排序字段要是 keyword/数值(text 不能直接 sort);from 过大(>10000)会报错,要用 search_after。
json
// bool 复合:品牌 must + 价格 filter(不算分)
GET /item/_search
{
  "query": {
    "bool": {
      "must":   [{ "match": { "name": "手机" } }],
      "filter": [
        { "term":  { "brand": "华为" } },
        { "range": { "price": { "gte": 1000 } } }
      ]
    }
  },
  "sort": [{ "price": "asc" }],
  "from": 0,
  "size": 20
}
json
// function_score:新品(isAD=true)加权
GET /item/_search
{
  "query": {
    "function_score": {
      "query": { "match_all": {} },
      "functions": [
        { "filter": { "term": { "isAD": true } }, "weight": 5 }
      ],
      "boost_mode": "sum"
    }
  }
}

7. DSL查询-高亮显示 ​

【重点要点】

  • 高亮:搜索词在结果中加标记显示(前端渲染成红色/黄色),提升体验。
  • 语法:highlight + fields 指定要高亮的字段,pre_tags/post_tags 自定义标签(默认 <em>)。
  • 命中词 fragment_size:默认高亮片段长度(约 100 字),可调 fragment_size 截取重点片段。
  • 高亮只在参与了算分的字段生效(match 的字段);filter 里过滤的词不高亮。
  • 前端处理:后端把高亮字段返回,前端替换 <em> 标签为高亮样式。
  • 易错点:高亮字段必须是参与搜索的 text 字段(keyword 不高亮);pre_tags 前端要能解析(JSON/HTML 转义);多字段匹配分别高亮需要 require_field_match: false。
json
// 查询 + 高亮
GET /item/_search
{
  "query": { "match": { "name": "华为手机" } },
  "highlight": {
    "fields": { "name": {} },
    "pre_tags":  "<em style='color:red'>",
    "post_tags": "</em>"
  }
}
// 结果 hits.hits[].highlight.name = ["<em style='color:red'>华为</em>手机"]

8. 聚合-桶聚合与度量聚合 ​

【重点要点】

  • 聚合(Aggregation):对查询结果统计分组,实现"按品牌筛选"、"价格区间统计"等,类似 SQL 的 GROUP BY + 聚合函数。
  • 桶聚合(Bucket):按条件分桶(分组)——terms(按字段值分组)、range(按数值区间分组)、date_histogram(按时间分组)。常用于筛选条件(左侧品牌列表)。
  • 度量聚合(Metric):对桶内数据计算——avg/min/max/sum、stats(一次给全部统计)、cardinality(去重计数)等。常与桶聚合嵌套(先桶后度)。
  • 语法:请求体 aggs + 聚合名(自定义) + terms: { field: "品牌字段" } + 可选 aggs 嵌套;结果在 aggregations 里。
  • 分页/排序:terms 里 size 控制返回桶个数、order 排序;size: 0 表示只统计不分页返回文档。
  • 易错点:桶聚合的字段必须是 keyword(text 不能直接聚合,除非加 keyword 子字段);嵌套聚合结构是"aggs 套 aggs";聚合结果字段名是自定义的,注意对应。
json
// 按品牌聚合 + 统计每个品牌的平均价格
GET /item/_search
{
  "size": 0,                       // 只要聚合结果,不要文档
  "aggs": {
    "brandAgg": {                  // 聚合名:品牌聚合
      "terms": { "field": "brand", "size": 10 },
      "aggs": {
        "avgPrice": { "avg": { "field": "price" } }   // 嵌套:平均价格
      }
    }
  }
}

9. 自动补全-拼音分词与completion ​

【重点要点】

  • 自动补全(suggest):输入"hua"就提示"华为Mate60"(搜索框联想),用 ES 的 completion suggester。
  • 前置条件:拼音分词器 + 自定义分词器(IK + 拼音组合:analyzer: ik_max_word 索引 + 拼音分词用于补全);拼音插件(pinyin)需安装并兼容版本。
  • 字段类型 completion:专用于自动补全的字段类型,必须精确前缀匹配,创建索引时指定(不能后续改)。
  • 查询语法:suggest + prefix(输入前缀)+ completion: { field: "suggestion" };配置 fuzzy 可容忍拼写错误。
  • 数据准备:文档里要有 suggestion 字段,值 = 完整可补全的字符串数组(如 ["华为Mate60", "华为Mate60pro"])。
  • 易错点:completion 字段要用独立字段存补全数据(把品牌+标题拼起来);拼音大小写/多音字("重庆chongqing")问题;自定义分词器要在索引创建时配置(不能后改)。
json
// 创建索引:suggestion 字段用 completion 类型
PUT /item
{
  "mappings": {
    "properties": {
      "title":      { "type": "text", "analyzer": "ik_max_word" },
      "suggestion": { "type": "completion" }          // 自动补全专用
    }
  }
}
json
// 自动补全查询:输入 p 前缀
GET /item/_search
{
  "suggest": {
    "suggestions": {
      "prefix": "华",
      "completion": {
        "field": "suggestion",
        "size": 10
      }
    }
  }
}

10. 数据同步-MQ同步方案 ​

【重点要点】

  • 问题:数据在 MySQL,搜索在 ES——数据库变了(增删改商品),ES 索引要跟着变,否则搜索到过期数据。
  • 三种同步方案对比:
    1. 同步双写:业务代码里写 MySQL 后顺手写 ES——简单但耦合(业务侵入、双写失败难一致、代码到处改)。
    2. 异步通知(MQ,黑马商城用这个):业务只发一条 MQ 消息,监听器消费后写 ES——解耦、可靠(MQ 可靠性学了就能用)。
    3. Canal 监听 binlog:旁路监听 MySQL 日志自动同步——零侵入但要额外部署 Canal。
  • MQ 方案要点(结合 MQ 笔记):声明 topic 交换机(如 hmall.item.topic,路由键 item.insert/item.update/item.delete)+ 队列;商品服务发消息、搜索服务 @RabbitListener 消费后调用 ES RestClient 增/改/删文档。
  • 易错点:幂等(消息可能重复,写 ES 前按 id 查或直接 upsert);顺序(先删除后新增的消息乱序要处理);监听器里 ES 操作失败要重试/死信(可靠性那套);_bulk 批量同步效率高。
java
// 商品服务:增删改后发消息
@RabbitListener(queues = "queue.item")
public void onItemMessage(ItemMsg msg) {
    if ("delete".equals(msg.getType())) {
        itemDocClient.delete(msg.getItemId());        // ES 删除
    } else {
        Item item = itemMapper.selectById(msg.getItemId());
        itemDocClient.saveOrUpdate(item);             // ES 新增/更新
    }
}
java
// 使用 RestHighLevelClient 写 ES(旧版客户端)
public void save(Item item) throws IOException {
    IndexRequest request = new IndexRequest("item")
        .id(item.getId().toString())
        .source(JSON.toJSONString(item), XContentType.JSON);
    client.index(request, RequestOptions.DEFAULT);
}

11. ES集群-分片与集群部署 ​

【重点要点】

  • 分片(Shard):一个索引数据量大会撑爆单节点,ES 把索引水平切分成多个分片分散到各节点(主分片 primary + 副本分片 replica)。
  • 主分片数 创建索引时定死(如 number_of_shards: 3,之后不能改);副本数可动态调(number_of_replicas)。
  • 节点角色:master(管理元数据、选主)、data(存数据)、coordinating(路由转发查询,默认所有节点兼);discovery-type: single-node 是单机开发模式。
  • 集群高可用:主分片挂了 → 副本顶上(故障转移);同一主分片及其副本不在同一节点(数据冗余)。
  • 部署:多节点 elasticsearch.yml 配 cluster.name(相同才算一个集群)+ node.name + 网络(Docker 多容器 or 多机)。
  • 易错点:主分片数不能改,规划不好后期要重建索引;副本数 = 节点数 - 1 是上限(副本不能和主分片同节点);脑裂问题(master 选举),生产 master 节点要配 discovery.zen.minimum_master_nodes(旧版)/ 新版 bootstrap 配置。
yaml
# elasticsearch.yml(每个节点)
cluster.name: hmall-es-cluster      # 集群名必须一致
node.name: node-1                   # 节点名唯一
network.host: 0.0.0.0
discovery.seed_hosts: ["node-2", "node-3"]   # 集群其他节点
cluster.initial_master_nodes: ["node-1"]
json
// 创建索引时指定分片与副本
PUT /item
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}

以「技术栈」为核心的个人知识库