基础理论和DSL语法

准备工作

什么是ElasticSearch?它和Lucene以及solr的关系是什么?

这些是自己的知识获取能力,自行百度百科

下载ElasticSearch的window版

linux版后续说明

自行百度Elastic,然后进到官网进行下载,我的版本是:7.8.0

下载postman

自行百度进行下载,也可以用其他的。

ElasticSearch中的目录解读

会tomcat,看到这些目录就不陌生

image

进到bin目录下,点击 elasticsearch.bat 文件即可启动 ES 服务

ELK技术是什么意思?

就图中这三个

image

注意事项

保证自己的JDK是1.8或以上。

ES非关系型和关系型数据库对应关系

img

注意:ES 7.x之后,type已经被淘汰了,其他的没变

只要玩ES,那么这个图就要牢牢地记在自己脑海里,后续的名词解释不再过多说明,就是操作这幅图中的东西

基础理论

正向索引和倒排索引

image

elasticsearch中使用的就是倒排索引

倒排索引中又有3个小东西:

  1. 词条是指索引中的最小存储或查询单元。这个其实很好理解,白话文来讲就是:字或者词组,英文就是一个单词,中文就是字或词组嘛,比如:你要查询的内容中具备含义的某一个字或词组,这就是词条呗,如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条。但是数据千千万万,一般的数据结构能够存的下吗?不可能的,所以这里做了文章,采用的是B+树和hash存储(如:hashmap)

  2. 词典:就是词条的集合嘛。字或者词组组成的内容呗

  3. 倒排表就是指 关键字 / 关键词 在索引中的位置。 有点类似于数组,你查询数组中某个元素的位置,但是区别很大啊,我只是为了好理解,所以才这么举例子的

type 类型

这玩意儿就相当于关系型数据库中的表,注意啊:关系型中表是在数据库下,那么ES中也相应的 类型是在索引之下建立的

表是个什么玩意呢?行和列嘛,这行和列有多少?N多行和N多列嘛,所以:ES中的类型也一样,可以定义N种类型。
同时:每张表要存储的数据都不一样吧,所以表是用来干嘛的?分类 / 分区嘛,所以ES中的类型的作用也来了:就是为了分类嘛。
另外:关系型中可以定义N张表,那么在ES中,也可以定义N种类型

因此:ES中的类型类似于关系型中的表,作用:为了分类 / 分区,同时:可以定义N种类型,但是:类型必须是在索引之下建立的( 是索引的逻辑体现嘛 )

但是:不同版本的ES,类型也发生了变化,上面的解读不是全通用的

image

field 字段

这也就类似于关系型中的列。 对文档数据根据不同属性(列字段)进行的分类标识

字段常见的简单类型:注意:id的类型在ES中id是字符串,这点需要注意

  • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)。text和keyword的区别如下;

    • text类型支持全文检索和完全查询,即:我搜索时只用字符串中的一个字符照样得到结果。原理:text使用了分词,就是把字符串拆分为单个字符串了
    • keyword类型支持完全查询,即:精确查询,前提:index不是false原理:keyword不支持分词,所以:查询时必须是完全查询( 所有字符匹配上才可以 )
  • 数值:long、integer、short、byte、double、float、

  • 布尔:boolean

  • 日期:date

  • 对象:object

  • 地图类型:geo_point 和 geo_shape

    • geo_point:有纬度(latitude) 和经度(longitude)确定的一个点,如:“32.54325453, 120.453254”
    • geo_shape:有多个geo_point组成的复杂集合图形,如一条直线 “LINESTRING (-77.03653 38.897676, -77.009051 38.889939)”
  • 自动补全类型:completion

注意:没有数组类型,但是可以实现出数组,因为每种类型可以有“多个值”,即可实现出类似于数组类型,例如下面的格式:

{
    "age": 21,	// Integer类型
    "weight": 52.1,		// float类型
    "isMarried": false,		// boolean类型
    "info": "这就是一个屌丝女",		// 字符串类型 可能为test,也可能为keyword 需要看mapping定义时对文档的约束时什么
    "email": "zixq8@slafjkl.com",	// 字符串类型 可能为test,也可能为keyword 需要看mapping定义时对文档的约束时什么
    "score": [99.1, 99.5, 98.9],	// 类似数组	就是利用了一个类型可以有多个值
    "name": {		// object对象类型
        "firstName": "紫",
        "lastName": "邪情"
    }
}

还有一个字段的拷贝: 可以使用copy_to属性将当前字段拷贝到指定字段

使用场景: 多个字段放在一起搜索的时候

注意: 定义的要拷贝的那个字段在ES中看不到,但是确实是存在的,就像个虚拟的一样

// 定义了一个字段
"all": {
    "type": "text",
    "analyzer": "ik_max_word"
}


"name": {
    "type": "text",
    "analyzer": "ik_max_word",
    "copy_to": "all"		// 将当前字段 name 拷贝到 all字段中去
}

document 文档

这玩意儿类似于关系型中的行。 一个文档是一个可被索引的基础信息单元,也就是一条数据嘛

即:用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息

新增文档:

// 这是kibana中进行的操作,要是使用如postman风格的东西发请求,则在 /索引库名/_doc/文档id 前加上es主机地址即可
POST /索引库名/_doc/文档id		// 指定了文档id,若不指定则es自动创建
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
    // ...
}

查看指定文档id的文档:

GET /{索引库名称}/_doc/{id}

删除指定文档id的文档:

DELETE /{索引库名}/_doc/id值

修改文档:有两种方式

  • 全量修改:直接覆盖原来的文档。其本质是:
    • 根据指定的id删除文档
    • 新增一个相同id的文档
    • 注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了
// 语法格式
PUT /{索引库名}/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    // ... 略
}
  • 增量/局部修改:是只修改指定id匹配的文档中的部分字段
// 语法格式
POST /{索引库名}/_update/文档id
{
    "doc": {
         "字段名": "新的值",
    }
}

mapping 映射

指的就是:结构信息 / 限制条件

还是对照关系型来看,在关系型中表有哪些字段、该字段是否为null、默认值是什么........诸如此的限制条件,所以ES中的映射就是:数据的使用规则设置

mapping是对索引库中文档的约束,常见的mapping属性包括:

  • index:是否创建索引,默认为true
  • analyzer:使用哪种分词器
  • properties:该字段的子字段

更多类型去官网查看:https://www.elastic.co/guide/en/elasticsearch/reference/8.8/mapping-params.html

创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:

  • 字段名
  • 字段数据类型
  • 是否参与搜索
  • 是否需要分词
  • 如果分词,分词器是什么?

其中:

  • 字段名、字段数据类型,可以参考数据表结构的名称和类型
  • 是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索
  • 是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词
  • 分词器,我们可以统一使用ik_max_word
{
  "mappings": {
    "properties": {		// 子字段
      "字段名1":{		// 定义字段名
        "type": "text",		// 该字段的类型
        "analyzer": "ik_smart"		// 该字段采用的分词器类型 这是ik分词器中的,一种为ik_smart 一种为ik_max_word,具体看一开始给的系列知识链接
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"		// 该字段是否可以被索引,默认值为trus,即:不想被搜索的字段就可以显示声明为false
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...略
    }
  }
}

创建索引库的同时,创建数据结构约束:

// 格式
PUT /索引库名称				// 创建索引库
{						// 同时创建数据结构约束信息
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...略
    }
  }
}



// 示例
PUT /user
{
  "mappings": {
    "properties": {
      "info":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email":{
        "type": "keyword",
        "index": "falsae"
      },
      "name":{
        "properties": {
          "firstName": {
            "type": "keyword"
          },
		 "lastName": {
			"type": "keyword"
          }
        }
      },
      // ... 略
    }
  }
}

index 索引库

所谓索引:类似于关系型数据库中的数据库

但是索引这个东西在ES中又有点东西,它的作用和关系型数据库中的索引是一样的,相当于门牌号,一个标识,旨在:提高查询效率,当然,不是说只针对查询,CRUD都可以弄索引,所以这么一说ES中的索引和关系型数据库中的索引是一样的,就不太类似于关系型中的数据库了,此言差矣!在关系型中有了数据库,才有表结构( 行、列、类型...... )

而在ES中就是有了索引,才有doc、field.....,因此:这就类似于关系型中的数据库,只是作用和关系型中的索引一样罢了

因此:ES中索引类似于关系型中的数据库,作用:类似于关系型中的索引,旨在:提高查询效率,当然:在一个集群中可以定义N多个索引,同时:索引名字必须采用全小写字母

当然:也别忘了有一个倒排索引

  • 关系型数据库通过增加一个B+树索引到指定的列上,以便提升数据检索速度。而ElasticSearch 使用了一个叫做 倒排索引 的结构来达到相同的目的

创建索引: 相当于在创建数据库

# 在kibana中进行的操作
PUT /索引库名称

# 在postman之类的地方创建
http://ip:port/indexName     如:http://127.0.0.1:9200/createIndex    	请求方式:put

注:put请求具有幂等性,幂等性指的是: 不管进行多少次重复操作,都是实现相同的结果。可以采用把下面的请求多执行几次,然后:观察返回的结果

具有幂等性的有:put、delete、get

查看索引库:

# 查看指定的索引库
GET /索引库名

# 查看所有的索引库
GET /_cat/indices?v 

修改索引库:

  • 倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping

虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。

语法说明

PUT /索引库名/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
        // ............
    }
  }
}

删除索引库:

DELETE /索引库名

文档_doc

使用post创建doc

这种方式:是采用ES随机生成id时使用的请求方式

注:需要先创建索引,因为:这就类似于关系型数据库中在数据库的表中 创建数据

语法:

http://ip:port/indexName/_doc     如: http://ip:9200/createIndex/_doc    请求方式:post

image

使用put创建doc-转幂等性-自定义id

在路径后面加一个要创建的id值即可

image

查询文档_doc - 重点

id查询单条_doc

语法:

http://ip:port/indexName/_doc/id      如: http://ip:9200/createIndex/_doc/100001     请求方式:get

image

查询ES中索引下的全部_doc

语法:

http://ip:port/indexName/_search    如: http://ip:9200/createIndex/_search     请求方式:get

注意:别再body中携带数据了,不然就会报:

Unknown key for a VALUE_STRING in [title]

image

返回的结果:

{
    "took": 69,   // 查询花费的时间  毫秒值
    "timed_out": false,     // 是否超时
    "_shards": {    // 分片  还没学,先不看
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 3,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [   // 查询出来的 当前索引下的所有_doc文档
            // .............................
        ]
    }
}

文档_doc的修改

全量修改

原理:利用内容覆盖,重新发一份文档罢了

语法:

http://ip:port/indexName/_doc/id      如: http://ip:9200/createIndex/_doc/100001     请求方式:post

局部修改

语法:

http://ip:port/indexName/_update/id   如: http://ip:9200/createIndex/_update/100001    请求方式:post

image

文档_doc的删除

使用delete请求即可

文档DSL查询

elasticsearch的查询依然是基于JSON风格的DSL来实现的

DSL查询分类

ElasticSearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据,一般测试用。例如:match_all

  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

    • match_query
    • multi_match_query
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段,所以不会对搜索条件分词。例如:

    • ids
    • range
    • term
  • 地理(geo)查询:根据经纬度查询。例如:

    • geo_distance
    • geo_bounding_box
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:

    • bool
    • function_score
  • 聚合(aggregations)查询: 可以让我们极其方便的实现对数据的统计、分析、运算,例如:

    • 桶(Bucket)聚合:用来对文档做分组
    • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
    • 管道(pipeline)聚合:其它聚合的结果为基础做聚合

查询的语法基本一致:除了聚合查询

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



// 例如:查询所有
GET /indexName/_search
{
  "query": {
    "match_all": {		// 查询类型为match_all
    }				  // 没有查询条件
  }
}

其它查询无非就是查询类型查询条件的变化

全文检索查询

定义: 利用分词器对用户输入内容分词,然后去倒排索引库中匹配

全文检索查询的基本流程如下:

  1. 对用户搜索的内容做分词,得到词条
  2. 根据词条去倒排索引库中匹配,得到文档id
  3. 根据文档id找到文档,返回给用户

使用场景: 搜索框搜索内容,如百度输入框搜索、google搜索框搜索……….

注意: 因为是拿着词条去匹配,因此参与搜索的字段必须是可分词的text类型的字段

常见的全文检索查询包括:

  • match查询:单字段查询
  • multi_match查询:多字段查询,任意一个字段符合条件就算符合查询条件

match查询语法如下:

GET /indexName/_search
{
  "query": {
    "match": {
      "field": "搜索的文本内容text"
    }
  }
}


// 例如:
GET /indexName/_search
{
  "query": {
    "match": {
      "name": "紫邪情"
    }
  }
}

mulit_match语法如下:

GET /indexName/_search
{
  "query": {
    "multi_match": {
      "query": "搜索的文本内容text",
      "fields": ["field1", "field2"]
    }
  }
}


// 例如:
GET /indexName/_search
{
  "query": {
    "multi_match": {
      "query": "Java",
      "fields": ["username","title", "context"]
    }
  }
}

注意: 搜索字段越多,对查询性能影响越大,因此建议采用copy_to,然后使用单字段查询的方式(即:match查询)

精准查询

定义: 根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段,所以不会对搜索条件分词

常见的精准查询有:

  • term:根据词条精确值查询
  • range:根据值的范围查询
term查询/精确查询

因为精确查询的字段搜是不分词的字段,因此查询的条件也必须是不分词的词条。查询时,用户输入的内容跟自动值完全匹配时才认为符合条件。如果用户输入的内容过多,反而搜索不到数据

语法说明:

// term查询
GET /indexName/_search
{
  "query": {
    "term": {
      "field": {
        "value": "要精确查询的内容"
      }
    }
  }
}


// 例如:
GET /indexName/_search
{
  "query": {
    "term": {
      "field": {
        "value": "遥远的救世主"
      }
    }
  }
}
range查询/范围查询

范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤

基本语法:

// range查询
GET /indexName/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte": 10, // gte代表大于等于,gt则代表大于
        "lte": 20 // lte代表小于等于,lt则代表小于
      }
    }
  }
}

// 例如:
GET /indexName/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 10000,
        "lte": 20000
      }
    }
  }
}

地理坐标查询

所谓的地理坐标查询,其实就是根据经纬度查询,官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-queries.html

常见的使用场景包括:

  • 携程:搜索我附近的酒店
  • 滴滴:搜索我附近的出租车
  • 微信:搜索我附近的人
矩形范围查询

矩形范围查询,也就是geo_bounding_box查询,查询坐标落在某个矩形范围的所有文档

查询时,需要指定矩形的左上右下两个点的坐标,然后画出一个矩形(就是对两个点画“十”字,中间交汇的部分就是要的矩形),落在该矩形内的都是符合条件的点,比如下图

DKV9HZbVS6

语法如下:

// geo_bounding_box查询
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        "top_left": { // 左上点
          "lat": 31.1,	// 这个点的经度
          "lon": 121.5	// 这个点的纬度
        },
        "bottom_right": { // 右下点
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}
附近查询/距离查询

附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档

换句话来说,在地图上找一个点作为圆心,以指定距离为半径,画一个圆,落在圆内的坐标都算符合条件,如下

vZrdKAh19C

语法说明:

// geo_distance 查询
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "距离", // 半径
      "field": "经度,纬度" // 圆心
    }
  }
}



// 例如:在经纬度为 31.21,121.5 的方圆15km的附近
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半径
      "location": "31.21,121.5" // 圆心
    }
  }
}

复合查询

复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑

常见的复合查询有两种:

  • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名
  • bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索
相关性算分算法

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列

在elasticsearch中,早期使用的打分算法是TF-IDF算法,公式如下:

image-20210721190152134

在后来的5.1版本升级中,elasticsearch将算法改进为BM25算法,公式如下:

image-20210721190416214

TF-IDF算法有一各缺陷,就是词条频率越高,文档得分也会越高,单个词条对文档影响较大。而BM25则会让单个词条的算分有一个上限,曲线更加平滑:

image-20210721190907320

function_score 算分函数查询

算分函数查询可以控制文档相关性算分,控制文档排名

以百度为例,你搜索的结果中,并不是相关度越高排名越靠前,而是谁掏的钱多排名就越靠前

要想人为控制相关性算分,就需要利用elasticsearch中的function score 查询了

语法格式说明:

image-20210721191544750

function score 查询中包含四部分内容:

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

function score的运行流程如下:

  1. 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
  2. 根据过滤条件,过滤文档
  3. 符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
  4. 原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分。

因此,其中的关键点是:

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

布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分

注意: 搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:

  • 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
  • 其它过滤条件,采用filter查询。不参与算分

示例:

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

排序查询

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

keyword、数值、日期类型排序的语法基本一致

语法

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "FIELD": "desc"  // 排序字段、排序方式ASC、DESC
    }
    // 多个字段排序就继续写
  ]
}

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

地理坐标排序略有不同

提示:获取你的位置的经纬度的方式:https://lbs.amap.com/demo/jsapi-v2/example/map/click-to-get-lnglat/

语法说明

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance" : {
          "FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点
          "order" : "asc", // 排序方式
          "unit" : "km" // 排序的距离单位
      }
    }
  ]
}

这个查询的含义是:

  • 指定一个坐标,作为目标点
  • 计算每一个文档中,指定字段(必须是geo_point类型)的坐标 到目标点的距离是多少
  • 根据距离排序

分页查询

elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:

  • from:从第几个文档开始
  • size:总共查询几个文档

类似于mysql中的limit ?, ?

基本分页

分页的基本语法如下:

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, // 分页开始的位置,默认为0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}
  • 优点:支持随机翻页
  • 缺点:深度分页问题,默认查询上限(from + size)是10000
  • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
深度分页问题

现在,我要查询990~1000的数据,查询逻辑要这么写:

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "from": 990, // 分页开始的位置,默认为0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}

这里是查询990开始的数据,也就是 第990~第1000条 数据

不过,elasticsearch内部分页时,必须先查询 0~1000条,然后截取其中的990 ~ 1000的这10条:

image-20210721200643029

查询TOP1000,如果es是单点模式,这并无太大影响

但是elasticsearch将来一定是集群,例如我集群有5个节点,我要查询TOP1000的数据,并不是每个节点查询200条就可以了

因为节点A的TOP200,在另一个节点可能排到10000名以外了

因此要想获取整个集群的TOP1000,必须先查询出每个节点的TOP1000,汇总结果后,重新排名,重新截取TOP1000

image-20210721201003229

那如果我要查询9900~10000的数据呢?是不是要先查询TOP10000呢?那每个节点都要查询10000条?汇总到内存中?

当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力,因此elasticsearch会禁止from+ size 超过10000的请求

针对深度分页,ES提供了两种解决方案,官方文档

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式
    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:只能向后逐页查询,不支持随机翻页
    • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
  • scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用
    • 优点:没有查询上限(单次查询的size不超过10000)
    • 缺点:会有额外内存消耗,并且搜索结果是非实时的
    • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 search after方案

高亮查询

高亮显示的实现分为两步:

  1. 给文档中的所有关键字都添加一个标签,例如<em>标签
  2. 页面给<em>标签编写CSS样式

高亮的语法

GET /indexName/_search
{
  "query": {
    "match": {
      "field": "TEXT" // 查询条件,高亮一定要使用全文检索查询
    }
  },
  "highlight": {
    "fields": {
      "FIELD": { // 指定要高亮的字段
        "pre_tags": "<em>",  // 用来标记高亮字段的前置标签,es默认添加的标签就是em
        "post_tags": "</em>" // 用来标记高亮字段的后置标签
      }
    }
  }
}

注意:

  • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
  • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
  • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false,可以解决的场景:要高亮的字段和搜索指定字段不一致。如:
GET /indexName/_search
{
  "query": {
    "match": {
      "name": "紫邪情" // 查询条件,高亮一定要使用全文检索查询
    }
  },
  "highlight": {
    "fields": {
      "all": { // 假如这里的all字段是利用copy_to将其他很多字段copy进来的,就造成上面搜索字段name与这里要高亮得到字段不一致
        "pre_tags": "<em>",
        "post_tags": "</em>",
        "require_field_match": "false"		// 是否要求字段匹配,即:要高亮字段和搜索字段是否匹配,默认是true
      }
    }
  }
}

聚合查询/数据聚合

聚合(aggregations可以让我们极其方便的实现对数据的统计、分析、运算。例如:

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?
  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索效果

聚合的分类

聚合常见的有三类:

  • 桶(Bucket)聚合:用来对文档做分组

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum等
  • 管道(pipeline)聚合:其它聚合的结果为基础做聚合

注意:参加聚合的字段必须是keyword、日期、数值、布尔类型,即:只要不是 text 类型即可,因为text类型会进行分词,而聚合不能进行分词

Bucket 桶聚合

桶(Bucket)聚合:用来对文档做分组

  • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
  • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

语法如下:

GET hhtp://ip:port/indexName/_search
{
  "query": {	// 加入基础查询,从而限定聚合范围,不然默认是将es中的文档全部查出来再聚合
    "查询类型": {
      "查询条件": "条件值"
    }
  },
  "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果	即:去掉结果hits中的hits数组的数据
  "aggs": { // 定义聚合
    "AggName": { //给聚合起个名字
      "aggType": { // 聚合的类型,跟多类型去官网
        "field": "value", // 参与聚合的字段
        "size": 20, // 希望获取的聚合结果数量	默认是10
		"order": {	// 改变聚合的排序规则,默认是 desc 降序
			"_key": "asc" // 按照什么关键字以什么类型排列
        }
      }
    }
  }
}

例如:

// 数据聚合
GET /indexName/_search
{
  "query": {
    "range": {
      "price": {
        "lte": 200
      }
    }
  }, 
  "size": 0, 
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 15,
        "order": {
          "_count": "asc"
        }
      }
    }
  }
}

image-20230627113244929

Metric 度量聚合

度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

  • Avg:求平均值
  • Max:求最大值
  • Min:求最小值
  • Stats:同时求max、min、avg、sum等

语法如下:

GET /indexName/_search
{
  "size": 0, 
  "aggs": {
    "aggName": { 
      "aggType": { 
        "field": "value", 
        "size": 20,
        "order": {
            "_key": "orderType"
        }
      },
      "aggs": { // brands聚合的子聚合,也就是分组后对每组分别计算
        "aggName": { // 聚合名称
          "aggType": { // 聚合类型,这里stats可以计算min、max、avg等
            "field": "value" // 聚合字段
          }
        }
      }
    }
  }
}


// 例如:
GET /indexName/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": { 
      "terms": { 
        "field": "brand", 
        "size": 20,
        "order": {
            "scoreAgg.avg": "asc"	// 注意:若是要使用子聚合来对每个桶进行排序,则这里的写法有点区别
        }
      },
      "aggs": {
        "scoreAgg": {
          "stats": {
            "field": "score"
          }
        }
      }
    }
  }
}

image-20230627114819466

自动补全查询 completion

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型

  • 字段的内容一般是用来补全的多个词条形成的数组

场景: 搜索框输入关键字,搜索框下面就会弹出很多相应的内容出来

image-20230627230906831

比如,一个这样的索引库:

// 创建索引库
PUT test
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion"	// 指定字段类型为 completion
      }
    }
  }
}

然后插入下面的数据:

// 示例数据
POST test/_doc
{
  "title": ["Sony", "WH-1000XM3"]	// 字段内容为多个词条组成的数组
}
POST test/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test/_doc
{
  "title": ["Nintendo", "switch"]
}

查询的DSL语句如下:

// 自动补全查询
GET /test/_search
{
  "suggest": {
    "title_suggest": {	// 起个名字
      "text": "s", // 关键字
      "completion": {
        "field": "title", // 补全查询的字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

Java操作ES篇 - 重点

摸索Java链接ES的流程

自行创建一个maven项目

父项目依赖管理

<properties>
    <ES-version>7.8.0</ES-version>
    <log4j-version>1.2.17</log4j-version>
    <junit-version>4.13.2</junit-version>
    <jackson-version>2.13.0</jackson-version>
    <fastjson.version>1.2.83</fastjson.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <!-- 注意:这里的版本问题,要和下载的window的ES版本一致,甚至后续用linux搭建也是一样的
                          到时用linux时,ES、kibana的版本都有这样的限定
                -->
            <version>${ES-version}</version>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <!-- 注意:这里别搞成了elasticsearch-client
                    这个东西在7.x已经不推荐使用了,而到了8.0之后,这个elasticsearch-client已经完全被废弃了
                 -->
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <!-- 同样的,注意版本问题 -->
            <version>${ES-version}</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j-version}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit-version}</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson-version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

摸索链接流程

获取父项目中的依赖

<dependencies>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
    </dependency>

    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
</dependencies>

代码编写:

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.Test;

import Java.io.IOException;


public class ConnectionTest {
    /**
     * 倒着看逻辑即可
     */
    @Test
    public void test() throws IOException {

        // 3、创建HttpHost
        HttpHost host = new HttpHost("127.0.0.1", 9200);	// 需要:String hostname, int port
      // 当然:这个方法重载中有一个参数scheme  这个是:访问方式 根据需求用http / https都可以  这里想传的话用:http就可以了

        // 2、创建RestClientBuilder
        RestClientBuilder clientBuilder = RestClient.builder(host);
        // 发现1、有重载;2、重载之中有几个参数,而HttpHost... hosts 这个参数貌似贴近我们想要的东西了,所以建一个HttpHost


        // 1、要链接client,那肯定需要一个client咯,正好:导入得有high-level-client
        RestHighLevelClient esClient = new RestHighLevelClient(clientBuilder);
        // 发现需要RestClientBuilder restClientBuilder,那就建

        // 4、释放资源
        esClient.close();
    }
}


Java中操作ES索引

向父项目获取自己要的依赖

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
</dependency>

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>

封装链接对象

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;

/**
 * @ClassName ESClientUtil
 * @Author ZiXieQing
 * @Date 2021/12/14
 * Version 1.0
 **/
public class ESClientUtil {

    private static final String HOST = "127.0.0.1";
    private static final Integer PORT = 9200;

    public static RestHighLevelClient getESClient() {
        return new RestHighLevelClient(RestClient.builder(new HttpHost(HOST, PORT)));
        // 还有一种方式
        // return new RestHighLevelClient(RestClient.builder(HttpHost.create("http://ip:9200")));
    }
}

操作索引

import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
import org.elasticsearch.action.admin.indices.flush.FlushResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

import static com.zixieqing.hotel.constant.MappingConstant.mappingContext;

/**
 * elasticsearch的索引库测试
 * 规律:esClient.indices().xxx(xxxIndexRequest(IndexName), RequestOptions.DEFAULT)
 *      其中 xxx 表示要对索引进行得的操作,如:create、delete、get、flush、exists.............
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest(classes = HotelApp.class)
public class o1IndexTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://ip:9200")));
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    /**
     * 创建索引 并 创建字段的mapping映射关系
     */
    @Test
    void createIndexAndMapping() throws IOException {
        // 1、创建索引
        CreateIndexRequest request = new CreateIndexRequest("person");
        // 2、创建字段的mapping映射关系   参数1:编写的mapping json字符串  参数2:采用的文本类型
        request.source(mappingContext, XContentType.JSON);
        // 3、发送请求 正式创建索引库与mapping映射关系
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        // 查看是否创建成功
        System.out.println("response.isAcknowledged() = " + response.isAcknowledged());
        // 判断指定索引库是否存在
        boolean result = client.indices().exists(new GetIndexRequest("person"), RequestOptions.DEFAULT);
        System.out.println(result ? "hotel索引库存在" : "hotel索引库不存在");
    }

    /**
     * 删除指定索引库
     */
    @Test
    void deleteIndexTest() throws IOException {
        // 删除指定的索引库
        AcknowledgedResponse response = client.indices()
                .delete(new DeleteIndexRequest("person"), RequestOptions.DEFAULT);
        // 查看是否成功
        System.out.println("response.isAcknowledged() = " + response.isAcknowledged());
    }

    // 索引库一旦创建,则不可修改,但可以添加mapping映射

    /**
     * 获取指定索引库
     */
    @Test
    void getIndexTest() throws IOException {
        // 获取指定索引
        GetIndexResponse response = client.indices()
                .get(new GetIndexRequest("person"), RequestOptions.DEFAULT);
    }

    /**
     * 刷新索引库
     */
    @Test
    void flushIndexTest() throws IOException {
        // 刷新索引库
        FlushResponse response = client.indices().flush(new FlushRequest("person"), RequestOptions.DEFAULT);
        // 检查是否成功
        System.out.println("response.getStatus() = " + response.getStatus());
    }
}

Java操作ES中的文档_doc - 重点

这里还需要json依赖,使用jackson或fastjson均可

同时:为了偷懒,所以把lombok也一起导入了

基本的文档CRUD

import com.alibaba.fastjson.JSON;
import com.zixieqing.hotel.pojo.Hotel;
import com.zixieqing.hotel.pojo.HotelDoc;
import com.zixieqing.hotel.service.IHotelService;
import org.apache.http.HttpHost;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

/**
 * elasticsearch的文档测试
 * 规律:esClient.xxx(xxxRequest(IndexName, docId), RequestOptions.DEFAULT)
 *      其中 xxx 表示要进行的文档操作,如:
 *          index   新增文档
 *          delete  删除指定id文档
 *          get     获取指定id文档
 *          update  修改指定id文档的局部数据
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest(classes = HotelApp.class)
public class o2DocumentTest {
    @Autowired
    private IHotelService service;

    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    /**
     * 添加文档
     */
    @Test
    void addDocumentTest() throws IOException {

        // 1、准备要添加的文档json数据
        // 通过id去数据库获取数据
        Hotel hotel = service.getById(36934L);
        // 当数据库中定义的表结构和es中定义的字段mapping映射不一致时:将从数据库中获取的数据转成 es 中定义的mapping映射关系对象
        HotelDoc hotelDoc = new HotelDoc(hotel);

        // 2、准备request对象    指定 indexName+文档id
        IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());

        // 3、把数据转成json
        request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);

        // 4、发起请求,正式在ES中添加文档    就是根据数据建立倒排索引,所以这里调研了index()
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);

        // 5、检查是否成功     使用下列任何一个API均可   若成功二者返回的结果均是 CREATED
        System.out.println("response.getResult() = " + response.getResult());
        System.out.println("response.status() = " + response.status());
    }

    /**
     * 根据id删除指定文档
     */
    @Test
    void deleteDocumentTest() throws IOException {
        // 1、准备request对象
        DeleteRequest request = new DeleteRequest("indexName", "docId");

        // 2、发起请求
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        // 查看是否成功   成功则返回 OK
        System.out.println("response.status() = " + response.status());
    }

    /**
     * 获取指定id的文档
     */
    @Test
    void getDocumentTest() throws IOException {
        // 1、获取request
        GetRequest request = new GetRequest"indexName", "docId");

        // 2、发起请求,获取响应对象
        GetResponse response = client.get(request, RequestOptions.DEFAULT);

        // 3、解析结果
        HotelDoc hotelDoc = JSON.parseObject(response.getSourceAsString(), HotelDoc.class);
        System.out.println("hotelDoc = " + hotelDoc);
    }

    /**
     * 修改指定索引库 和 文档id的局部字段数据
     * 全量修改是直接删除指定索引库下的指定id文档,然后重新添加相同文档id的文档即可
     */
    @Test
    void updateDocumentTest() throws IOException {
        // 1、准备request对象
        UpdateRequest request = new UpdateRequest("indexName", "docId");

        // 2、要修改那个字段和值      注:参数是 key, value 形式 中间是 逗号
        request.doc(
                "price",500
        );

        // 3、发起请求
        UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
        // 查看结果 成功则返回 OK
        System.out.println("response.status() = " + response.status());
    }
}

批量操作文档

本质:把请求封装了而已,从而让这个请求可以传递各种类型参数,如:删除的、修改的、新增的,这样就可以搭配for循环

package com.zixieqing.hotel;

import com.alibaba.fastjson.JSON;
import com.zixieqing.hotel.pojo.Hotel;
import com.zixieqing.hotel.pojo.HotelDoc;
import com.zixieqing.hotel.service.IHotelService;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetRequest;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.List;

/**
 * elasticsearch 批量操作文档测试
 * 规律:EsClient.bulk(new BulkRequest()
 *                    .add(xxxRequest("indexName").id().source())
 *                    , RequestOptions.DEFAULT)
 * 其中:xxx 表示要进行的操作,如
 *      index   添加
 *      delete  删除
 *      get     查询
 *      update  修改
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest(classes = HotelApp.class)
public class o3BulkDocumentTest {
    @Autowired
    private IHotelService service;

    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    /**
     * 批量添加文档数据到es中
     */
    @Test
    void bulkAddDocumentTest() throws IOException {
        // 1、去数据库批量查询数据
        List<Hotel> hotels = service.list();

        // 2、将数据库中查询的数据转成 es 的mapping需要的对象
        BulkRequest request = new BulkRequest();
        for (Hotel hotel : hotels) {
            HotelDoc hotelDoc = new HotelDoc(hotel);
            // 批量添加文档数据到es中
            request.add(new IndexRequest("hotel")
                    .id(hotelDoc.getId().toString())
                    .source(JSON.toJSONString(hotelDoc), XContentType.JSON));
        }

        // 3、发起请求
        BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
        // 检查是否成功   成功则返回OK
        System.out.println("response.status() = " + response.status());
    }

    /**
     * 批量删除es中的文档数据
     */
    @Test
    void bulkDeleteDocumentTest() throws IOException {
        // 1、准备要删除数据的id
        List<Hotel> hotels = service.list();

        // 2、准备request对象
        BulkRequest request = new BulkRequest();
        for (Hotel hotel : hotels) {
            // 根据批量数据id 批量删除es中的文档
            request.add(new DeleteRequest("hotel").id(hotel.getId().toString()));
        }

        // 3、发起请求
        BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
        // 检查是否成功       成功则返回 OK
        System.out.println("response.status() = " + response.status());
    }

    
    // 批量获取和批量修改是同样的套路  批量获取还可以使用 mget 这个API


    /**
     * mget批量获取
     */
    @Test
    void mgetTest() throws IOException {
        List<Hotel> hotels = service.list();

        // 1、准备request对象
        MultiGetRequest request = new MultiGetRequest();
        for (Hotel hotel : hotels) {
            // 添加get数据    必须指定index 和 文档id,可以根据不同index查询
            request.add("hotel", hotel.getId().toString());
        }

        // 2、发起请求,获取响应
        MultiGetResponse responses = client.mget(request, RequestOptions.DEFAULT);
        for (MultiGetItemResponse response : responses) {
            GetResponse resp = response.getResponse();
            // 如果存在则打印响应信息
            if (resp.isExists()) {
                System.out.println("获取到的数据= " +resp.getSourceAsString());
            }
        }
    }
}

Java进行DSL文档查询

其实这种查询都是套路而已,一看前面玩的DSL查询的json形式是怎么写的,二看你要做的是什么查询,然后就是用 queryBuilds 将对应的查询构建出来,其他都是相同套路了

查询所有 match all

match all:查询出所有数据

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

/**
 * es的dsl文档查询之match all查询所有,也可以称之为 全量查询
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest
public class o1MatchAll {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }


    /**
     * 全量查询:查询所有数据
     */
    @Test
    void matchAllTest() throws IOException {
        // 1、准备request
        SearchRequest request = new SearchRequest("indexName");
        // 2、指定哪种查询/构建DSL语句
        request.source().query(QueryBuilders.matchAllQuery());
        // 3、发起请求 获取响应对象
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4、处理响应结果
        // 4.1、获取结果中的Hits
        SearchHits searchHits = response.getHits();
        // 4.2、获取Hits中的total
        long total = searchHits.getTotalHits().value;
        System.out.println("总共获取了 " + total + " 条数据");
        // 4.3、获取Hits中的hits
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            // 4.3.1、获取hits中的source 也就是真正的数据,获取到之后就可以用来处理自己要的逻辑了
            String source = hit.getSourceAsString();
            System.out.println("source = " + source);
        }
    }
}

Java代码和前面玩的DSL语法的对应情况:

image-20230623213506444

全文检索查询

match 单字段查询 与 multi match多字段查询

下面的代码根据情境需要,可以自行将响应结果处理进行抽取

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

/**
 * DLS之全文检索查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配
 * match_query 单字段查询 和 multi_match_query 多字段查询
 *
 * <p>@author       : ZiXieqing</p>
 */


@SpringBootTest
public class o2FullTextTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    /**
     * match_query  单字段查询
     */
    @Test
    void matchQueryTest() throws IOException {
        // 1、准备request
        SearchRequest request = new SearchRequest("indexName");
        // 2、准备DSL
        request.source().query(QueryBuilders.matchQuery("city", "上海"));
        // 3、发送请求,获取响应对象
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 处理响应结果,后面都是一样的流程 都是解析json结果而已
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("获取了 " + total + " 条数据");
        for (SearchHit hit : searchHits.getHits()) {
            String dataJson = hit.getSourceAsString();
            System.out.println("dataJson = " + dataJson);
        }
    }

    /**
     * multi match 多字段查询 任意一个字段符合条件就算符合查询条件
     */
    @Test
    void multiMatchTest() throws IOException {
        SearchRequest request = new SearchRequest("indexName");
        request.source().query(QueryBuilders.multiMatchQuery("成人用品", "name", "business"));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理响应结果,后面都是一样的流程 都是解析json结果而已
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("获取了 " + total + " 条数据");
        for (SearchHit hit : searchHits.getHits()) {
            String dataJson = hit.getSourceAsString();
            System.out.println("dataJson = " + dataJson);
        }
    }
}

精确查询

精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段,所以不会对搜索条件分词

range 范围查询 和 term精准查询

term:根据词条精确值查询

range:根据值的范围查询

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

/**
 * DSL之精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段,所以 不会 对搜索条件分词
 * range 范围查询 和 term 精准查询
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest
public class o3ExactTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    /**
     * term 精准查询 根据词条精确值查询
     * 和 match 单字段查询有区别,term要求内容完全匹配
     */
    @Test
    void termTest() throws IOException {
        SearchRequest request = new SearchRequest("indexName");
        request.source().query(QueryBuilders.termQuery("city", "深圳"));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理响应结果,后面都是一样的流程 都是解析json结果而已
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("获取了 " + total + " 条数据");
        for (SearchHit hit : searchHits.getHits()) {
            String dataJson = hit.getSourceAsString();
            System.out.println("dataJson = " + dataJson);
        }
    }

    /**
     * range 范围查询
     */
    @Test
    void rangeTest() throws IOException {
        SearchRequest request = new SearchRequest("indexName");
        request.source().query(QueryBuilders.rangeQuery("price").lte(250));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理响应结果,后面都是一样的流程 都是解析json结果而已
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("获取了 " + total + " 条数据");
        for (SearchHit hit : searchHits.getHits()) {
            String dataJson = hit.getSourceAsString();
            System.out.println("dataJson = " + dataJson);
        }
    }
}

地理坐标查询

geo_distance 附近查询
package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

/**
 * DSL之地理位置查询
 * geo_bounding_box 矩形范围查询 和 geo_distance 附近查询
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest
public class o4GeoTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    /**
     * geo_distance 附近查询
     */
    @Test
    void geoDistanceTest() throws IOException {
        SearchRequest request = new SearchRequest("indexName");
        request.source()
                .query(QueryBuilders.geoDistanceQuery("location")
                        .distance("15km").point(31.21,121.5));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理响应结果,后面都是一样的流程 都是解析json结果而已
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("获取了 " + total + " 条数据");
        for (SearchHit hit : searchHits.getHits()) {
            String dataJson = hit.getSourceAsString();
            System.out.println("dataJson = " + dataJson);
        }
    }
}

复合查询

function_score 算分函数查询 是差不多的道理

bool 布尔查询之must、should、must not、filter查询

布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分

注意: 搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:

  • 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
  • 其它过滤条件,采用filter查询。不参与算分
package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

/**
 * DSL之复合查询:基础DSL查询进行组合,从而得到实现更复杂逻辑的复合查询
 * function_score 算分函数查询
 *
 * bool布尔查询
 *  must     必须匹配每个子查询   即:and “与”   参与score算分
 *  should   选择性匹配子查询    即:or “或”    参与score算分
 *  must not 必须不匹配         即:“非"       不参与score算分
 *  filter   必须匹配           即:过滤        不参与score算分
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest
public class o5Compound {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }


    /**
     * bool布尔查询
     *  must     必须匹配每个子查询   即:and “与”   参与score算分
     *  should   选择性匹配子查询    即:or “或”    参与score算分
     *  must not 必须不匹配         即:“非"       不参与score算分
     *  filter   必须匹配           即:过滤        不参与score算分
     */
    @Test
    void boolTest() throws IOException {
        SearchRequest request = new SearchRequest("indexName");
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 构建must   即:and 与
        boolQueryBuilder.must(QueryBuilders.termQuery("city", "北京"));
        // 构建should   即:or 或
        boolQueryBuilder.should(QueryBuilders.multiMatchQuery("速8", "brand", "name"));
        // 构建must not   即:非
        boolQueryBuilder.mustNot(QueryBuilders.rangeQuery("price").gte(250));
        // 构建filter   即:过滤
        boolQueryBuilder.filter(QueryBuilders.termQuery("starName", "二钻"));

        request.source().query(boolQueryBuilder);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理响应结果,后面都是一样的流程 都是解析json结果而已
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("获取了 " + total + " 条数据");
        for (SearchHit hit : searchHits.getHits()) {
            String dataJson = hit.getSourceAsString();
            System.out.println("dataJson = " + dataJson);
        }
    }
}

Java代码和前面玩的DSL语法对应关系:

image-20230624131548461

fuzzy 模糊查询

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

/**
 * DSL之模糊查询
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest
public class o6FuzzyTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

	/**
     * 模糊查询
     */
    @Test
    void fuzzyTest() throws IOException {
        SearchRequest request = new SearchRequest("indexName");
        // fuzziness(Fuzziness.ONE)     表示的是:字符误差数  取值有:zero、one、two、auto
        // 误差数  指的是:fuzzyQuery("name","深圳")这里面匹配的字符的误差    可以有几个字符不一样,多/少几个字符?
        request.source().query(QueryBuilders.fuzzyQuery("name", "深圳").fuzziness(Fuzziness.ONE));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理响应结果,后面都是一样的流程 都是解析json结果而已
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("获取了 " + total + " 条数据");
        for (SearchHit hit : searchHits.getHits()) {
            String dataJson = hit.getSourceAsString();
            System.out.println("dataJson = " + dataJson);
        }
    }
}

排序和分页查询

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

/**
 * DSL之排序和分页
 *
 * <p>@author       : ZiXieqing</p>
 */


@SpringBootTest
public class o7SortAndPageTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    /**
     * sort 排序查询
     */
    @Test
    void sortTest() throws IOException {
        SearchRequest request = new SearchRequest("indexName");
        request.source()
                .query(QueryBuilders.matchAllQuery())
                .sort("price", SortOrder.ASC);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 处理响应结果,后面都是一样的流程 都是解析json结果而已
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("获取了 " + total + " 条数据");
        for (SearchHit hit : searchHits.getHits()) {
            String dataJson = hit.getSourceAsString();
            System.out.println("dataJson = " + dataJson);
        }
    }

    /**
     * page 分页查询
     */
    @Test
    void pageTest() throws IOException {
        int page = 2, size = 20;
        SearchRequest request = new SearchRequest("indexName");
        request.source()
                .query(QueryBuilders.matchAllQuery())
                .from((page - 1) * size).size(size);

        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 处理响应结果,后面都是一样的流程 都是解析json结果而已
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("获取了 " + total + " 条数据");
        for (SearchHit hit : searchHits.getHits()) {
            String dataJson = hit.getSourceAsString();
            System.out.println("dataJson = " + dataJson);
        }
    }
}

高亮查询

返回结果处理的逻辑有点区别,但思路都是一样的

package com.zixieqing.hotel.dsl_query_document;

import com.alibaba.fastjson.JSON;
import com.zixieqing.hotel.HotelApp;
import com.zixieqing.hotel.pojo.HotelDoc;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.Map;

/**
 * DSL之高亮查询
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest(classes = HotelApp.class)
public class o8HighLightTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    /**
     * 高亮查询
     * 返回结果处理不太一样
     */
    @Test
    void highLightTest() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source()
                .query(QueryBuilders.matchQuery("city", "北京"))
                .highlighter(SearchSourceBuilder.highlight()
                        .field("name")  // 要高亮的字段
                        .preTags("<em>")    // 前置HTML标签 默认就是em
                        .postTags("</em>")  // 后置标签
                        .requireFieldMatch(false));     // 是否进行查询字段和高亮字段匹配

        // 发起请求,获取响应对象
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 处理响应结果
        for (SearchHit hit : response.getHits()) {
            String originalData = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(originalData, HotelDoc.class);
            System.out.println("原始数据为:" + originalData);

            // 获取高亮之后的结果
            // key 为要进行高亮的字段,如上为field("name")   value 为添加了标签之后的高亮内容
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)) {
                // 根据高亮字段,获取对应的高亮内容
                HighlightField name = highlightFields.get("name");
                if (name != null) {
                    // 获取高亮内容   是一个数组
                    String highLightStr = name.getFragments()[0].string();
                    hotelDoc.setName(highLightStr);
                }
            }

            System.out.println("hotelDoc = " + hotelDoc);
        }
    }
}

代码和DSL语法对应关系: request.source() 获取到的就是返回结果的整个json文档

image-20230624175348848

聚合查询

聚合(aggregations可以让我们极其方便的实现对数据的统计、分析、运算

聚合常见的有三类:

  • 桶(Bucket)聚合:用来对文档做分组

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求max、min、avg、sum等
  • 管道(pipeline)聚合:其它聚合的结果为基础做聚合

注意:参加聚合的字段必须是keyword、日期、数值、布尔类型,即:只要不是 text 类型即可,因为text类型会进行分词,而聚合不能进行分词

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.List;

/**
 * 数据聚合 aggregation 可以让我们极其方便的实现对数据的统计、分析、运算
 * 桶(Bucket)聚合:用来对文档做分组
 *      TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
 *      Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
 *
 *  度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
 *      Avg:求平均值
 *      Max:求最大值
 *      Min:求最小值
 *      Stats:同时求max、min、avg、sum等
 *
 *  管道(pipeline)聚合:其它聚合的结果为基础做聚合
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest(classes = HotelApp.class)
public class o9AggregationTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    @Test
    void aggregationTest() throws IOException {
        // 获取request
        SearchRequest request = new SearchRequest("indexName");
        // 组装DSL
        request.source()
                .size(0)
                .query(QueryBuilders
                        .rangeQuery("price")
                        .lte(250)
                )
                .aggregation(AggregationBuilders
                        .terms("brandAgg")
                        .field("brand")
                        .order(BucketOrder.aggregation("scoreAgg.avg",true))
                        .subAggregation(AggregationBuilders
                                .stats("scoreAgg")
                                .field("score")
                        )
                );

        // 发送请求,获取响应
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 处理响应结果
        System.out.println("response = " + response);
        // 获取全部聚合结果对象 getAggregations
        Aggregations aggregations = response.getAggregations();
        // 根据聚合名 获取其聚合对象
        Terms brandAgg = aggregations.get("brandAgg");
        // 根据聚合类型 获取对应聚合对象
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            // 根据key获取其value
            String value = bucket.getKeyAsString();
            // 将value根据需求做处理
            System.out.println("value = " + value);
        }
    }
}

请求组装对应关系:

image-20230627140843561

响应结果对应关系:

image-20230627141303392

自动补全查询

package com.zixieqing.hotel.dsl_query_document;

import com.zixieqing.hotel.HotelApp;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

/**
 * 自动补全 completion类型: 这个查询会匹配以用户输入内容开头的词条并返回
 *  参与补全查询的字段 必须 是completion类型
 *  字段的内容一般是用来补全的多个词条形成的数组
 *
 * <p>@author       : ZiXieqing</p>
 */

@SpringBootTest(classes = HotelApp.class)
public class o10Suggest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(
                RestClient.builder(HttpHost.create("http://ip:9200"))
        );
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    @Test
    void completionTest() throws IOException {
        // 准备request
        SearchRequest request = new SearchRequest("hotel");
        // 构建DSL
        request.source()
                .suggest(new SuggestBuilder()
                        .addSuggestion(
                                "title_suggest",
                                SuggestBuilders.completionSuggestion("title")
                                        .prefix("s")
                                        .skipDuplicates(true)
                                        .size(10)
                        ));

        // 发起请求,获取响应对象
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 解析响应结果
        // 获取整个suggest对象
        Suggest suggest = response.getSuggest();
        // 通过指定的suggest名字,获取其对象
        CompletionSuggestion titleSuggest = suggest.getSuggestion("title_suggest");
        for (CompletionSuggestion.Entry options : titleSuggest) {
            // 获取每一个options中的test内容
            String context = options.getText().string();
            // 按需求对内容进行处理
            System.out.println("context = " + context);
        }
    }
}

代码与DSL、响应结果对应关系:

image-20230627235426570

ES与MySQL数据同步

这里的同步指的是:MySQL发生变化,则elasticsearch索引库也需要跟着发生变化

数据同步一般有三种方式:同步调用方式、异步通知方式、监听MySQL的binlog方式

1、同步调用:

  • 优点:实现简单,粗暴
  • 缺点:业务耦合度高

image-20230628155716064

2、异步通知:

  • 优点:低耦合,实现难度一般
  • 缺点:依赖mq的可靠性

image-20230628160432048

3、监听MySQL的binlog文件:

  • 优点:完全解除服务间耦合
  • 缺点:开启binlog增加数据库负担、实现复杂度高

image-20230628160321828

高级篇链接

地址:https://www.cnblogs.com/xiegongzi/p/15770665.html

posted on 2021-12-13 23:35  紫邪情  阅读(1148)  评论(7编辑  收藏  举报