大型微服务项目:听书——5 ElasticSearch 简介

5 ElasticSearch 简介

5.1 ElasticSearch 概述

5.1.1 简介

  • 官网:Elastic — The Search AI Company | Elastic
  • ElasticSearch 是一个基于 Lucene 的搜索服务器,它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口;
  • ElasticSearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。;
  • 现在 Elastic 官方宣布 ElasticSearch 进入Version 8,在速度、扩展、高相关性和简单性方面开启了一个全新的时代;
  • 说明:ElasticSearch 8最低 JDK 版本要求 17,此处选择的版本是:ElasticSearch 8.5.3;
  • 数据库实时排名:DB-Engines Ranking - popularity ranking of database management systems

5.1.2 特性

  • 实时:理论上数据(TB/PB级别)从写入 ElasticSearch 到数据可以被搜索只需要 1 秒左右的时间,实现准实时的数据索引和查询;
  • 高可用:数据多副本、多节点存储,单节点的故障不影响集群的使用,默认天生集群模式;
  • Rest API
    • ElasticSearch 提供标准的 Rest API,这使得所有支持 Rest API 的语言都能够轻易的使用 ElasticSearch,具备多语言通用的支持特性,易于使用;
    • ElasticSearch Version 8 以后,去除了以前 High-Level API、Low-Level API,统一标准的 Rest API,这将使得 ElasticSearch 更加容易使用,原来被诟病的 API 混乱问题终于得到完美解决;
  • 多客户端支持:支持 Java、Python、Go、PHP、Ruby 等多语言客户端,还支持 JDBC、ODBC 等客户端。

5.1.3 应用场景

  • 作为独立数据库系统

    • ElasticSearch 本身提供了数据持久化存储的能力,并且提供了增删改查的功能,在某些应用场景下可以直接当做数据库系统来使用,既提供了存储能力,又能够同时具备搜索能力,整体技术架构会比较简单,例如博客系统、评论系统;
    • 需要注意的是,ElasticSearch 不支持事务,且写入的性能相对关系型数据库稍弱,所有需要使用事务的场景都不能将 ElasticSearch 当做唯一的数据库系统,这使得这种使用场景很少见;
  • 搭建搜索系统(配合 MySQL 使用)

    • ElasticSearch 为搜索而生,用于搭建全文搜索系统是自然而然的事情,它能够提供快速的索引和搜索功能,还有相关的评分功能、分词插件等,支持丰富的搜索特性,可以用于搭建大型的搜索引擎,更加常用语实现站内搜索,例如银行 App、购物 App 等站内商品、服务搜索;

    • 目前大量的需要支持事务的系统使用 MySQL 作为数据库,但随着业务的开展,数据量会越来越大,而 MySQL 的性能会越来越差,虽然可以通过分库分表的方案进行解决,但是操作比较复杂,而且往往每隔一段时间就需要进行扩展,且代码需要配合修改;

    • 这种情况下可以将数据从 MySQL 同步到 ElasticSearch(双写),针对主要查询历史数据且数据量比较大的而且不需要事务控制等场景使用 ElasticSearch 提供查询,而对需要事务实时控制的即时数据还是通过 MySQL 存储和查询;

      在这里插入图片描述

  • 搭建日志系统

    • 日志系统应该是 ElasticSearch 使用最广泛的场景之一了,ElasticSearch 支持海量数据的存储和查询,特别适合日志搜索场景;

    • 广泛使用的 ELK 套件(Elasticsearch、Logstash、Kibana)是日志系统最经典的案例,使用 Logstash 和 Beats 组件进行日志收集,ElasticSearch 存储和查询应用日志,Kibana 提供日志的可视化搜索界面;

      在这里插入图片描述

    • 上面讲的 ELK 比较“重”,如果为了寻求轻量化,可以使用 GLA;

  • 搭建数据分析系统(大数据领域):ElasticSearch 支持数据分析,例如强大的数据聚合功能,通过搭配 Kibana,提供诸如直方图、统计分组、范围聚合等方便使用的功能,能够快速实现一些数据报表等功能。

5.1.4 数据的分类

  • 结构化数据:
    • 特点:将数据具有的特征事先以结构化的形式定义好,数据有固定的格式或有限的长度;
    • 典型的结构化数据就是传统关系型数据库的表结构,数据特征直接体现在表结构的字段上;
  • 非结构化数据:
    • 特点:数据没有预先定义好的结构化特征,也没有固定格式和固定长度;
    • 典型的非结构化数据包括文章、图片、视频、邮件等。

5.1.5 全文检索

  • 概念:对于非结构化的数据检索,被称为全文检索;

  • 背景:假设现在 MySQL 中有一张User表,含有三个字段:姓名name、年龄age和爱好favor

    在这里插入图片描述

    • 对于 User 表来说,整体上是结构化的;

    • 比如nameage都可以直接建立索引来快速地检索,而其中的favor字段是一个 text 类型,存储的是非结构化的文本数据,比如:“我喜欢 Java“、”我的爱好是篮球“、”我喜欢题足球“等;

    • 与结构化查询相比,全文检索面临的最大问题就是性能问题;

      • 全文检索一般的应用场景是根据一些关键字去查找包含这些关键字的文档,比如互联网搜索引擎要实现的功能就是根据一些关键字查找网页。显然,如果没有对文档做特别处理,查找的办法似乎只能是逐条比对;

      • 比如:假设现在需要找到favor中含有“足球”这个关键字的 User,那么只能使用like模糊查询:

        select * from user where favor like '%足球%'
        
      • like语句是无法建立索引的,查询时会进行全表扫描,并且在每个favor字段中进行遍历匹配,以找到含有“足球”这个关键字的记录,整体复杂度特别高,所以对于全文检索来说关系型数据库不是能很好的支持;

      • 为了解决这个问题,需要一种新索引方法,这种索引方法就是倒排索引

5.1.6 倒排索引

  • 原理:

    • 倒排索引先将文档中包含的关键字全部提取出来,然后再将关键字与文档的对应关系(映射关系)保存起来,最后再对关键字本身做索引排序;
    • 用户在检索某一关键字时,可以先对关键字的索引进行查找,再通过关键字与文档的对应关系找到所在文档;

    在这里插入图片描述

  • 查询过程举例

    • 用户输入:

      Elasticsearch OR Pydantic
      
    • 解析查询:将要用户的输入转成 DSL 语言

      {
        "bool": {
          "should": [
            {"match": { "content": "Elasticsearch" }},
            {"match": { "content": "Pydantic" }}
          ]
        }
      }
      
    • 词项分解:

      ["elasticsearch", "pydantic"]
      
    • 去倒排列表查找对应的文档:

      [1], [2]
      
    • 合并结果

      [1, 2] (OR 操作)
      
    • 应用过滤器:当前无过滤条件,保留全部文档;

    • 计算评分:为文档 1 和 2 分配得分;

    • 排序:按得分降序排列;

    • 分页:返回第 1 页(默认 size=10);

    • 返回结果:

      {
          "id": 1,
          "content": "Elasticsearch..."
      }, 
      {
          "id": 2,
          "content": "Pydantic..."
      }
      
  • 优点:

    • 高效的关键词搜索:倒排索引允许快速查找包含特定关键词的文档,极大提高了查询效率;
    • 可扩展性:通过分片和副本机制,保证 ElasticSearch 能够处理大规模数据,保证高可扩展性和高可用性;
    • 灵活的查询能力:支持多种查询类型,如布尔查询、范围查询、模糊查询等,满足不同应用需求;
  • 缺点:

    • 存储空间占用较大:倒排索引需要存储倒排列表,可能占用较多存储空间,尤其是处理大规模文本数据时;
    • 准实时性较弱:由于倒排索引的构建和更新需要一定时间,可能无法满足高实时性要求的应用场景。

5.2 核心概念

5.2.1 ES 对照 MySQL

在这里插入图片描述

5.2.2 索引 Index

  • ES 的索引对应于 MySQL 的库;

  • 一个索引就是一个由一些有几分相似特征的文档构成的文档集合

  • 比如:可以有一个客户数据的索引、一个产品目录的索引、一个订单数据的索引;

  • 一个索引由一个名字来标识(必须全部是小写字母),并且当我们要对这个索引中的文档进行分词索引查找、搜索、更新和删除的时候,都要使用到这个名字。

5.2.3 类型 Type

  • ES 的类型对应于 MySQL 的表;

  • 在一个索引中,可以定义一种或多种类型;

    对应于MySQL:在一个库汇总,可以定义一张或多张表;

  • 一个类型是其所在的索引的一个逻辑上的分类/分区,其语义完全由开发者来定。通常,会为具有一组共同字段的文档定义一个类型;

  • 不同的 ES 版本,类型发生了不同的变化;

    版本Type
    5.x支持多种type
    6.x只能有一种type
    7.x默认不再支持自定义索引类型(默认类型为:_doc)
    8.x默认类型为:_doc

5.2.4 文档 Document

  • ES 的文档对应于 MySQL 的行;

  • 一个文档是一个可被找到的基础信息单元,也就是一条数据;

  • 比如:可以有某一个客户的文档、某一个产品的文档,某一个订单的文档;

  • 文档以 JSON(Javascript Object Notation)格式来表示,因为 JSON 是目前在互联网中最方便的数据交互格式;

  • 在一个 index/type 里面,可以存储任意多的文档。

5.2.5 字段 Field

  • ES 的字段对应于 MySQL 的列;

  • 用于对文档数据根据不同属性进行分类标识。

5.2.6 映射 Mapping

  • Mapping 是指 ES 在处理数据的方式和规则上做的一些限制;
  • 比如:某个字段的数据类型、默认值、分词器、是否建立倒排索引等。

5.3 基础功能

  • 官方文档:[What is Elasticsearch? | Elasticsearch Guide 8.5] | Elastic

5.3.1 分词器&安装IK分词器

  • 官方提供的分词器有:Standard、etter、Lowercase、Whitespace、UAX URL Email、Classic、Thai 等;

    POST _analyze
    {
      "analyzer": "standard",
      "text": "我是中国人"
    }
    结果:
    {
      "tokens": [
        {
          "token": "我",
          "start_offset": 0,
          "end_offset": 1,
          "type": "<IDEOGRAPHIC>",
          "position": 0
        },
        {
          "token": "是",
          "start_offset": 1,
          "end_offset": 2,
          "type": "<IDEOGRAPHIC>",
          "position": 1
        },
        {
          "token": "中",
          "start_offset": 2,
          "end_offset": 3,
          "type": "<IDEOGRAPHIC>",
          "position": 2
        },
        {
          "token": "国",
          "start_offset": 3,
          "end_offset": 4,
          "type": "<IDEOGRAPHIC>",
          "position": 3
        },
        {
          "token": "人",
          "start_offset": 4,
          "end_offset": 5,
          "type": "<IDEOGRAPHIC>",
          "position": 4
        }
      ]
    }
    
  • 中文分词器可以使用第三方的,比如:IK 分词器

    POST _analyze
    {
      "analyzer": "ik_smart",
      "text": "我是中国人"
    }
    结果:
    {
      "tokens": [
        {
          "token": "我",
          "start_offset": 0,
          "end_offset": 1,
          "type": "CN_CHAR",
          "position": 0
        },
        {
          "token": "是",
          "start_offset": 1,
          "end_offset": 2,
          "type": "CN_CHAR",
          "position": 1
        },
        {
          "token": "中国人",
          "start_offset": 2,
          "end_offset": 5,
          "type": "CN_WORD",
          "position": 2
        }
      ]
    }
    
    POST _analyze
    {
      "analyzer": "ik_max_word",
      "text": "我是中国人"
    }
    结果:
    {
      "tokens": [
        {
          "token": "我",
          "start_offset": 0,
          "end_offset": 1,
          "type": "CN_CHAR",
          "position": 0
        },
        {
          "token": "是",
          "start_offset": 1,
          "end_offset": 2,
          "type": "CN_CHAR",
          "position": 1
        },
        {
          "token": "中国人",
          "start_offset": 2,
          "end_offset": 5,
          "type": "CN_WORD",
          "position": 2
        },
        {
          "token": "中国",
          "start_offset": 2,
          "end_offset": 4,
          "type": "CN_WORD",
          "position": 3
        },
        {
          "token": "国人",
          "start_offset": 3,
          "end_offset": 5,
          "type": "CN_WORD",
          "position": 4
        }
      ]
    }
    
  • 安装 IK 分词器

    # 进入es所在的目录(与 1.7 `docker-compose.yaml`一键部署中间件 的配置对应上)
    cd /mydata/es/plugins/
    
    # 下载ik分词插件,也可以下载后直接上传(版本要与es版本一致)
    https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v8.5.0
    
    # 解压
    # ik 是解压后的文件夹名
    # elasticsearch-analysis-ik-8.5.0.zip 是要被解压的zip包
    unzip -d ik elasticsearch-analysis-ik-8.5.0.zip
    
    # 删除zip包
    rm -rf elasticsearch-analysis-ik-8.5.0.zip
    
    # 重启es
    docker restart 容器id
    
  • 测试:访问部署在服务器(虚拟机)上的 ES

    • 使用ik_smart分词器:

      在这里插入图片描述

    • 使用ik_max_word分词器:

      在这里插入图片描述

5.3.2 索引操作

  • 创建索引:PUT /{索引名称}

    PUT /my_index
    
    结果:
    {
      "acknowledged" : true,
      "shards_acknowledged" : true,
      "index" : "my_index"
    }
    
  • 查看所有索引:

    GET /_cat/indices
    
    结果:
    yellow open my_index Dix6Krz7TLqUCYlEjPcsYQ 1 1 0 0 225b 225b
    
  • 查看单个索引:GET /{索引名称}

    GET /my_index
    
    结果:
    {
      "my_index": {
        "aliases": {},
        "mappings": {},
        "settings": {
          "index": {
            "routing": {
              "allocation": {
                "include": {
                  "_tier_preference": "data_content"
                }
              }
            },
            "number_of_shards": "1",
            "provided_name": "my_index",
            "creation_date": "1752564967586",
            "number_of_replicas": "1",
            "uuid": "Dix6Krz7TLqUCYlEjPcsYQ",
            "version": {
              "created": "8050099"
            }
          }
        }
      }
    }
    
  • 删除索引:DELETE /{索引名称}

    DELETE /my_index
    
    结果:
    {
      "acknowledged" : true
    }
    

5.3.3 文档操作

  • 文档是 ES 搜索数据的最小单位,不依赖预先定义字段,所以可以将文档类比为表的一行 JSON 类型的数据;

  • 在关系型数据库中,要提前定义字段才能使用,而 ES 对于字段是非常灵活的,可以忽略该字段,或者动态地添加一个新的字段;

  • 创建文档:

    语法1PUT /{索引名称}/{类型}/{id}(一定要有id)
    {
        jsonbody
    }
    语法2POST /{索引名称}/{类型}/{id}(可以省略id,若省略会自动生成一个id)
    {
        jsonbody
    }
    
    例:会自动创建索引和类型,es7开始默认类型名为:_doc
    PUT /my_index/_doc/1
    {
      "title": "小米手机",
      "category": "小米",
      "images": "http://www.gulixueyuan.com/xm.jpg",
      "price": 3999
    }
    结果:
    {
      "_index": "my_index",
      "_id": "1",
      "_version": 1,
      "result": "created",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "_seq_no": 0,
      "_primary_term": 1
    }
    
  • 查看指定文档:GET /{索引名称}/{类型}/{id}

    GET /my_index/_doc/1
    
    结果:
    {
      "_index": "my_index",
      "_id": "1",
      "_version": 1,
      "_seq_no": 0,
      "_primary_term": 1,
      "found": true,
      "_source": {
        "title": "小米手机",
        "category": "小米",
        "images": "http://www.gulixueyuan.com/xm.jpg",
        "price": 3999
      }
    }
    
  • 查看所有文档: GET /{索引名称}/_search

    GET /my_index/_search
    
    结果:
    {
      "took": 50,
      "timed_out": false,
      "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": {
          "value": 1,
          "relation": "eq"
        },
        "max_score": 1,
        "hits": [
          {
            "_index": "my_index",
            "_id": "1",
            "_score": 1,
            "_source": {
              "title": "小米手机",
              "category": "小米",
              "images": "http://www.gulixueyuan.com/xm.jpg",
              "price": 3999
            }
          }
        ]
      }
    }
    
  • 修改文档(全部属性,PUT、POST都可以):

    语法:
    PUT /{索引名称}/{类型}/{id}
    {
        jsonbody
    }
    
    例:
    PUT /my_index/_doc/1
    {
      "title": "华为手机",
      "category": "华为",
      "images": "http://www.gulixueyuan.com/xm.jpg",
      "price": 4500
    }
    
    结果:
    {
      "_index": "my_index",
      "_id": "1",
      "_version": 2,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "_seq_no": 1,
      "_primary_term": 1
    }
    
    再查询一下
    GET /my_index/_doc/1
    
    结果:
    {
      "_index": "my_index",
      "_id": "1",
      "_version": 2,
      "_seq_no": 1,
      "_primary_term": 1,
      "found": true,
      "_source": {
        "title": "华为手机",
        "category": "华为",
        "images": "http://www.gulixueyuan.com/xm.jpg",
        "price": 4500
      }
    }
    
  • 修改文档(局部属性,只能用 POST)

    语法:
    POST /{索引名称}/_update/{docId}
    {
        "doc": {
            "属性": "值"
        }
    }
    
    例:
    POST /my_index/_update/1
    {
      "doc": {
        "price": 9999
      }
    }
    
    结果:
    {
      "_index": "my_index",
      "_id": "1",
      "_version": 3,
      "result": "noop",
      "_shards": {
        "total": 0,
        "successful": 0,
        "failed": 0
      },
      "_seq_no": 2,
      "_primary_term": 1
    }
    
    再查询一下
    GET /my_index/_doc/1
    
    结果:
    {
      "_index": "my_index",
      "_id": "1",
      "_version": 3,
      "_seq_no": 2,
      "_primary_term": 1,
      "found": true,
      "_source": {
        "title": "华为手机",
        "category": "华为",
        "images": "http://www.gulixueyuan.com/xm.jpg",
        "price": 9999
      }
    }
    
  • 删除文档: DELETE /{索引名称}/{类型}/{id}

    DELETE /my_index/_doc/1
    
    结果:
    {
      "_index": "my_index",
      "_id": "1",
      "_version": 4,
      "result": "deleted",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "_seq_no": 3,
      "_primary_term": 1
    }
    

5.3.4 映射 Mapping

  • 在 MySQL 中,创建表需要设置字段名称,类型,长度,约束等;
  • 在 ES 中,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)
5.3.4.1 查看映射
  • 语法:GET /{索引名称}/_mapping

    GET /my_index/_mapping
    
    # 结果:
    {
      "my_index": {
        "mappings": {
          "properties": {
            "category": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword",
                  "ignore_above": 256
                }
              }
            },
            "images": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword",
                  "ignore_above": 256
                }
              }
            },
            "price": {
              "type": "long"
            },
            "title": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword",
                  "ignore_above": 256
                }
              }
            }
          }
        }
      }
    }
    
5.3.4.2 动态映射
  • 在关系数据库中,需要先创建数据库,然后在该数据库下创建数据表,并创建表字段、指定类型、主键等,最后才能基于表插入数据;

  • 而 ES 中不需要定义这种映射关系,在创建文档时,会根据文档字段自动识别类型,这种机制称之为动态映射

  • ES 的映射规则:

    数据对应的类型
    null字段不添加
    true|flaseboolean
    字符串text(默认)/keyword
    数值long
    小数float
    日期date
  • 文档值如果是字符串类型,ES 会有两种类型和这个文档值做映射,别是text(默认)和keyword

    • 如果在 ES 中把某个字段的类型指定为text,就代表这个字段的值会被分词,即会进行全文检索;
    • 如果在 ES 中把某个字段的类型指定为keyword,就代表这个字段的值不会被分词,也不会进行全文检索;
    • text类型一旦可以分词,就不能进行聚合(分组) ;keyworld类型虽然不能分词,但是可以进行聚合(分组)。
5.3.4.3 静态映射
  • 静态映射是在 ES 中事先定义好映射,即手动映射,包含文档的各字段类型、分词器、分词等;

    删除原来创建的索引
    DELETE /my_index
    
    创建索引,并同时指定映射关系和分词器等
    PUT /my_index
    {
      "mappings": {
        "properties": {
          "title": {
            "type": "text",
            "index": true,
            "analyzer": "ik_max_word",
            "search_analyzer": "ik_max_word"
          },
          "category": {
            "type": "keyword",
            "index": true
          },
          "images": {
            "type": "keyword",
            "index": true
          },
          "price": {
            "type": "integer",
            "index": true
          }
        }
      }
    }
    
    结果:
    {
      "acknowledged" : true,
      "shards_acknowledged" : true,
      "index" : "my_index"
    }
    
  • 在 ES 中,有下面这些 Type:

    • 字符串:text(支持分词)和keyword(不支持分词)
    • 数值型:short、byte、integer、long、double、float
    • 日期型:date
    • 布尔型:boolean
    • 特殊数据类型:nested(对象类型)
5.3.4.4 nested 介绍
  • nested类型是一种特殊的对象数据类型,允许对象数组彼此独立地进行索引和查询;

  • 例:

    1. 建立一个索引,存储博客文章及其所有评论

      PUT my_comment_index/_doc/1
      {
        "title": "狂人日记",
        "body": "《狂人日记》是一篇象征性和寓意很强的小说,当时,鲁迅对中国国民精神的麻木愚昧颇感痛切。",
        "comments": [
          {
            "name": "张三",
            "age": 34,
            "rating": 8,
            "comment": "非常棒的文章",
            "commented_on": "30 Nov 2023"
          },
          {
            "name": "李四",
            "age": 38,
            "rating": 9,
            "comment": "文章非常好",
            "commented_on": "25 Nov 2022"
          },
          {
            "name": "王五",
            "age": 33,
            "rating": 7,
            "comment": "手动点赞",
            "commented_on": "20 Nov 2021"
          }
        ]
      }
      
      • 上面的代码创建了一个文档,存储了titlebodycomments三个字段;
      • 其中comments字段是一个对象数组,存储了一个个评论对象;
    2. 执行查询:查询年龄为“李四”,且年龄为“34”的文档

      • query:表示查询;
      • bool:表示多条件的组合;
      • must:表示条件的与关系;
      • match:表示模糊查询;
      • term:表示精准查询;
      GET /my_comment_index/_search
      {
        "query": {
          "bool": {
            "must": [
              {
                "match": {
                  "comments.name": "李四"
                }
              },
              {
                "term": {
                  "comments.age": {
                    "value": "34"
                  }
                }
              }
            ]
          }
        }
      }
      

      在这里插入图片描述

      • 查询年龄为“李四”,且年龄为“34”的文档,显示是不可能找得到的,但是上面为什么 ES 又找得到呢?

      • comments字段的默认数据类型是 Object,故文档内部存储为(扁平化存储):

        {
            "title": "狂人日记",
            "body": "《狂人日记》是一篇象征性和寓意很强的小说,当时,鲁迅对中国国民精神的麻木愚昧颇感痛切。",
            "comments.name": "张三, 李四, 王五",
            "comments.comment": "非常棒的文章, 文章非常好, 手动点赞",
            "comments.age": "33, 34, 38",
            "comments.rating": "7, 8, 9"
        }
        
      • 可以发现,comments.namecomments.comment等之间的关系已经丢失了;

    3. 删除当前索引:

      DELETE /my_comment_index
      
    4. 创建一个nested类型的文档(comments字段会被映射为nested类型,而不是默认的object类型)

      PUT my_comment_index
      {
        "mappings": {
            "properties": {
              "comments": {
                "type": "nested" 
              }
          }
        }
      }
      
      PUT my_comment_index/_doc/1
      {
        "title": "狂人日记",
        "body": "《狂人日记》是一篇象征性和寓意很强的小说,当时,鲁迅对中国国民精神的麻木愚昧颇感痛切。",
        "comments": [
          {
            "name": "张三",
            "age": 34,
            "rating": 8,
            "comment": "非常棒的文章",
            "commented_on": "30 Nov 2023"
          },
          {
            "name": "李四",
            "age": 38,
            "rating": 9,
            "comment": "文章非常好",
            "commented_on": "25 Nov 2022"
          },
          {
            "name": "王五",
            "age": 33,
            "rating": 7,
            "comment": "手动点赞",
            "commented_on": "20 Nov 2021"
          }
        ]
      }
      
    5. 执行查询:查询年龄为“李四”,且年龄为“34”的文档

      GET /my_comment_index/_search
      {
        "query": {
          "bool": {
            "must": [
              {
                "match": {
                  "comments.name": "李四"
                }
              },
              {
                "term": {
                  "comments.age": {
                    "value": "34"
                  }
                }
              }
            ]
          }
        }
      }
      

      在这里插入图片描述

      • 结果没有返回任何文档;

      • 当将字段设置为nested后,comments字段将其数组中的每个对象视为一个个单独的文档,故文档内部存储为:

        {
            "comments.name": "张三",
            "comments.comment": "非常棒的文章",
            "comments.age": 34 ,
            "comments.rating": 9 
        },
        {
            "comments.name": "李四",
            "comments.comment": "文章非常好",
            "comments.age": 38,
            "comments.rating": 8
        },
        {
            "comments.name": "王五",
            "comments.comment": "手动点赞",
            "comments.age": 33,
            "comments.rating": 7 
        },
        {
            "title": "狂人日记 ",
            "body": "《狂人日记》是一篇象征性和寓意很强的小说,当时,鲁迅对中国国民精神的麻木愚昧颇感痛切。 "
        }
        
      • 每个内部对象都在内部存储为单独的文档,这保持了他们的领域之间的关系。

5.4 DSL 查询

5.4.1 概述

  • DSL,Domain Specific Language(领域专用语言),ES 提供了基于 JSON 的 DSL 来定义查询;

  • DSL 概览:

    在这里插入图片描述

    • preifx用的很少

    • must:与(计算得分,速度较慢)

    • should:或

    • filter:与(不算得分,速度较快)

    • must_not:非

  • 准备数据:

    PUT /my_index/_doc/1
    {"id":1,"title":"华为笔记本电脑","category":"华为","images":"http://www.gulixueyuan.com/xm.jpg","price":5388}
    
    PUT /my_index/_doc/2
    {"id":2,"title":"华为手机","category":"华为","images":"http://www.gulixueyuan.com/xm.jpg","price":5500}
    
    PUT /my_index/_doc/3
    {"id":3,"title":"VIVO手机","category":"vivo","images":"http://www.gulixueyuan.com/xm.jpg","price":3600}
    

5.4.2 查询所有文档

POST /my_index/_search
{
  "query": {
    "match_all": {}
  }
}

在这里插入图片描述

5.4.3 匹配查询

POST /my_index/_search
{
  "query": {
    "match": {
      "title": "华为"
    }
  }
}

在这里插入图片描述

5.4.4 多字段匹配查询

POST /my_index/_search
{
  "query": {
    "multi_match": {
      "query": "华为智能手机",
      "fields": ["title","category"]  # 或的关系
    }
  }
}

在这里插入图片描述

5.4.5 关键字精确查询

  • term不会进行分词;
  • 在创建索引时,华为被默认动态映射为text,被分词了,所以下面查询不到;
GET /my_index/_search
{
  "query": {
    "term": { # 不会进行分词
      "category": {
        "value": "华为"
      }
    }
  }
}

在这里插入图片描述

5.4.6 多关键字精确查询

GET /my_index/_search
{
  "query": {
    "terms": {
      "category": [
        "华为",
        "vivo"
      ]
    }
  }
}

在这里插入图片描述

5.4.7 范围查询

  • gte: 大于等于
  • lte: 小于等于
  • gt: 大于
  • lt: 小于
POST /my_index/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 3000,
        "lte": 5000
      }
    }
  }
}

在这里插入图片描述

5.4.8 返回指定字段

GET /my_index/_search
{
  "query": {
    "match": {
      "title": "手机"
    }
  },
  "_source": ["title","price"]
}

在这里插入图片描述

5.4.9 组合查询

  • bool表示组合查询,各条件之间有与或非的关系
    • must:各个条件都必须满足,所有条件是与的关系
    • should:各个条件有一个满足即可,即各条件是或的关系
    • must_not:不满足所有条件,即各条件是非的关系
    • filter:与must效果等同,但是它不计算得分,查询效率更高
must
POST /my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "华为"
          }
        },
        {
          "range": {
            "price": {
              "gte": 3000,
              "lte": 5400
            }
          }
        }
      ]
    }
  }
}

在这里插入图片描述

should
POST /my_index/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": "华为"
          }
        },
        {
          "range": {
            "price": {
              "gte": 3000,
              "lte": 4000
            }
          }
        }
      ]
    }
  }
}

在这里插入图片描述

must_not
POST /my_index/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "title": "华为"
          }
        },
        {
          "range": {
            "price": {
              "gte": 3000,
              "lte": 4000
            }
          }
        }
      ]
    }
  }
}

在这里插入图片描述

filter
  • 查询后,发现_score的分值为0
  • 在 ES 中,_score 字段代表每个文档的相关性分数(relevance score)。这个分数用于衡量一个文档与特定查询的匹配程度,它是基于搜索查询的条件和文档的内容来计算的。相关性分数越高,表示文档与查询的匹配度越高,排名也越靠前;
POST /my_index/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "match": {
            "title": "华为"
          }
        }
      ]
    }
  }
}

在这里插入图片描述

5.4.10 聚合查询

  • 聚合允许使用者对 ES 的文档进行统计分析,例如取最大值、平均值、分组等;
  • 而的聚合分析主要又分为两种,第一种是对数据集求最大、最小、平均值以及求和等指标的聚合,这种称为指标聚合
  • 而第二种则和 MySQL 中的分组Group by类似,先对数据进行分组然后再进行指标聚合,这种就称为桶聚合(MySQL 分组);
max
POST /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "max_price": {
      "max": {
        "field": "price"
      }
    }
  }
}

在这里插入图片描述

min
POST /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "min_price": {
      "min": {
        "field": "price"
      }
    }
  }
}

在这里插入图片描述

avg
POST /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

在这里插入图片描述

sum
POST /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "sum_price": {
      "sum": {
        "field": "price"
      }
    }
  }
}

在这里插入图片描述

count
GET /my_index/_count
{
  "query": {
    "match_all": {}
  }
}

在这里插入图片描述

stats
  • 统计基础指标聚合集,ES 中提供了一个stats关键字,可以将某个字段的countmaxminavgsum一次性统计出来;
POST /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "stats_price": {
      "stats": {
        "field": "price"
      }
    }
  }
}

在这里插入图片描述

terms(桶聚合)
  • 此处categorytext类型,该类型无法聚合;

在这里插入图片描述

  • 当改成category.keyword

在这里插入图片描述

  • 还可以对桶继续计算,比如计算每个品牌对应的平均值是多少
POST /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0, 
  "aggs": {
    "groupby_category": {
      "terms": {
        "field": "category.keyword",
        "size": 10
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

在这里插入图片描述

5.4.11 排序

POST /my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "华为"
          }
        }
      ]
    }
  },
  "sort": [
    {
      "price": {
        "order": "asc" # 升序
      }
    }
  ]
}

在这里插入图片描述

5.4.12 分页查询

  • 分页的两个关键属性:fromsize

    • from:当前页的起始索引,默认从0开始。 from = (pageNum - 1) * size
    • size:每页显示多少条
POST /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 2
}

在这里插入图片描述

5.4.13 高亮显示

GET /my_index/_search
{
  "query": {
    "match": {
      "title": "华为手机"
    }
  },
  "highlight": {
    "fields": {
      "title": {}
    },
    "pre_tags": ["<font style='color:red'>"],
    "post_tags": ["</font>"]
  }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失散13

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值