ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ElasticSearch

2021-11-13 14:59:57  阅读:133  来源: 互联网

标签:count index doc 索引 ElasticSearch type id


1. ElasticSearch简介

1.1. Elasticsearch

ElasticSearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式的 RESTful 风格的搜索和数据分析引擎。
Elasticsearch 是用 Java 语言开发的,并作为 Apache 许可条款下的开放源码发布,是一种流行的企业级搜索引擎。
ElasticSearch 能够达到实时搜索,稳定,可靠,快速,安装使用方便。

特性:

  • 存储:分布式的文档存储引擎,支持PB级数据。
  • 查询和分析:分布式的搜索引擎和分析引擎。
  • 可扩展:支持一主多从且扩容简易,只要cluster.name一致且在同一个网络中就能自动加入当前集群;也支持很多开源的第三方插件,如分词插件、同步插件、Hadoop插件、可视化插件等。
  • 高可用:在一个集群的多个节点中进行分布式存储,索引支持shards和复制,即使部分节点down掉,也能自动进行数据恢复和主从切换。
  • RestfulAPI标准:通过http接口使用JSON格式进行操作数据。
  • 数据类型丰富:数字、文本、地理位置、结构化、非结构化等。
索引(indices)----------------------Databases 数据库

  类型(type)--------------------------Table 数据表(7.0版本已被弃用)

     文档(Document)----------------------Row 行 表记录

	    字段(Field)-------------------------Columns 列 

1.2. 同类产品

Solr、ElasticSearch、Hermes(腾讯)(实时检索分析)

  • Solr、ES

    1. 源自搜索引擎,侧重搜索与全文检索。
    2. 数据规模从几百万到千万不等,数据量过亿的集群特别少。
      有可能存在个别系统数据量过亿,但这并不是普遍现象(就像Oracle的表里的数据规模有可能超过Hive里一样,但需要小型机)。
  • Hermes

    1. 一个基于大索引技术的海量数据实时检索分析平台。侧重数据分析。
    2. 数据规模从几亿到万亿不等。最小的表也是千万级别。
      在 腾讯17 台TS5机器,就可以处理每天450亿的数据(每条数据1kb左右),数据可以保存一个月之久。
  • Solr、ES区别
    全文检索、搜索、分析。基于lucene

    1. Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能;
    2. Solr 支持更多格式的数据,而 Elasticsearch 仅支持json文件格式;
    3. Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供;
    4. Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch-----附近的人

Lucene是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎
搜索引擎产品简介

1.3. 相关概念

1.3.1索引

索引基本概念(indices):

索引是含义相同属性的文档集合,是 ElasticSearch 的一个逻辑存储,可以理解为关系型数据库中的数据库,ElasticSearch 可以把索引数据存放到一台服务器上,也可以 sharding 后存到多台服务器上,每个索引有一个或多个分片,每个分片可以有多个副本。

索引类型(index_type):

索引可以定义一个或多个类型,文档必须属于一个类型。在 ElasticSearch 中,一个索引对象可以存储多个不同用途的对象,通过索引类型可以区分单个索引中的不同对象,可以理解为关系型数据库中的表。每个索引类型可以有不同的结构,但是不同的索引类型不能为相同的属性设置不同的类型。

1.3.2. 文档

文档(document):

文档是可以被索引的基本数据单位。存储在 ElasticSearch 中的主要实体叫文档 document,可以理解为关系型数据库中表的一行记录。每个文档由多个字段构成,ElasticSearch 是一个非结构化的数据库,每个文档可以有不同的字段,并且有一个唯一的标识符。

1.3.3. 映射

映射(mapping):

ElasticSearch 的 Mapping 非常类似于静态语言中的数据类型:声明一个变量为 int 类型的变量,以后这个变量都只能存储 int 类型的数据。同样的,一个 number 类型的 mapping 字段只能存储 number 类型的数据。

同语言的数据类型相比,Mapping 还有一些其他的含义,Mapping 不仅告诉 ElasticSearch 一个 Field 中是什么类型的值, 它还告诉 ElasticSearch 如何索引数据以及数据是否能被搜索到。

ElaticSearch 默认是动态创建索引和索引类型的 Mapping 的。这就相当于无需定义 Solr 中的 Schema,无需指定各个字段的索引规则就可以索引文件,很方便。但有时方便就代表着不灵活。比如,ElasticSearch 默认一个字段是要做分词的,但我们有时要搜索匹配整个字段却不行。如有统计工作要记录每个城市出现的次数。对于 name 字段,若记录 new york 文本,ElasticSearch 可能会把它拆分成 new 和 york 这两个词,分别计算这个两个单词的次数,而不是我们期望的 new york。

1.2. 索引操作(indeces)

1.2.1. 初步索引

GET /_cat/nodes:查看所有节点
GET /_cat/health:查看 es 健康状况
GET /_cat/master:查看主节点
GET /_cat/indices:查看所有索引 show databases;

1.2.2. 创建索引

PUT /索引名
PUT customer

返回结果:
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "customer"
}

1.2.3. 查看索引具体信息

GET /索引名
GET customer

返回结果:
{
  "customer" : {
    "aliases" : { },
    "mappings" : { },
    "settings" : {
      "index" : {
        "creation_date" : "1635937861845",
        "number_of_shards" : "1",
        "number_of_replicas" : "1",
        "uuid" : "kVIb3V4yTy6M5NSgLCMGZg",
        "version" : {
          "created" : "7040299"
        },
        "provided_name" : "customer"
      }
    }
  }
}

1.2.4. 删除索引

DELETE /索引库名
DELETE customer

返回结果:
{
  "acknowledged" : true
}

1.3. 映射配置(_mapping)

Mapping 是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和
索引的。比如,使用 mapping 来定义:

  • 哪些字符串属性应该被看做全文本属性(full text fields)。
  • 哪些属性包含数字,日期或者地理位置。
  • 文档中的所有属性是否都能被索引(_all 配置)。
  • 日期的格式。
  • 自定义映射规则来执行动态添加属性

新版本改变
Es7 及以上移除了 type 的概念。

  • 关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用,
    但 ES 中不是这样的。elasticsearch 是基于 Lucene 开发的搜索引擎,而 ES 中不同 type
    下名称相同的 filed 最终在 Lucene 中的处理方式是一样的。
  • 两个不同 type 下的两个 user_name,在 ES 同一个索引下其实被认为是同一个 filed,
    你必须在两个不同的 type 中定义相同的 filed 映射。否则,不同 type 中的相同字段
    名称就会在处理中出现冲突的情况,导致 Lucene 处理效率下降。
  • 去掉 type 就是为了提高 ES 处理数据的效率。
    Elasticsearch 7.x
  • URL 中的 type 参数为可选。比如,索引一个文档不再要求提供文档类型。
    Elasticsearch 8.x
  • 不再支持 URL 中的 type 参数。
    解决:
    1)、将索引从多类型迁移到单类型,每种类型文档一个独立索引
    2)、将已存在的索引下的类型数据,全部迁移到指定位置即可。详见数据迁移
    索引有了,接下来肯定是添加数据。但是,在添加数据之前必须定义映射。

1.3.1. 创建映射字段

PUT /索引库名
{
	"mappings": {
		 "properties": {
   			 "字段名": {
   		     "type": "类型",
     	 	 "index": true,
     	 	 "store": true,
     		 "analyzer": "分词器"
   			 }
 		 }
	}
}

类型名称:就是前面将的type的概念,类似于数据库中的不同表

字段名:类似于列名,properties下可以指定许多字段。

每个字段可以有很多属性。例如:

  • type:类型,可以是text、long、short、date、integer、object等
  • index:是否索引,默认为true
  • store:是否存储,默认为false
  • analyzer:分词器,这里使用ik分词器:ik_max_word或者ik_smart

发起请求:

PUT atguigu
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "images": {
        "type": "keyword",
        "index": "false"
      },
      "price": {
        "type": "long"
      }
    }
  }
}

返回结果:
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "atguigu"
}

1.3.2. 查看映射关系

GET /索引库名/_mapping

GET /atguigu/_mapping

返回结果:
{
  "atguigu" : {
    "mappings" : {
      "goods" : {
        "properties" : {
          "images" : {
            "type" : "keyword",
            "index" : false
          },
          "price" : {
            "type" : "long"
          },
          "title" : {
            "type" : "text",
            "analyzer" : "ik_max_word"
          }
        }
      }
    }
  }
}

type:字段类型。String(text keyword) Numeric(long integer float double) date boolean

index:是否创建索引

analyzer:分词器(ik_max_word)

1.3.3. 更新映射

对于已经存在的映射字段,我们不能更新。更新必须创建新的索引进行数据迁移

1.3.4. 数据迁移

将旧索引的 type 下的数据进行迁移

POST _reindex 
{
  "source": {  
    "index": "旧索引名",
    "type": "旧索引的类型,如果有则需写"
  },
  "dest": {
    "index": "新索引名"
  }
}

1.4. 文档操作(document)

有了索引、类型和映射,就可以对文档做增删改查操作了。

1.4.1. 新增文档

如果我们想要自己新增的时候指定id,可以这么做:

PUT/索引库名/类型/id值
{
	---
}
PUT 和 POST 都可以,
POST 新增。如果不指定 id,会自动生成 id。指定 id 就会修改这个数据,并新增版本号
PUT 可以新增可以修改。PUT 必须指定 id;由于 PUT 需要指定 id,我们一般都用来做修改
操作,不指定 id 会报错
-------
 
PUT my-index/external/1
{ 
  "name": "John Doe"
}

返回结果:
{
  "_index" : "my-index",
  "_type" : "external",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

1.4.2. 智能判断

事实上Elasticsearch非常智能,你不需要给索引库设置任何mapping映射,它也可以根据你输入的数据来判断类型,动态添加数据映射。

测试一下:

POST /atguigu/goods/2
{
    "title":"小米手机",
    "images":"http://image.jd.com/12479122.jpg",
    "price":2899,
    "stock": 200,
    "saleable":true,
    "attr": {
        "category": "手机",
        "brand": "小米"
    }
}

我们额外添加了stock库存,saleable是否上架,attr其他属性几个字段。

来看结果:GET /atguigu/_search

{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "atguigu",
        "_type" : "goods",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "title" : "华为手机",
          "images" : "http://image.jd.com/12479122.jpg",
          "price" : 4288
        }
      },
      {
        "_index" : "atguigu",
        "_type" : "goods",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://image.jd.com/12479122.jpg",
          "price" : 2899,
          "stock" : 200,
          "saleable" : true,
          "attr" : {
            "category" : "手机",
            "brand" : "小米"
          }
        }
      }
    ]
  }
}

再看下索引库的映射关系: GET /atguigu/_mapping

{
  "atguigu" : {
    "mappings" : {
      "goods" : {
        "properties" : {
          "attr" : {
            "properties" : {
              "brand" : {
                "type" : "text",
                "fields" : {
                  "keyword" : {
                    "type" : "keyword",
                    "ignore_above" : 256
                  }
                }
              },
              "category" : {
                "type" : "text",
                "fields" : {
                  "keyword" : {
                    "type" : "keyword",
                    "ignore_above" : 256
                  }
                }
              }
            }
          },
          "images" : {
            "type" : "keyword",
            "index" : false
          },
          "price" : {
            "type" : "long"
          },
          "saleable" : {
            "type" : "boolean"
          },
          "stock" : {
            "type" : "long"
          },
          "title" : {
            "type" : "text",
            "analyzer" : "ik_max_word"
          }
        }
      }
    }
  }
}

stock,saleable,attr都被成功映射了。

如果是字符串类型的数据,会添加两种类型:text+ keyword。如上例中的category 和 brand

14.3. 更新数据

POST /索引库名/类型名/id值/_update
{
	---
}
或者
POST /索引库名/类型名/id值
{
	---
}
不同:
	POST 操作会对比源文档数据,如果相同不会有什么操作,文档 version 不增加
	PUT 操作总会将数据重新保存并增加 version 版本;
	带_update 对比元数据如果一样就不进行任何操作。
	看场景;
		对于大并发更新,不带 update;
		对于大并发查询偶尔更新,带 update;对比更新,重新计算分配规则。
---

POST customer/external/1/_update
{ 
	"name": "John Doe"
}
POST customer/external/1
{ 
	"name": "John Doe"
}

更新时增加新属性 需用 doc
POST customer/external/1/_update
{ 
	"doc": {
		 "name": "Jane Doe",
		  "age": 20 
	 }
}


1.4.6. 删除数据

删除使用DELETE请求,同样,需要根据id进行删除:

DELETE /索引库名/类型名/id值

DELETE /atguigu/goods/3

返回结果:
{
  "_index" : "atguigu",
  "_type" : "goods",
  "_id" : "3",
  "_version" : 2,
  "result" : "deleted",
  "_shards" : {
    "total" : 4,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

14.7. bulk 批量 API

POST /索引库名/类型名/_bulk
{ action: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n

action: index, create, delete and update

1、index ,create 下一行需要资源。(已存在的情况下create 会失败,index 则会添加或者替换已有的文档);
{"index": {"_index": 索引名称, "_id": 文档ID1}} 
{新增文档1内容}
{"creade": {"_index": 索引名称, "_id": 文档ID1}} 
{新增文档1内容}

2、delete 下一行不需要资源;
{"delete": {"_index": 索引名称, "_id": 文档ID1}} 

3、update 需要部分文档,upsert and script and its options 需要在下一行指明。
{"update": {"_index": 索引名称, "_id": 文档ID1}} 
{"doc": {文档1内容} }

-----------------------------
POST customer/external/_bulk
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }

复杂示例:
POST /_bulk
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }}
{ "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123"} }
{ "doc" : {"title" : "My updated blog post"} }

bulk API 以此按顺序执行所有的 action(动作)。如果一个动作因任何原因而失败,
它将继续处理它后面剩余的动作。当 bulk API 返回时,它将提供每个动作的状态(与发送
的顺序相同),所以您可以检查是否一个指定的动作是不是失败了。

进阶检索

2. SearchAPI

准备数据:

https://gitee.com/tax_yuan_hao/es-learning-json-data/blob/master/json%E6%95%B0%E6%8D%AE.json

ES 支持两种基本方式检索 :

  • 一个是通过使用 REST request URI 发送搜索参数(uri+检索参数)
  • 另一个是通过使用 REST request body 来发送它们(uri+请求体)

查询指定索引下的所有信息:

GET /{index}/_search
GET /{index}/{type}/{id} 根据id查询:

GET bank/_search 检索 bank 下所有信息,包括 type 和 docs
GET bank/_search?q=*&sort=account_number:asc 请求参数方式检索

elasticsearch作为搜索引擎,最复杂最强大的功能就是搜索查询功能。包括:匹配查询、词条查询、模糊查询、组合查询、范围查询、高亮、排序、分页等等查询功能。

基本查询语法如下:

GET /索引名/_search
{
    "query":{
        "查询类型":{
            "查询条件":"查询条件值"
        }
    }
}


# 查询bank索引中年龄再10岁到20岁之间的并按降序排序,显示一条数据
GET /bank/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 10,
        "lte": 20
      }
    }
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ],
  "size": 1
}

返回结果:
{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 44,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "157",
        "_score" : null,
        "_source" : {
          "account_number" : 157,
          "balance" : 39868,
          "firstname" : "Claudia",
          "lastname" : "Terry",
          "age" : 20,
          "gender" : "F",
          "address" : "132 Gunnison Court",
          "employer" : "Lumbrex",
          "email" : "claudiaterry@lumbrex.com",
          "city" : "Castleton",
          "state" : "MD"
        },
        "sort" : [
          20
        ]
      }
    ]
  }
}

这里的query代表一个查询对象,里面可以有不同的查询属性

  • 查询类型: 例如:match_allmatchtermrange 等等
  • 查询条件:查询条件会根据类型的不同,写法也有差异,后面详细讲解

查询结果:

  • took:查询花费时间,单位是毫秒
  • time_out:是否超时
  • _shards:分片信息
  • hits:搜索结果总览对象
    • total:搜索到的总条数
    • max_score:所有结果中文档得分的最高分
    • hits:搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
      • _index:索引库
      • _type:文档类型
      • _id:文档id
      • _score:文档得分
      • _source:文档的源数据

2.1. Query DSL

POST /atguigu/goods/_bulk
{"index":{"_id":1}}
{ "title":"小米手机", "images":"http://image.jd.com/12479122.jpg", "price":1999, "stock": 200, "attr": { "category": "手机", "brand": "小米" } }
{"index":{"_id":2}}
{"title":"超米手机", "images":"http://image.jd.com/12479122.jpg", "price":2999, "stock": 300, "attr": { "category": "手机", "brand": "小米" } }
{"index":{"_id":3}}
{ "title":"小米电视", "images":"http://image.jd.com/12479122.jpg", "price":3999, "stock": 400, "attr": { "category": "电视", "brand": "小米" } }
{"index":{"_id":4}}
{ "title":"小米笔记本", "images":"http://image.jd.com/12479122.jpg", "price":4999, "stock": 200, "attr": { "category": "笔记本", "brand": "小米" } }
{"index":{"_id":5}}
{ "title":"华为手机", "images":"http://image.jd.com/12479122.jpg", "price":3999, "stock": 400, "attr": { "category": "手机", "brand": "华为" } }
{"index":{"_id":6}}
{ "title":"华为笔记本", "images":"http://image.jd.com/12479122.jpg", "price":5999, "stock": 200, "attr": { "category": "笔记本", "brand": "华为" } }
{"index":{"_id":7}}
{ "title":"荣耀手机", "images":"http://image.jd.com/12479122.jpg", "price":2999, "stock": 300, "attr": { "category": "手机", "brand": "华为" } }
{"index":{"_id":8}}
{ "title":"oppo手机", "images":"http://image.jd.com/12479122.jpg", "price":2799, "stock": 400, "attr": { "category": "手机", "brand": "oppo" } }
{"index":{"_id":9}}
{ "title":"vivo手机", "images":"http://image.jd.com/12479122.jpg", "price":2699, "stock": 300, "attr": { "category": "手机", "brand": "vivo" } }
{"index":{"_id":10}}
{ "title":"华为nova手机", "images":"http://image.jd.com/12479122.jpg", "price":2999, "stock": 300, "attr": { "category": "手机", "brand": "华为" } }

2.2. 匹配查询(match)

GET /atguigu/_search
{
    "query":{
        "match_all": {}
    }
}
  • query:代表查询对象
  • match_all:代表查询所有
GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  }
}

查询出很多数据,不仅包括小米手机,而且与小米或者手机相关的都会查询到,es会把数据类型为text的数据进行分词查询,但某些情况下,我们需要更精确查找,可以这样做字段名.keyword,es就不会进行分词
或者使用match_phrase将需要匹配的值当成一个整体单词(不分词)进行检索

GET /atguigu/_search
{
  "query": {
    "match": {
      "title.keyword": "小米手机"
    }
  }
}

=========或者
GET /atguigu/_search
{
  "query": {
    "match_phrase": {
      "title": "小米手机"
    }
  }
}

返回结果:
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.9924302,
    "hits" : [
      {
        "_index" : "atguigu",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.9924302,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://image.jd.com/12479122.jpg",
          "price" : 1999,
          "stock" : 200,
          "attr" : {
            "category" : "手机",
            "brand" : "小米"
          }
        }
      }
    ]
  }
}

## 子属性匹配
GET /atguigu/_search
{
  "query": {
    "match": {
      "attr.brand": "小米"
    }
  }
}

match只能根据一个字段匹配查询,如果要根据多个字段匹配查询可以使用multi_match

## 查询title中包含小米,或attr的子属性brand为小米的所有信息
GET /atguigu/_search
{
    "query":{
        "multi_match": {
            "query": "小米",
            "fields": ["title", "attr.brand.keyword"]
        }
	}
}

2.3. 词条查询(term)

term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串。

GET /atguigu/_search
{
    "query":{
        "term":{
            "price": 4999
        }
    }
}

2.4. 范围查询(range)

range 查询找出那些落在指定区间内的数字或者时间

GET /atguigu/_search
{
    "query":{
        "range": {
            "price": {
                "gte":  1000,
                "lt":   3000
            }
    	}
    }
}

range查询允许以下字符:

操作符说明
gt大于
gte大于等于
lt小于
lte小于等于

2.5. 布尔组合(bool)

布尔查询又叫组合查询

bool把各种其它查询通过must(与)、must_not(非)、should(或)的方式进行组合

  • must:必须达到 must 列举的所有条件
  • must_not 必须不是指定的情况
  • should:应该达到 should 列举的条件,如果达到会增加相关文档的评分,并不会改变
    查询的结果。如果 query 中只有 should 且只有一种匹配规则,那么 should 的条件就会
    被作为默认匹配条件而去改变查询结果

注意: 一个组合查询里面只能出现一种组合,不能混用

## 查询价格在1000-3000 且在2000 到4000范围内的数据
GET /atguigu/_search
{
    "query":{
        "bool":{
        	"must": [
        	  {
        	    "range": {
        	      "price": {
        	        "gte": 1000,
        	        "lte": 3000
        	      }
        	    }
        	  },
        	  {
        	    "range": {
        	      "price": {
        	        "gte": 2000,
        	        "lte": 4000
        	      }
        	    }
        	  }
        	]
        }
    }
}

2.6. 过滤(filter)

所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter方式:

GET /atguigu/_search
{
  "query": {
    "bool": {
      "must": {
        "match": { "title": "小米手机" }
      },
      "filter": {
        "range": {
          "price": { "gt": 2000, "lt": 3000 }
        }
      }
    }
  }
}

2.7. 排序(sort)

sort 可以让我们按照不同的字段进行排序,并且通过order指定排序的方式

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  },
  "sort": [
    {
      "price": { "order": "desc" }
    },
    {
      "_score": { "order": "desc"}
    }
  ]
}

2.8. 分页(from/size)

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  },
  "from": 2,
  "size": 2
}

from:从那一条开始

size:取多少条

2.10. 结果过滤(_source)

默认情况下,elasticsearch在搜索的结果中,会把文档中保存在_source的所有字段都返回。

如果我们只想获取其中的部分字段,可以添加_source的过滤

GET /atguigu/_search
{
  "_source": ["title","price"],
  "query": {
    "term": {
      "price": 2699
    }
  }
}

返回结果:
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "atguigu",
        "_type" : "goods",
        "_id" : "9",
        "_score" : 1.0,
        "_source" : {
          "price" : 2699,
          "title" : "vivo手机"
        }
      }
    ]
  }
}

3. 聚合(aggregations)

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

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

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

3.1 基本概念

Elasticsearch中的聚合,包含多种类型,最常用的两种,一个叫,一个叫度量

桶(bucket)

桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个,例如我们根据国籍对人划分,可以得到中国桶英国桶日本桶……或者我们按照年龄段对人进行划分:010,1020,2030,3040等。

Elasticsearch中提供的划分桶的方式有很多:

  • Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组
  • Histogram Aggregation:根据数值阶梯分组,与日期类似
  • Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组
  • Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组
  • ……

bucket aggregations 只负责对数据进行分组,并不进行计算,因此往往bucket中往往会嵌套另一种聚合:metrics aggregations即度量

度量(metrics)

分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量

比较常用的一些度量聚合方式:

  • Avg Aggregation:求平均值
  • Max Aggregation:求最大值
  • Min Aggregation:求最小值
  • Percentiles Aggregation:求百分比
  • Stats Aggregation:同时返回avg、max、min、sum、count等
  • Sum Aggregation:求和
  • Top hits Aggregation:求前几
  • Value Count Aggregation:求总数
  • ……

3.2 聚合为桶

首先,我们按照手机的品牌attr.brand.keyword来划分

GET /atguigu/_search
{
    "size" : 0,
    "aggs" : { 
        "brands" : { 
            "terms" : { 
              "field" : "attr.brand.keyword"
            }
        }
    }
}
  • size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
  • aggs:声明这是一个聚合查询,是aggregations的缩写
    • brands:给这次聚合起一个名字,任意。
      • terms:划分桶的方式,这里是根据词条划分
        • field:划分桶的字段
### 结果:

{
  "took" : 124,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 10,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "brands" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "华为",
          "doc_count" : 4
        },
        {
          "key" : "小米",
          "doc_count" : 4
        },
        {
          "key" : "oppo",
          "doc_count" : 1
        },
        {
          "key" : "vivo",
          "doc_count" : 1
        }
      ]
    }
  }
}
  • hits:查询结果为空,因为我们设置了size为0
  • aggregations:聚合的结果
  • brands:我们定义的聚合名称
  • buckets:查找到的桶,每个不同的品牌字段值都会形成一个桶
    • key:这个桶对应的品牌字段的值
    • doc_count:这个桶中的文档数量

3.3 桶内度量

前面的例子告诉我们每个桶里面的文档数量,这很有用。 但通常,我们的应用需要提供更复杂的文档度量。 例如,每种品牌手机的平均价格是多少?

因此,我们需要告诉Elasticsearch使用哪个字段使用何种度量方式进行运算,这些信息要嵌套在内,度量的运算会基于内的文档进行

现在,我们为刚刚的聚合结果添加 求价格平均值的度量:

## 统计attr中的(brand)品牌有多少种,再求各品牌商品的平均价格
GET /atguigu/_search
{
  "aggs": {
    "brands": {
      "terms": {
        "field": "attr.brand.keyword"
      },
      "aggs": {
        "price_avg": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  },
  "size": 0
}
  • aggs:我们在上一个aggs(brands)中添加新的aggs。可见度量也是一个聚合
  • price_avg:聚合的名称
  • avg:度量的类型,这里是求平均值
  • field:度量运算的字段
返回结果:
{
  "took" : 19,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brand_count" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "华为",
          "doc_count" : 4,
          "price_avg" : {
            "value" : 3999.0
          }
        },
        {
          "key" : "小米",
          "doc_count" : 4,
          "price_avg" : {
            "value" : 3499.0
          }
        },
        {
          "key" : "oppo",
          "doc_count" : 1,
          "price_avg" : {
            "value" : 2799.0
          }
        },
        {
          "key" : "vivo",
          "doc_count" : 1,
          "price_avg" : {
            "value" : 2699.0
          }
        }
      ]
    }
  }
}

可以看到每个桶中都有自己的price_avg字段,这是度量聚合的结果

3.4 桶内嵌套桶

刚刚的案例中,我们在桶内嵌套度量运算。事实上桶不仅可以嵌套运算, 还可以再嵌套其它桶。也就是说在每个分组中,再分更多组。

比如:我们想统计每个品牌都生产了那些产品,按照attr.category.keyword字段再进行分桶


## 统计attr中的(brand)品牌有多少种,再求各品牌商品的平均价格,且统计各品牌中商品的类别(category)
GET /atguigu/_search
{
    "size" : 0,
    "aggs" : { 
        "brands" : { 
            "terms" : { 
              "field" : "attr.brand.keyword"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                },
                "categorys": {
                  "terms": {
                    "field": "attr.category.keyword"
                  }
                }
            }
        }
    }
}


=======================================
##返回结果:
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "brands" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "华为",
          "doc_count" : 4,
          "categorys" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "手机",
                "doc_count" : 3
              },
              {
                "key" : "笔记本",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 3999.0
          }
        },
        {
          "key" : "小米",
          "doc_count" : 4,
          "categorys" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "手机",
                "doc_count" : 2
              },
              {
                "key" : "电视",
                "doc_count" : 1
              },
              {
                "key" : "笔记本",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 3499.0
          }
        },
        {
          "key" : "oppo",
          "doc_count" : 1,
          "categorys" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "手机",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 2799.0
          }
        },
        {
          "key" : "vivo",
          "doc_count" : 1,
          "categorys" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "手机",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 2699.0
          }
        }
      ]
    }
  }
}
  • 我们可以看到,新的聚合categorys被嵌套在原来每一个brands的桶中。
  • 每个品牌下面都根据 attr.category.keyword字段进行了分组
  • 我们能读取到的信息:
    • 华为有4中产品
    • 华为产品的平均售价是 3999.0美元。
    • 其中3种手机产品,1种笔记本产品

练习:

1、搜索 address 中包含 mill 的所有人的年龄分布以及平均年龄,但不显示这些人的详情
GET /bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  },
  "aggs": {
    "group_by_age": {
      "terms": {
        "field": "age"
      }
    },
    "avg_age":{
      "avg": {
        "field": "age"
      }
    }
  },
  "size": 0
}

===========
返回结果:
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "avg_age" : {
      "value" : 34.0
    },
    "group_by_age" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : 38,
          "doc_count" : 2
        },
        {
          "key" : 28,
          "doc_count" : 1
        },
        {
          "key" : 32,
          "doc_count" : 1
        }
      ]
    }
  }
}

2、按照年龄聚合,并且请求这些年龄段的这些人的平均薪资 
GET /bank/_search
{
  "aggs": {
    "group_by_age": {
      "terms": {
        "field": "age",
        "size": 1 #表示只返回一条数据
      },
      "aggs": {
        "avg_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 0
}

========
返回结果:
{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1000,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_age" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 939,
      "buckets" : [
        {
          "key" : 31,
          "doc_count" : 61,
          "avg_balance" : {
            "value" : 28312.918032786885
          }
        }
      ]
    }
  }
}




3、查出所有年龄分布,并且这些年龄段中 男性 的平均薪资和 女性 的平均薪资以及这个年龄段的总体平均薪资
GET /bank/_search
{
  "aggs": {
    "group_by_age": {
      "terms": {
        "field": "age",
        "size": 1
      },
      "aggs": {
        "gender_agg": {
          "terms": {
            "field": "gender.keyword"
          },
          "aggs": {
            "avg_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    },
    "avg_balance": {
      "avg": {
        "field": "balance"
      }
    }
  },
  "size": 0
}

================
返回结果:
{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1000,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_age" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 939,
      "buckets" : [
        {
          "key" : 31,
          "doc_count" : 61,
          "gender_agg" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "M",
                "doc_count" : 35,
                "avg_balance" : {
                  "value" : 29565.628571428573
                }
              },
              {
                "key" : "F",
                "doc_count" : 26,
                "avg_balance" : {
                  "value" : 26626.576923076922
                }
              }
            ]
          }
        }
      ]
    },
    "avg_balance" : {
      "value" : 25714.837
    }
  }
}

分词

一个 tokenizer(分词器)接收一个字符流,将之分割为独立的 tokens(词元,通常是独立的单词),然后输出 tokens流。
例如,whitespace tokenizer 遇到空白字符时分割文本。它会将文本 “Quick brown fox!” 分割为 [Quick, brown, fox!]。该 tokenizer(分词器)还负责记录各个 term(词条)的顺序或 position位置(用于 phrase
语和 word proximity 词近邻查询),以及 term(词条)所代表的原始 word(单词)的 start(起始)和 end(结束)的 character offsets(字符偏移量)(用于高亮显示搜索的内容)。
Elasticsearch 提供了很多内置的分词器,可以用来构建 custom analyzers(自定义分词器)。

安装 ik 分词器

注意:不能用默认 elasticsearch-plugin install xxx.zip 进行自动安装
下载 es 版本相同的ik版本安装

  • 进入 es 的 plugins 目录 安装ES时设置了文件挂载所以在进入外部的plugins目录上传下载的zip就行
  • unzip 解压下载的文件
  • rm –rf *.zip 解压完后一定要删除压缩包 不然ES不能正常启动

确认是否安装好了分词器:
进入 es 容器内部 plugins 目录

  • docker exec -it 容器 id /bin/bash
  • cd …/bin
  • elasticsearch plugin list:即可列出系统的分词器

测试分词器

1、默认分词器
POST _analyze
{
  "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
    }
  ]
}

2、使用ik_smart分词器
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
    }
  ]
}

3、使用ik_max_word分词器
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
    }
  ]
}


根据返回结果可以看出,默认的分词器对中文分词不友好,只是把每次字单独拆分,还有写词语没有分出来

4. SpringData-Elasticsearch

官网文档链接

目前市面上有两类客户端:一类是TransportClient 为代表的ES原生客户端,不能执行原生dsl语句必须使用它的Java api方法。另外一种是以Rest Api为主的missing client,最典型的就是jest。 这种客户端可以直接使用dsl语句拼成的字符串,直接传给服务端,然后返回json字符串再解析。两种方式各有优劣,但是最近elasticsearch官网,宣布计划在7.0以后的版本中废除TransportClient。以RestClient为主。由于原生的Elasticsearch客户端API非常麻烦。所以这里直接学习Spring提供的套件:Spring Data Elasticsearch。
注:spring-data-Elasticsearch 使用之前,必须先确定版本,elasticsearch 对版本的要求比较高。

4.1. 创建module

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

配置es客户端
官网地址

  @Bean
    public RestHighLevelClient esRestConfig(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                new HttpHost("192.168.126.129", 9200, "http")));
        return client;
    }

配置请求
官网文档

    public static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//        builder.addHeader("Authorization", "Bearer " + TOKEN);Bearer
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }

简单测试 ,新增索引

 @Autowired
    private RestHighLevelClient restClient;
@Test
    public void testResElastic() throws IOException {
    	//创建索引请求
        IndexRequest request = new IndexRequest("user");
        User user = new User();
        user.setAge("18");
        user.setName("好");
        user.setSex("男");
        //往请求中加入json数据
        request.source(JSONObject.toJSONString(user), XContentType.JSON);
        //发送给es创建
        IndexResponse index = restClient.index(request, GulimallElasticSearchConfig.COMMON_OPTIONS);
        System.out.println(index);
    }

复杂测试:

    @Test
    public void searchData() throws IOException {
        //创建索引请求
        SearchRequest searchRequest = new SearchRequest("bank");
        //DS构造器
        SearchSourceBuilder searchBuilder = new SearchSourceBuilder();

        searchBuilder.query(QueryBuilders.matchQuery("address","mill"));

        //构建聚合
        searchBuilder.aggregation(AggregationBuilders.terms("group_by_age").field("age"));
        searchBuilder.aggregation(AggregationBuilders.avg("avg_age").field("age"));
        //指定DSL,检索条件
        searchRequest.source(searchBuilder);
        System.out.println("builder:"+searchRequest);

        //执行索引
        SearchResponse search = restClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        System.out.println("返回结果:"+search.toString());

        //解析数据
        SearchHits hits = search.getHits();
        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString();
            count count = JSONObject.parseObject(sourceAsString, count.class);
            System.out.println("数据:"+count);
        }

        //解析聚合数据
        Aggregations aggregations = search.getAggregations();
        System.out.println("001"+aggregations);
        Terms terms = aggregations.get("group_by_age");
        for (Terms.Bucket bucket : terms.getBuckets()) {
            System.out.println("年龄:"+bucket.getKey()+"===共:"+bucket.getDocCount());
        }

    }

es语句

GET bank/_search
{
	"query": {
		"match": {
			"address": {
				"query": "mill"
			}
		}
	},
	"aggs": {
		"group_by_age": {
			"terms": {
				"field": "age",
				"size": 10
			}
		},
		"avg_age": {
			"avg": {
				"field": "age"
			}
		}
	}
}

标签:count,index,doc,索引,ElasticSearch,type,id
来源: https://blog.csdn.net/qq_46519504/article/details/121127361

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有