ElasticSearch 8 学习笔记 | 豆包MarsCode AI刷题

228 阅读26分钟

1.1.1. 官网 : Elasticsearch: 权威指南 | Elastic

1.1.2. 查考视频 : 黑马Elasticsearch

1. ES 介绍

1.1.1. 介绍

Elasticsearch 是一个高度可扩展的开源实时搜索和分析引擎,它允许用户在近实时的时间内执行全文搜索、结构化搜索、聚合、过滤等功能。Elasticsearch 基于 Lucene 构建,提供了强大的全文搜索功能,并且具有广泛的应用领域,包括日志和实时分析、社交媒体、电子商务等。

1.1.2. ElasticSearch的作用

  • 虽然不是每个问题都是搜索问题,但Elasticsearch在各种用例中提供了处理数据的速度和灵活性:
  • 为APP或网站增加搜索功能
  • 存储和分析日志、指标和安全事件数据
  • 使用机器学习实时自动建模数据的行为
  • 使用Elasticsearch作为存储引擎自动化业务工作流
  • 使用Elasticsearch作为地理信息系统(GIS)管理、集成和分析空间信息
  • 使用Elasticsearch作为生物信息学研究工具存储和处理遗传数据

基本上,Elasticsearch已经渗透到了我们工作和生活的方方面面。我们打开电商网站搜索商品、打开APP查询资料,或者工作上使用EFK搭建日志系统等,这背后都有Elasticsearch的贡献。对了,GitHub的搜索功能也是基于Elasticsearch构建起来的。

1.1.3. 架构与工作原理

1.1.3.1. 架构概述

Elasticsearch 架构主要由三个组件构成:索引、分片和节点。

  • 索引是文档的逻辑分组,类似于数据库中的表;
  • 分片是索引的物理分区,用于提高数据分布和查询性能;
  • 节点是运行 Elasticsearch 的服务器实例。
1.1.3.2. 工作原理

Elasticsearch 通过以下步骤完成搜索和分析任务:

  1. 接收用户查询请求:Elasticsearch 通过 RESTful API 或 JSON 请求接收用户的查询请求。
  2. 路由请求:接收到查询请求后,Elasticsearch 根据请求中的索引和分片信息将请求路由到相应的节点。
  3. 执行查询:节点执行查询请求,并在相应的索引中查找匹配的文档。
  4. 返回结果:查询结果以 JSON 格式返回给用户,包括匹配的文档和相关字段信息。

1.1.4. ELK技术栈

  • ElasticSearch结合kibanaLogstashBeats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域
  • ElasticSearchelastic stack的核心,负责存储、搜索、分析数据

1.1.5. ElasticSearch和Lucene的关系

  • ElasticSearch底层是基于Lucene来实现的
  • Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目
  • Lucene的优势是 易扩展高性能(基于倒排索引)
  • Lucene的缺点 只限于Java语言开发 , 学习曲线陡峭 , 不支持水平扩展
  • 相比于Lucene,ElasticSearch具备以下优势
    • 支持分布式,可水平扩展
    • 提供Restful接口,可以被任意语言调用

2. ES 的倒排索引

  • 倒排索引的概念是基于MySQL这样的正向索引而言的

2.1. 概念

倒排索引中有两个非常重要的概念

  • 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
  • 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我最喜欢的FPS游戏是Apex,就可以分为我、我最喜欢、FPS游戏、最喜欢的FPS、Apex这样的几个词条
  • 创建倒排索引是对正向索引的一种特殊处理,流程如下
    • 将每一个文档的数据利用算法分词,得到一个个词条
    • 创建表,每行数据包括词条、词条所在文档id、位置等信息
    • 因为词条唯一性,可以给词条创建索引,例如hash表结构索引
词条(term)文档id
小米1,3,4
手机1,2
华为2,3
充电器3
手环4

例如

  • 用户输入条件 华为手机,进行搜索。
  • 对用户输入的内容分词,得到词条:华为、手机。
  • 拿着词条在倒排索引中查找,可以得到包含词条的文档id为:1、2、3。
  • 拿着文档id到正向索引中查找具体文档

虽然要先查询倒排索引,再查询正向索引,但是无论是词条还是文档id,都建立了索引,所以查询速度非常快,无需全表扫描

2.2. 对比

那么为什么一个叫做正向索引,一个叫做倒排索引呢?

    • 正向索引是最传统的,根据id索引的方式。但是根据词条查询是,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档查找词条的过程
    • 倒排索引则相反,是先找到用户要搜索的词条,然后根据词条得到包含词条的文档id,然后根据文档id获取文档,是根据词条查找文档的过程

那么二者的优缺点各是什么呢?

  • 正向索引
    • 优点:可以给多个字段创建索引,根据索引字段搜索、排序速度非常快
    • 缺点:根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描
  • 倒排索引
    • 优点:根据词条搜索、模糊搜索时,速度非常快

    • 缺点:只能给词条创建索引,而不是字段,无法根据字段做排序

3. ES 基本概念

3.1. 文档与字段

  • ElasticSearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在ElasticSearch中 ,而Json文档中往往包含很多的字段(Field),类似于数据库中的列

3.1.1. 文档

文档是Elasticsearch中存储和检索的基本单位,它是序列化为JSON格式的数据结构。每个文档都有一个唯一的标识符,称为_id字段,用于唯一标识该文档。每个文档都存储在一个索引中,并且可以包含多个字段,这些字段可以是不同的数据类型,如文本、数字、日期等常间的基础字段

文档的属性包括_index、_type和_source等。_index表示文档所属的索引名称,_type表示文档所属的类型名称 , _source表示文档的原始JSON数据。

3.1.2. 字段( type )

3.1.2.1. 字符串
  • Text: 用于全文搜索(分词)。
  • Keyword: 用于精确匹配(不分词)。
3.1.2.2. 数值
  • Long: 64 位有符号整数。
  • Integer: 32 位有符号整数。
  • Short: 16 位有符号整数。
  • Byte: 8 位有符号整数。
  • Double: 64 位双精度浮点数。
  • Float: 32 位单精度浮点数。
  • Boolean: 布尔值(true/false)。
3.1.2.3. 日期
  • Date: 日期字段,支持多种日期格式(ISO 8601)。
  • Date_nanos: 纳秒精度的日期字段。
3.1.2.4. 其他
  • Binary: 二进制数据(Base64 编码)。
  • Object: 嵌套对象(键值对的集合)。
  • Nested: 类似对象,但专门用于数组的嵌套文档,支持独立索引。

还包含 专用数据类型 : IP , Geo_point 等等 ,

3.2. 索引和映射

3.2.1. 索引(Index),就是相同类型的文档的集合 , 类似于数据库的表

例如 :所有用户文档,可以组织在一起,成为用户的索引

{
    "id": 101,
    "name": "张三",
    "age": 39
}

{
    "id": 102,
    "name": "李四",
    "age": 49
}

{
    "id": 103,
    "name": "王五",
    "age": 69
}
  • 因此,我们可以把索引当做是数据库中的表

3.2.2. 映射

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库就有映射(mapping),是索引中文档的字段约束信息,类似于表的结构约束

映射定义了文档的结构及其字段的类型、行为和其他属性

包含具体字段及字段的具体约束 , 例如 是否索引、如何分词、存储格式等。

3.2.2.1. ****type 字段属性:

适用于大多数字段类型:

  • type: 指定字段类型(如 textkeyword) 具体字段看上面。
  • index: 是否对字段索引,默认值为 true
    • true: 可以搜索该字段。
    • false: 仅存储,不支持搜索。
  • store: 是否单独存储字段(默认值为 false,从 _source 提取)。
  • null_value: 指定字段值为 null 时的替代值。
  • doc_values: 是否为字段生成 doc_values(优化聚合和排序)。

适用于 text 类型字段:

  • analyzer: 指定分词器(如 standardwhitespace)。
  • search_analyzer: 搜索时的分词器。
  • fields: 定义多字段映射,例如同时索引为 textkeyword

适用于数值类型(如 integer, float):

  • coerce: 是否自动转换数据类型(如字符串转整数),默认 true

适用于 datedate_nanos

  • format: 定义日期格式,支持自定义格式和 ISO 8601。

还有地理元素专有的约束 , 动态映射配置等

3.2.2.2. analyzer :使用哪种分词器
3.2.2.3. index: 是否使用索引
3.2.2.4. properties:该字段的子字段

3.3. 与 Mysql 的对比

MysqlElasticsearch说明
TableIndex索引(index),就是文档的集合,类似数据库的表(Table)
RowDocument文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
ColumnField字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
SchemaMappingMapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQLDSLDSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD

在企业中,往往是这二者结合使用

  • 对安全性要求较高的写操作,使用MySQL实现
  • 对查询性能个较高的搜索需求,使用ElasticSearch实现
  • 二者再基于某种方式,实现数据的同步,保证一致性

4. 索引库操作

  • 索引库就类似于数据库表,mapping映射就类似表的结构
  • 我们要向es中存储数据,必须先创建

4.1.1. 查看健康状态

GET /_cat/health?v

4.1.2. 创建索引

  • 基本语法
    • 请求方式:PUT
    • 请求路径:/{索引库名},可以自定义
    • 请求参数:mapping映射
PUT /{索引库名}
{
  "mappings": {
    "properties": {
      "字段名1": {
        "type": "text ",
        "analyzer": "standard"
      },
      "字段名2": {
        "type": "text",
        "index": true
      },
      "字段名3": {
        "type": "text",
        "properties": {
          "子字段1": {
            "type": "keyword"
          },
          "子字段2": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

示例 :

PUT /test001
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email": {
        "type": "keyword",
        "index": false
      },
      "name": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "keyword"
          },
          "lastName": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

4.1.3. 查询索引库

  • 基本语法
    • 请求方式:GET
    • 请求路径:/{索引库名}
GET /{索引库名}
GET /_cat/indices?v

4.1.4. 修改索引库

  • 基本语法
    • 请求方式:PUT
    • 请求路径:/{索引库名}/_mapping
    • 请求参数:mapping映射
PUT /{索引库名}/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}

注意

  • 倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,就无法修改mapping
  • 虽然无法修改mapping中已有的字段,但是却允许添加新字段到mapping中,因为不会对倒排索引产生影响
  • 如果强行改,则会报错

4.1.5. 删除索引库

基本语法:

  • 请求方式:DELETE
  • 请求路径:/{索引库名}
DELETE /{索引库名}

4.1.6.

5. 文档操作

5.1.1. 创建文档

将 JSON 文档添加到指定的数据流或索引并使其可搜索。如果目标是索引并且文档已经存在,则请求更新文档并递增其版本。

PUT /<target>/_doc/<_id>
POST /<target>/_doc/
PUT /<target>/_create/<_id>
POST /<target>/_create/<_id>
POST /{索引库名}/_doc/{文档id}
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
    // ...
}

示例

POST /review-1/_create/1
{
    "id":1,
    "userID":147982601,
    "score":5,
    "status":2,
    "publishTime":"2023-09-09T16:07:42.499144+08:00",
    "content":"这是一个好评!",
    "tags":[
        {
            "code":1000,
            "title":"好评"
        },
        {
            "code":2000,
            "title":"物超所值"
        },
        {
            "code":3000,
            "title":"有图"
        }
    ]
}

5.1.2. 判断文档是否存在

HEAD /review-1/_doc/1

如果存在,Elasticsearch 返回 200 - OK的响应状态码,如果不存在则返回404 - Not Found

5.1.3. 获取文档

GET /review-1/_doc/1

返回整个文档的内容,包括元数据。

5.1.4. 获取文档指定数据

GET /review-1/_source/1

返回数据 _source 内容

# 可以在查询时指定查询的具体字段。
GET /review-1/_source/1?_source=content,score

返回数据 _source 内容的 content 和 score

5.1.5. 更新文档

更新文档的请求格式如下:

POST /<index>/_update/<_id>

例如,下面的命令用于更新_id为1的文档。

POST /review-1/_update/1
{
  "doc": {
    "content": "这是修改过的好评!"
  }
}

返回 updated 表示更新成功。

5.1.6. 批量获取

命令格式:

GET /_mget

GET /<index>/_mget

GET /review-1/_mget
{
  "docs":[
    {
      "_id":"1"
    },
    {
      "_id":"2"
    }
  ]
}

5.1.7. 删除文档

DELETE /review-1/_doc/1

6. DSL 搜索语句

6.1. 简单分类

ElasticSearch提供了基于DSL来定义查询。常见的查询类型包括

  • 查询所有:查询出所有数据,一般测试用。例如
    • match_all
  • 全文检索(full text):利用分词器对用户输入的内容分词,然后去倒排索引库中匹配。例如
    • match_query
    • multi_match_query
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如
    • ids
    • range
    • term
  • 地理查询(geo):根据经纬度查询。例如
    • geo_distance
    • geo_bounding_box
  • 复合查询(compound):复合查询可以将上述各种查询条件组合起来,合并查询条件。例如
    • bool
    • function_score

6.2. 普通查询

语法

GET /indexname/_search
{
    "query": {
        "查询类型": {
            "查询条件": "条件值"
        }
    }
}

这里以查询所有为例

  • 查询类型为match_all
  • 没有查询条件
GET /indexName/_search
{
    "query": {
        "match_all": {

        }
    }
}
  • 其他的无非就是查询类型查询条件的变化

6.3. 全文检索

6.3.1. 语法

  • 常见的全文检索包括
    • match查询:单字段查询
    • multi_match查询:多字段查询,任意一个字段符合条件就算符合查询条件
  • match查询语法如下
GET /indexName/_search
{
    "query": {
        "match": {
            "FIELD": "TEXT"
        }
    }
}
  • multi_match语法如下
GET /indexName/_search
{
    "query": {
        "multi_match": {
            "fields": ["FIELD1", "FIELD2"]
        }
    }
}

6.3.2. 示例

  • 查询上海外滩的酒店数据
    • 以match查询示例,这里的all字段是之前由namecitybusiness这三个字段拷贝得来的
GET /hotel/_search
{
"query": {
    "match": {
    "all": "上海外滩"
    }
}
}
  • 以multi_match查询示例
GET /hotel/_search
{
"query": {
    "multi_match": {
    "query": "上海外滩",
    "fields": ["brand", "city", "business"]
    }
}
}

6.4. 精准搜索

  • 精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有
    • term:根据词条精确值查询
    • range:根据值的范围查询
  1. term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
  2. range查询:根据数值范围查询,可以使数值、日期的范围

6.4.1. term 语法

GET /indexName/_search
{
    "query": {
        "term": {
            "FIELD": {
                "value": "VALUE"
            }
        }
    }
}
  • 示例:查询北京的酒店数据
GET /hotel/_search
{
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}

6.4.2. range语法

  • 范围查询,一般应用在对数值类型做范围过滤的时候。例如做价格范围的过滤
GET /hotel/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte": 10, //这里的gte表示大于等于,gt表示大于
        "lte": 20  //这里的let表示小于等于,lt表示小于
      }
    }
  }
}
  • 示例:查询酒店价格在1000~3000的酒店
GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 1000,
        "lte": 3000
      }
    }
  }
}

6.5. 地理坐标查询

    1. 携程:搜索附近的酒店
    2. 滴滴:搜索附近的出租车
    3. 微信:搜索附近的人
6.5.1.1. 矩形范围查询
  • 矩形范围查询,也就是geo_bounding_box查询,查询坐标落在某个矩形范围内的所有文档
  • 查询时。需指定矩形的左上、游戏啊两个点的坐标,然后画出一个矩形,落在该矩形范围内的坐标,都是符合条件的文档
  • 基本语法
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        "top_left": {       // 左上点
          "lat": 31.1,      // lat: latitude 纬度 
          "lon": 121.5      // lon: longitude 经度
        },
        "bottom_right": {   // 右下点
          "lat": 30.9,      // lat: latitude 纬度 
          "lon": 121.7      // lon: longitude 经度
        }
      }
    }
  }
}
  • 示例
GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": {
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}
6.5.1.2. 附近查询
  • 附近查询,也叫做举例查询(geo_distance):查询到指定中心点的距离小于等于某个值的所有文档
  • 换句话说,也就是以指定中心点为圆心,指定距离为半径,画一个圆,落在圆内的坐标都算符合条件
  • 语法说明
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "3km",            // 半径
      "location": "39.9, 116.4"     // 圆心
    }
  }
}
  • 示例:查询我附近3km内的酒店文档
GET /hotel/_search
{
  "query": {
    "geo_distance": {
      "distance": "3km",
      "location": "39.9, 116.4"
    }
  }
}

6.6. 复合查询

复合(compound)查询:复合查询可以将其他简单查询组合起来,实现更复杂的搜索逻辑,常见的有两种

  1. function score:算分函数查询,可以控制文档相关性算分,控制文档排名(例如搜索引擎的排名,第一大部分都是广告)
  2. bool query:布尔查询,利用逻辑关系组合多个其他的查询,实现复杂搜索

6.6.1. 相关性算分

6.6.1.1. 自带打算算法
  • 当我们利用match查询时,文档结果会根据搜索词条的关联度打分(_score),返回结果时按照分值降序排列
  • 例如我们搜索虹桥如家,结果如下
[  {    "_score" : 17.850193,    "_source" : {      "name" : "虹桥如家酒店真不错",    }  },  {    "_score" : 12.259849,    "_source" : {      "name" : "外滩如家酒店真不错",    }  },  {    "_score" : 11.91091,    "_source" : {      "name" : "迪士尼如家酒店真不错",    }  }]

5.1 版本后打算算法为 BM25 算法 , 公式如下

6.6.1.2. 算分函数查询
  • 根据相关度打分是比较合理的需求,但是合理的并不一定是产品经理需要的
  • 以某搜索引擎为例,你在搜索的结果中,并不是相关度越高就越靠前,而是谁掏的钱多就让谁的排名越靠前
  • 要想控制相关性算分,就需要利用ES中的function score查询了
  • 语法说明
GET /indexName/_search
{
    "query": {
        "function_score": {
            "query": {
                "match": {
                    "all": "外滩"
                }
            },
            "functions": [
                {
                    "filter": {
                        "term": {
                            "id": "1"
                        }
                    },
                    "weight": 10
                }
            ],
            "boost_mode": "multiply"
        }
    }
}

function score查询中包含四部分内容
  1. 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
  2. 过滤条件:filter部分,符合该条件的文档才会被重新算分
  3. 算分函数:符合filter条件的文档要根据这个函数做运算,得到函数算分(function score),有四种函数
    • weight:函数结果是常量
    • field_value_factor:以文档中的某个字段值作为函数结果
    • random_score:以随机数作为函数结果
    • script_score:自定义算分函数算法
  1. 运算模式:算分函数的结果、原始查询的相关性算分,二者之间的运算方式,包括
    • multiply:相乘
    • replace:用function score替换query score
    • 其他,例如:sum、avg、max、min
function score的运行流程如下
  1. 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算法(query score)
  2. 根据过滤条件,过滤文档
  3. 符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
  4. 原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终给结果,作为相关性算分

因此,其中的关键点是:

  • 过滤条件:决定哪些文档的算分被修改
  • 算分函数:决定函数算分的算法
  • 运算模式:决定最终算分结果

{% note info no-icon %}
需求:给如家这个品牌的酒店排名靠前一点
思路:过滤条件为"brand": "如家",算分函数和运算模式我们可以暴力一点,固定算分结果相乘
{% endnote %}

  • 对应的DSL语句如下,我们搜索外滩的酒店,对如家品牌过滤,最终的运算结果是10倍的原始算分
GET /hotel/_search
{
    "query": {
        "function_score": {
            "query": {
                "match": {
                    "all": "外滩"
                }
            },
            "functions": [
                {
                    "filter": {
                        "term": {
                            "brand": "如家"
                        }
                    },
                    "weight": 10
                }
            ],
            "boost_mode": "multiply"
        }
    }
}

6.6.2. 布尔查询(*)

6.6.2.1. 语法
  • 布尔查询是一个或多个子查询的组合,每一个子句就是一个子查询。子查询的组合方式有
    1. must:必须匹配每个子查询,类似
    2. should:选择性匹配子查询,类似
    3. must_not:必须不匹配,不参与算分,类似
    4. filter:必须匹配,不参与算分
  • 每一个不同的字段,其查询条件、方式都不一样,必须是多个不同的查询,而要组合这些查询,就需要用到布尔查询了
    {% note warning no-icon %}
    需要注意的是,搜索时,参与打分的字段越多,查询的性能就越差,所以在多条件查询时
  • 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
  • 其他过滤条件,采用filter和must_not查询,不参与算分
    {% endnote %}
    {% note info no-icon %}
6.6.2.2. 案例
  • 需求:搜索名字中包含如家,价格不高于400,在坐标39.9, 116.4周围10km范围内的酒店
    分析:
  • 名称搜索,属于全文检索查询,应该参与算分,放到must
  • 价格不高于400,用range查询,属于过滤条件,不参与算分,放到must_not
  • 周围10km范围内,用geo_distance查询,属于过滤条件,放到filter
    {% endnote %}
GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "name": {
              "value": "如家"
            }
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gt": 400
            }
          }
        }
      ],
      "filter": [
        {
          "geo_distance": {
            "distance": "10km",
            "location": {
              "lat": 39.9,
              "lon": 116.4
            }
          }
        }
      ]
    }
  }
}

{% note info no-icon %}
需求:搜索城市在上海,品牌为皇冠假日华美达,价格不低于500,且用户评分在45分以上的酒店
{% endnote %}

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {
          "city": {
            "value": "上海"
          }
        }}
      ],
      "should": [
        {"term": {
          "brand": {
            "value": "皇冠假日"
          }
        }},
        {"term": {
          "brand": {
            "value": "华美达"
          }
        }}
      ],
      "must_not": [
        {"range": {
          "price": {
            "lte": 500
          }
        }}
      ], 
      "filter": [
        {"range": {
          "score": {
            "gte": 45
          }
        }}
      ]
    }
  }
}

{% note warning no-icon %}

  • 如果细心一点,就会发现这里的should有问题,must和should一起用的时候,should会不生效,结果中会查询到除了皇冠假日华美达之外的品牌。
  • 对于DSL语句的解决方案比较麻烦,需要在must里再套一个bool,里面再套should,但是对于Java代码来说比较容易修改
    {% endnote %}

7. 搜索结果的处理

  • 搜索的结果可以按照用户指定的方式去处理或展示

7.1. 排序

  • ES默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序的字段有:keyword类型、数值类型、地理坐标类型、日期类型等

7.1.1. 普通字段查询

  • keyword、数值、日期类型排序的语法基本一致
GET /hotel/_search
{
  "query": {
    "match_all": {
    }
  },
  "sort": [
    {
      "FIELD": {
        "order": "desc"
      },
      "FIELD": {
        "order": "asc"
      }
    }
  ]
}

排序条件是一个数组,也就是可以写读个排序条件。按照声明顺序,当第一个条件相等时,再按照第二个条件排序,以此类推

7.1.1.1. 示例

需求:酒店数据按照用户评价(score)降序排序,评价相同再按照价格(price)升序排序

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "score": {
        "order": "desc"
      },
      "price": {
        "order": "asc"
      }
    }
  ]
}

7.1.2. 地理坐查询

7.1.2.1. 语法说明
GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance": {
        "FIELD": {
          "lat": 40,
          "lon": -70
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

这个查询的含义是

  • 指定一个坐标,作为目标点
  • 计算每一个文档中,指定字段(必须是geo_point类型)的坐标,到目标点的距离是多少
  • 根据距离排序
  • 需求:实现酒店数据按照到你位置坐标的距离升序排序

7.2. 分页

  • ES默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。
  • ES中通过修改from、size参数来控制要返回的分页结果
    • from:从第几个文档开始
    • size:总共查询几个文档
  • 类似于mysql中的limit ?, ?

7.2.1. 基本的分页

  • 分页的基本语法如下
GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 20
}

7.2.2. 深度分页问题

....

7.3. 关键词高亮

7.3.1. 高亮原理

  • 什么是高亮呢?
  • 我们在百度搜索时,关键字会变成红色,比较醒目,这就叫高亮显示
  • 高亮显示的实现分为两步
    1. 给文档中的所有关键字都添加一个标签,例如<em>标签
    2. 页面给<em>标签编写CSS样式

7.3.2. 高亮语法

GET /indexName/_search
{
  "query": {
    "match": {
      "FIELD": "TEXT"
    }
  },
  "highlight": {
    "fields": {
      "FIELD": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}

注意:

  • 高亮是对关键词高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询
  • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
  • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
7.3.2.1. 示例
GET /hotel/_search
{
  "query": {
    "match": {
      "all": "上海如家"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "pre_tags": "<em>",
        "post_tags": "</em>",
        "require_field_match": "false"
      }
    }
  }
}

  • 但默认情况下就是加的<em>标签,所以我们也可以省略
GET /hotel/_search
{
  "query": {
    "match": {
      "all": "上海如家"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "require_field_match": "false"
      }
    }
  }
}

8. Linux 部署 ES

8.1. 无密码 http 快速部署

version: "3.7"

services:
  elasticsearch:
    container_name: elasticsearch
    image: docker.elastic.co/elasticsearch/elasticsearch:8.9.2
    environment:
      - node.name=elasticsearch
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
      - discovery.type=single-node
      - xpack.security.enabled=false # 取消安全策略
    volumes:
      - ./data/es/data:/usr/share/elasticsearch/data
      - ./data/es/plugins:/usr/share/elasticsearch/plugins
      - ./data/es/config:/usr/share/elasticsearch/config
    ports:
      - 9200:9200
      - 9300:9300
    networks:
      - elastic


  kibana:
    image: docker.elastic.co/kibana/kibana:8.9.2
    container_name: kibana
    ports:
      - 5601:5601
    networks:
      - elastic
    depends_on:
      - elasticsearch

networks:
  elastic:
    name: elastic

8.1.1. 创建文件夹

mkdir ./data/es
mkdir ./data/es/config ./data/es/data

8.1.2. 注释调 configdata 的数据卷挂载

启动 compose 文件做数据卷映射

docker cp elasticsearch:/usr/share/elasticsearch/data ./data/es

docker cp elasticsearch:/usr/share/elasticsearch/config ./data/es

chmod 777 ./data/es/*

8.1.3. 取消注释重新启动即可

8.2. 配置密码 http 配置

version: "3.7"

services:
  elasticsearch:
    container_name: elasticsearch
    image: docker.elastic.co/elasticsearch/elasticsearch:8.9.2
    environment:
      - node.name=elasticsearch
      - cluster.name=cluster_elasticsearch
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
      - discovery.type=single-node
    volumes:
      - ./data/es/data:/usr/share/elasticsearch/data
      - ./data/es/plugins:/usr/share/elasticsearch/plugins
      - ./data/es/config:/usr/share/elasticsearch/config
    ports:
      - 9200:9200
      - 9300:9300
    networks:
      - elastic


  kibana:
    image: docker.elastic.co/kibana/kibana:8.9.2
    container_name: kibana
    ports:
      - 5601:5601
    volumes: 
      - ./data/kibana/config:/usr/share/kibana/config
    networks:
      - elastic
    depends_on:
      - elasticsearch

networks:
  elastic:
    name: elastic

8.2.1. 创建文件夹

mkdir ./data/es
mkdir ./data/es/config ./data/es/data
mkdir ./data/kibana
mkdir ./data/kibana/config ./data/kibana/config

8.2.2. 注释调 configdata 的数据卷挂载 并启动容器

8.2.3. 复制数据卷

docker cp elasticsearch:/usr/share/elasticsearch/data ./data/es

docker cp elasticsearch:/usr/share/elasticsearch/config ./data/es

docker cp kibana:/usr/share/kibana/data ./data/kibana
docker cp kibana:/usr/share/kibana/config ./data/kibana

chmod 777 ./data/es/* ./data/kibana/*

8.2.4. 自定义重置 es 和 kibana 密码

docker exec -it elasticsearch /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic -i
docker exec -it elasticsearch /usr/share/elasticsearch/bin/elasticsearch-reset-password -u kibana_system -i

8.2.5. 追加配置内容

  1. .data/es/config/elasticsearch.yml文件内容
nano ./data/es/config/elasticsearch.yml

# 修改安全配置 关闭 证书校验
xpack.security.http.ssl:
  enabled: false
xpack.security.transport.ssl:
  enabled: false

2. .data/kibana/config/kibana.yml 文件内容

cat <<EOL >> ./data/kibana/config/kibana.yml

i18n.locale: zh-CN # 中文

elasticsearch.username: "kibana_system"
elasticsearch.password: "123456" # 密码为刚刚设置的
EOL

8.2.6. 取消注释重新启动即可

8.3. 配置 https 自签证书连接

参考文档ElasticSearch和Kibana的安全设置以及https设置_kibana 和 elasticsearch 之间启用传输层安全-CSDN博客

设置步骤如下:

8.3.1. 进入容器 docker exec -it --user root elasticsearch /bin/bash

8.3.2. 在启动 Elasticsearch 之前, 使用bin目录下的elasticsearch-certutil 工具为生成 CA

elasticsearch-certutil ca

# 会出现如下提示
Please enter the desired output file [elastic-stack-ca.p12]:  # 回车就行  默认文件名elastic-stack-ca.p12
Enter password for elastic-stack-ca.p12 :  # 设置CA的密码

8.3.3. 用上面生成的CA生成证书和私钥

elasticsearch-certutil cert --ca elastic-stack-ca.p12

# 会出现如下提示
Enter password for CA (elastic-stack-ca.p12) :  # 输入使用CA的密码  上面设置的
Please enter the desired output file [elastic-certificates.p12]:  # 回车就行   默认证书的名字
Enter password for elastic-certificates.p12 :  # 设置证书的密码

8.3.4. 把生成的证书拷贝到配置文件夹config中

mv ./elastic-certificates.p12 ./config

mv ./elastic-stack-ca.p12 ./config

8.3.5. 修改es的配置

xpack.security.enabled: true

xpack.security.enrollment.enabled: true

# Enable encryption and mutual authentication between cluster nodes
xpack.security.transport.ssl:
  enabled: true
  verification_mode: certificate
  keystore.path: elastic-certificates.p12
  truststore.path: elastic-certificates.p12

8.3.6. 在容器李将CA和证书密码存储在Elasticsearch密钥库,使用bin目录下的elasticsearch-keystore工具

elasticsearch-keystore add xpack.security.transport.ssl.keystore.secure_password
elasticsearch-keystore add xpack.security.transport.ssl.truststore.secure_passwor

8.3.7. 加密 Elasticsearch 的 HTTP 客户端通信(https)

通过运行 Elasticsearch HTTP 证书工具elasticsearch-certutil以生成证书签名请求 (CSR)

# 输入指令
 elasticsearch-certutil http

# 会出现如下提示
Generate a CSR? [y/N]n
Use an existing CA? [y/N]y  # 是否使用已经存在的ca,基本安全设置已经生成过了
CA Path: elastic-stack-ca.p12  # 输入ca的相对配置文件夹的路径,应该把ca复制到config中
Password for elastic-stack-ca.p12: # ca的密码
You may enter the validity period in years (e.g. 3Y), months (e.g. 18M), or days (e.g. 90D)
For how long should your certificate be valid? [5y] 5y  # 证书有效期 
Generate a certificate per node? [y/N]n  # 是否为每个节点生成,单节点就n 集群的话就y 根据情况而定
Enter all the hostnames that you need, one per line.
When you are done, press <ENTER> once more to move on to the next step  # 输入可以可以颁发(通过)证书的域名  直接回车
Is this correct [Y/n]y

Enter all the IP addresses that you need, one per line.
When you are done, press <ENTER> once more to move on to the next step.  # 输入可以可以颁发(通过)证书的IP  直接回车
Is this correct [Y/n]y
Do you wish to change any of these options? [y/N]n  # 是否修改上述信息
Provide a password for the "http.p12" file:  [<ENTER> for none] # 设置http证书请求的密码
What filename should be used for the output zip file? [C:\cvzhanshi\environment\elasticsearch-8.15.0\elasticsearch-ssl-http.zip]  # 生成的文件名  回车默认

8.3.8. 把生成的文件解压 unizp elasticsearch-ssl-http.zip

elasticsearch
|_ README.txt
|_ http.p12
|_ sample-elasticsearch.yml
/kibana
|_ README.txt
|_ elasticsearch-ca.pem
|_ sample-kibana.yml

8.3.9. 把http.p12复制到es的config中,elasticsearch-ca.pem复制到kibana的config中

8.3.10. 修改es的配置文件末尾追加

# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents
xpack.security.http.ssl:
  enabled: true
  keystore.path: http.p12
  truststore.path: http.p12

8.3.11. 进入容器将私钥的密码添加到 Elasticsearch 中的安全设置中

elasticsearch-keystore add xpack.security.http.ssl.keystore.secure_password
elasticsearch-keystore add xpack.security.http.ssl.truststore.secure_password

8.3.12. 加密 Kibana 和 Elasticsearch 之间的通信 ,将上面的elasticsearch-ca.pem复制到kibana的config中

# ** THIS IS AN AUTO-GENERATED FILE **
#

# Default Kibana configuration for docker target
server.host: "0.0.0.0"
server.shutdownTimeout: "5s"
elasticsearch.hosts: [ "https://elasticsearch:9200" ]
monitoring.ui.container.elasticsearch.enabled: true

i18n.locale: zh-CN # 中文

elasticsearch.username: "kibana_system"
elasticsearch.password: "Fuck0668!"

elasticsearch.ssl.certificateAuthorities: /usr/share/kibana/config/elasticsearch-ca.pem

8.3.13. 加密浏览器和 Kibana 之间的通信

8.3.13.1. 通过es的工具elasticsearch-certutil为 Kibana 生成服务器证书和私钥。
# 进入容器输入
elasticsearch-certutil csr -name kibana-server -dns example.com,www.example.com

得到一个文件解压目录如下:

/kibana-server
|_ kibana-server.csr
|_ kibana-server.key

8.3.14. 解压csr-bundle.zip文件,获取kibana-server.csr未签名安全证书和kibana-server.key未加密私钥

8.3.15. 将 kibana-server.csr 证书签名请求发送到您的内部 CA 或受信任的 CA 进行签名,以获得签名证书。

# 可以使用命令  生成kibana-server.crt证书
openssl x509 -req -in ./kibana-server.csr -signkey ./kibana-server.key -out ./kibana-server.crt
1
2

8.3.16. 把证书拷贝到config目录下

添加 kibana 配置文件


server.ssl.enabled: true
server.ssl.certificate: C:/cvzhanshi/environment/kibana-8.15.0/config/kibana-server.crt
server.ssl.key: ...

8.4. 配置分词器

注意 : 尽量保持与 es 同版本的 ik 分词器

8.4.1. 离线下载

8.4.1.1. 线下访问并安装 github.com/infinilabs/…
8.4.1.2. 解压并到数据卷目录
mkdir ./data/es/plugins/ik
chmod 777 ./data/es/plugins/ik
unzip elasticsearch-analysis-ik-8.9.2.zip ./data/es/plugins/ik/
8.4.1.3. 重启 docker-compose

8.4.2. 在线下载

# 进入容器内部
docker exec -it elasticsearch /bin/bash

# 在线下载并安装
./bin/elasticsearch-plugin  install https://github.com/infinilabs/analysis-ik/releases/download/v8.9.2/elasticsearch-analysis-ik-8.9.2.zip

#退出
exit
#重启容器
docker-compose up -d

9. ES 集群