ElasticSearch学习笔记

文章目录

一、简介

全文搜索属于最常见的需求,开源的 Elasticsearch (以下简称 Elastic)是目前全文搜索引擎的首选。它可以快速地储存、搜索和分析海量数据。
Elastic 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的接口。Elastic 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。

二、安装

官网:https://www.elastic.co/cn/elasticsearch

1. 使用 docker 安装

# 1. 拉取镜像
# 存储和检索数据
docker pull elasticsearch:7.4.2
# 可视化检索数据
docker pull kibana:7.4.2

# 2. 配置挂载数据文件夹
# 创建配置文件目录
mkdir -p /mydata/elasticsearch/config
# 创建数据目录
mkdir -p /mydata/elasticsearch/data
# 将/mydata/elasticsearch/文件夹中文件都可读可写
chmod -R 777 /mydata/elasticsearch/
# 配置任意机器可以访问 elasticsearch
echo "http.host: 0.0.0.0" >/mydata/elasticsearch/config/elasticsearch.yml

# 3. 编写 docker-compose 文件
version: "3.8"
services:
  elasticsearch:
    image: elasticsearch:7.4.2
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      discovery.type: "single-node"
      ES_JAVA_OPTS: "-Xms64m -Xmx512m"
    volumes:
      - "/mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml"
      - "/mydata/elasticsearch/data:/usr/share/elasticsearch/data"
      - "/mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins"
    restart: always
  kibana:
    image: kibana:7.4.2
    ports:
      - "5601:5601"
    environment:
      ELASTICSEARCH_HOSTS: "http://10.211.55.12:9200" # 注意,这里要指定上面es的地址
    restart: always

2. 查看 ES 是否安装成功

访问 IP:9200 看到返回的 json 数据说明启动成功。
在这里插入图片描述

3. 查看 Kibana 是否安装成功

访问 IP:5601 测试:
在这里插入图片描述

4. 汉化

进入容器,打开 Kibana 配置文件:config/kibana.yml,在最后一行添加如下代码:

# 进入容器
docker exec -it dockercompose_kibana_1 /bin/bash

# 打开 Kibana 配置文件
vi config/kibana.yml
#在文件最后一行添加
i18n.locale: "zh-CN"

# 退出容器后重启容器
docker restart dockercompose_kibana_1

5. 向 es 发送请求

Kibana 左侧菜单栏中有个 Dev Tools(开发工具),可以在这里写请求操作 es,并且有自动补全的提示,后续的话,我在这里写的请求

三、基本概念

Elasticsearch 是面向文档的,一切都是 JSON

索引(Index)

Elastic 会索引所有字段,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。
所以,Elastic 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。

  • 动词,相当于 mysql 的 insert
  • 名词,相当于 mysql 的 database

类型(Type)(过时)

在 Index(索引)中,可以定义一个或多个类型。
类似于 MySQL 的 Table,每一种类 型的数据存放在一起。
在 Elasticsearch8.0 之后,Type 类型被移除。

文档(Document)

存储在 Elasticsearch 中的主要实体叫文档(Document)。用关系型数据库来类比的话,一个文档相当于数据库表中的一行记录。Elasticsearch 和 MongoDB 中的文档类似,都可以有不同的结构,但 Elasticsearch 的文档中,相同字段必须有相同类型。文档由多个字段组成,每个字段可能多次出现在一个文档里,这样的字段叫多值字段(multivalued)。每个字段的类型,可以是文本、数值、日期等。字段类型也可以是复杂类型,一个字段包含其他子文档或者数组。

在这里插入图片描述
在这里插入图片描述

映射(Mapping)

  • 所有文档写进索引之前都会先进行分析,如何将输入的文本分割为词条、哪些词条又会被过滤,这种行为叫做映射。一般由用户自己定义规则。详见第七章。

分片与副本(Shards & Replicas)

四、ElasticSearch 使用

1. _cat(查看ES信息)

  • /_cat/nodes:查看所有节点
  • /_cat/health:查看ES健康状况
  • /_cat/master:查看主节点信息
  • /_cat/indicies:查看所有索引

2. 索引的CRUD

创建索引,PUT请求:
PUT localhost:9200/{索引名}
{
  // 常用的创建规则,根据需要配置
  "settings":{
    // 一些设置
    "index":{
      "number_of_shards":"2",	// 分片数
      "number_of_replicas":"0"	// 副本数
    }
  },
  "mappings": {
    "properties": {
      // 这里配置字段的类型
      "字段名": {
        "type" : "字段类型"
      }
    }
  }
}
删除索引,DELETE 请求:
DELETE localhost:9200/{索引名}
修改索引,GET 请求:
查询索引,GET 请求:
GET /{索引名}

3. 查询出的文档结构字段说明

在 Elasticsearch 中,文档以 JSON 格式进行存储,可以是复杂的结构

元数据(metadata)

查询一个文档时,返回的不只文档的数据。它还包含了元数据(metadata)——关于文档的信息。常见的元数据有:

节点说明
_index文档存储的索引
_type文档代表的类,推荐是 _doc
_id文档的唯一标识
_score文档的分数,当查询出多条结果时,匹配度越高则分值越高
_source文档中的数据
_version文档的版本号,增删改时会自增 1
found是否存在数据

例如:这是一条查询语句查询出的结果:

{
  "_index" : "test3",
  "_type" : "_doc",
  "_id" : "5",
  "_version" : 17,
  "_seq_no" : 25,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "name" : "赵六",
    "age" : 26
  }
}

4. 文档的 CRUD

类型名称推荐使用 _doc

methodurl地址描述
PUTlocalhost:9200 / { 索引名称 } / { 类型名称 } / { 文档id }创建文档(指定文档id)
POSTlocalhost:9200 / { 索引名称 } / { 类型名称 }创建文档(随机文档id)
DELETElocalhost:9200 / { 索引名称 } / { 类型名称 } / { 文档id }删除文档
POSTlocalhost:9200 / { 索引名称 } / _update / { 文档id }修改文档
GETlocalhost:9200 / { 索引名称 } / { 类型名称 } / { 文档id }通过文档 id 查询文档
POSTlocalhost:9200 / { 索引名称 } / _search查询所有数据

添加文档,PUT请求:

PUT /test3/_doc/5
{
  "name": "张三",
  "age": 23,
  "desc": "法外狂徒",
}

删除文档,DELETE请求:

DELETE /test3/_doc/5

修改文档,PUT请求或者POST请求:

  • PUT请求,通过覆盖的方式进行更新,如果有字段没有值,那么值就为空:
PUT /test3/_doc/5
{
  "name": "李四",
}
  • POST请求,如下代码,将要修改的字段放到 doc 中。使用该方法只会修改指定的值,其余值不做修改。
POST /test3/_update/5
{
  "doc": {
    "age": 100
  }
}

注意:每次对同一个 id 值进行增、删、改时,该 id 对应的文档中的 _version 都会加 1 ,用于乐观锁

几种更新文档的区别

在上面索引文档即保存文档的时候介绍,还有两种更新文档的方式:

  • 当PUT请求带id,且有该id数据存在时,会更新文档;
  • 当POST请求带id,与PUT相同,该id数据已经存在时,会更新文档;
    这两种请求类似,即带id,且数据存在,就会执行更新操作。
    类比:
  • 请求体的报文格式不同,_update方式要修改的数据要包裹在 doc 键下
  • _update方式不会重复更新,数据已存在不会更新,版本号不会改变,另两种方式会重复更新(覆盖原来数据),版本号会改变
  • 这几种方式在更新时都可以增加属性,PUT请求带id更新和POST请求带id更新,会直接覆盖原来的数据,不会在原来的属性里面新增属性

查询文档,GET请求(简单的查询):

根据 id 查询:

GET test3/_doc/5
GET test3/_search?q=name:李四

5. bulk-批量操作数据

语法格式:

{action:{metadata}}   # 例如index保存记录,update更新
{request body  }
{action:{metadata}}
{request body  }
  1. 指定索引和类型的批量操作
POST /customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"John Doe"}
  1. 对所有索引执行批量操作
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 返回时,它将提供每个动作的状态(与发送的顺序相同),所以您可以检查是否一个指定的动作是否失败了。

6. 检索示例介绍

下面的请求都是在 Kibana dev-tools 操作

请求接口

GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": "asc"
    }
  ]
}
# query 查询条件
# sort 排序条件

结果

{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1000,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "0",
        "_score" : null,
        "_source" : {
          "account_number" : 0,
          "balance" : 16623,
          "firstname" : "Bradshaw",
          "lastname" : "Mckenzie",
          "age" : 29,
          "gender" : "F",
          "address" : "244 Columbus Place",
          "employer" : "Euron",
          "email" : "bradshawmckenzie@euron.com",
          "city" : "Hobucken",
          "state" : "CO"
        },
        "sort" : [
          0
        ]
      },
      ...
    ]
  }
}

响应字段解释

  • took:Elasticsearch运行查询所用的时间(毫秒)
  • timed_out:搜索请求是否超时
  • _shards:搜索了多少个碎片,以及成功、失败或跳过的碎片的详细信息
  • max_score:找到的最相关文档的分数
  • hits.total.value:找到了多少匹配的文件
  • hits.sort:文档的排序位置(不按相关性分数排序时)
  • hits._score:文档的相关性得分(使用match_all时不适用)

响应结果说明

Elasticsearch 默认会分页返回10条数据,不会一下返回所有数据。
请求方式说明
ES支持两种基本方式检索;

  • 通过REST request uri 发送搜索参数 (uri +检索参数);
  • 通过REST request body 来发送它们(uri+请求体);
    也就是说除了上面示例的请求接口,根据请求体进行检索外;
    还可以用GET请求参数的方式检索:
GET bank/_search?q=*&sort=account_number:asc
# q=* 查询所有
# sort=account_number:asc 按照account_number进行升序排列

五、Query DSL

本小节参考官方文档:Query DSL

1. match查询

match 查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。

match 会先分析文档,然后再通过分析的文档进行查询

模糊查询-文本字符串

如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析 match 一下查询字符:

# 查找匹配 address 包含 mill 或 lane 的数据
GET bank/_search
{
  "query": {
    "match": {
      "address": "mill lane"
    }
  }
}

match 即全文检索,对检索字段进行分词匹配,会按照响应的评分 _score 排序,原理是倒排索引。

如果用 match 下指定了一个确切值,在遇到数字,日期,布尔值或者 not_analyzed 的字符串时,它将为你搜索你
给定的值:

精确查询-基本数据类型(非文本)
GET bank/_search
{
  "query": {
    "match": {
      "account_number": 20
    }
  }
}
# 查找匹配 account_number 为 20 的数据 非文本推荐使用 term

term 不会对查询的条件进行分词,keyword 不会对存储的数据进行分词

精确匹配

如果不希望对 text 进行分词查询,则使用

GET bank/_search
{
  "query": {
    "match": {
      "address.keyword": "288 Mill Street"
    }
  }
}

2. match_phrase-短语匹配

将需要匹配的值当成一整个单词(不分词)进行检索

GET bank/_search
{
  "query": {
    "match_phrase": {
      "address": "mill lane"
    }
  }
}
# 这里会检索 address 匹配包含短语 mill lane 的数据

3. multi_math-多字段匹配

GET bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill",
      "fields": [
        "city",
        "address"
      ]
    }
  }
}
# 检索 city 或 address 匹配包含 mill 的数据,会对查询条件分词

4. term查询

term 主要用于精确匹配哪些值,用于非 text 字段,比如数字,日期,布尔值。
避免使用 term 查询文本字段,默认情况下,Elasticsearch 会通过 analysis 分词将文本字段的值拆分为一部分,这使精确匹配文本字段的值变得困难。如果要查询文本字段值,请使用 match 查询代替。

{ "term": { "age": 26 }}
{ "term": { "date": "2014-09-01" }}
{ "term": { "public": true }}
{ "term": { "tag": "full_text" }}
POST	127.0.0.1:9200/itcast/person/_search
{
    "query":{
        "term":{
            "age":20
        }
    }
}

5. terms查询

terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配:

POST	127.0.0.1:9200/itcast/person/_search
{
    "query":{
        "terms":{
            "age" : [20,21]
        }
    }
}

6. range查询

range 过滤允许我们按照指定范围查找一批数据:
范围操作符包含:

  • gt:大于
  • gte:大于等于
  • lt:小于
  • lte:小于等于
POST test3/_search
{
  "query":{
    "range":{
      "age":{
        "gte":20,
        "lte":22
      }
    }
  }
}

7. exists 查询

exists 查询可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的 IS_NULL 条件

POST	127.0.0.1:9200/itcast/person/_search
{
    "query":{
        "exists":{
            "field": "hobby"
        }
    }
}
# 找出存在 hobby 字段的数据

8. bool查询

bool 查询可以用来合并多个条件查询结果的布尔逻辑,它包含一下操作符:

  • must:多个查询条件的完全匹配,相当于 and
  • must_not:多个查询条件的相反匹配,相当于 not
  • should:至少有一个查询条件匹配,相当于 or
    这些参数可以分别继承一个查询条件或者一个查询条件的数组:
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "gender": "M"
          }
        },
        {
          "match": {
            "address": "mill"
          }
        }
      ]
    }
  }
}
# 查询 gender 为 M 且 address 包含 mill 的数据

在boolean查询中,must, should 和must_not 元素都被称为查询子句。文档是否符合每个“must”或“should”子句中的标准,决定了文档的“相关性得分”。 得分越高,文档越符合您的搜索条件。 默认情况下,Elasticsearch 返回根据这些相关性得分排序的文档。

“must_not”子句中的条件被视为“filter”。它影响文档是否包含在结果中,但不影响文档的评分方式。还可以显式地指定任意过滤器来包含或排除基于结构化数据的文档。

9. 过滤查询 filter

并不是所有的查询都需要产生分数,特别是哪些仅用于 filtering 过滤的文档。为了不计算分数,elasticsearch 会自动检查场景并且优化查询的执行。
filter 对结果进行过滤,且不计算相关性得分

GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "address": "mill"
          }
        }
      ],
      "filter": {
        "range": {
          "balance": {
            "gte": "10000",
            "lte": "20000"
          }
        }
      }
    }
  }
}
# 这里先是查询所有匹配 address 包含 mill 的文档,
# 然后再根据 10000<=balance<=20000 进行过滤查询结果

10. sort 文档排序

GET test3/_search
{
  "sort": [
    {
      "字段名": {
        "order": "desc"	// asc 为升序
      }
    }
  ]
}

11. 分页 form/size

这两个参数与 MySQL 中 limit 中的两个参数相同

  • from:跳过几个文档
  • size:选取多少个文档
GET test3/_search
{
  "from": 0,
  "size": 20
}

12. 嵌套查询:nested

当一个字段中是一个对象(json字符串)时,需要使用 nested 嵌套查询,嵌套查询搜索嵌套字段对象时,就像它们被索引为单独的文档一样。如果对象与搜索匹配,嵌套查询将返回根父文档。

GET /my-index-000001/_search
{
  "query": {
    "nested": {
      "path": "obj1",
      "query": {
        "bool": {
          "must": [
            { "match": { "obj1.name": "blue" } },
            { "range": { "obj1.count": { "gt": 5 } } }
          ]
        }
      },
      "score_mode": "avg"
    }
  }
}

13. 高亮

通过 highlight 来对匹配出的字段设置高亮显示。

属性说明
fields要高亮的字段
pre_tags前缀,默认是<em>
post_tags后缀,默认是</em>
GET test3/_search
{
  "query": {
    "match": {
      "name": "张三"
    }
  },
  "highlight": {
    "pre_tags": "<p class='key' style='color: red'>",
    "post_tags": "</p>", 
    "fields": {
      "name": {}
    }
  }
}

六、聚合(Aggregation)

官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-aggregations.html
聚合语法

GET /my-index-000001/_search
{
  "aggs":{
    "aggs_name":{ # 这次聚合的名字,方便展示在结果集中
        "AGG_TYPE":{ # 聚合的类型(avg,term,terms)
        	"field": "my-field"
        }   
     }
    }
}

示例1-搜索address中包含mill的所有人的年龄分布以及平均年龄

terms聚合:比如性别有男、女,就会创建两个桶,分别存放男女的信息。默认会搜集 doc_count 的信息,即记录有多少男生,有多少女生,然后返回给客户端,这样就完成了一个 terms 的统计。

GET bank/_search
{
  "query": {
    "match": {
      "address": "Mill"
    }
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 10
      }
    },
    "ageAvg": {
      "avg": {
        "field": "age"
      }
    },
    "balanceAvg": {
      "avg": {
        "field": "balance"
      }
    }
  },
  "size": 0
}
# 先 query 再 match,会先查找数据,再对查找出后的结果进行聚合
# "ageAgg": {                     --- 聚合名为 ageAgg
#   "terms": {                  --- 聚合类型为 terms,按字段分别创建桶,分别存放不同字段的信息。默认会搜集doc_count的信息,即记录每个桶有多少数据
#     "field": "age",     --- 聚合字段为 age
#     "size": 10                --- 取聚合后前十个数据
#   }
# },
# ------------------------
# "ageAvg": {                     --- 聚合名为 ageAvg
#   "avg": {                      --- 聚合类型为 avg 求平均值
#     "field": "age"        --- 聚合字段为 age
#   }
# },
# ------------------------
# "balanceAvg": {                 --- 聚合名为 balanceAvg
#   "avg": {                      --- 聚合类型为 avg 求平均值
#     "field": "balance"  --- 聚合字段为 balance
#   }
# }
# ------------------------
# "size": 0               --- 不显示命中结果,只看聚合信息

结果:

{
  "took" : 11,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "ageAgg" : {
      "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
        }
      ]
    },
    "ageAvg" : {
      "value" : 34.0
    },
    "balanceAvg" : {
      "value" : 25208.0
    }
  }
}

示例2-按照年龄聚合,并且求这些年龄段的这些人的平均薪资(每个年龄的平均薪资)

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "ageAvg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 0
}

结果(数据太多,只取了3条):

{
  "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" : {
    "ageAgg" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 820,
      "buckets" : [
        {
          "key" : 31,
          "doc_count" : 61,
          "ageAvg" : {
            "value" : 28312.918032786885
          }
        },
        {
          "key" : 39,
          "doc_count" : 60,
          "ageAvg" : {
            "value" : 25269.583333333332
          }
        },
        {
          "key" : 26,
          "doc_count" : 59,
          "ageAvg" : {
            "value" : 23194.813559322032
          }
        }
      ]
    }
  }
}

示例3-查出所有年龄分布,并且这些年龄段中性别为M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "genderAgg": {
          "terms": {
            "field": "gender.keyword"
          },
          "aggs": {
            "balanceAvg": {
              "avg": {
                "field": "balance"
              }
            }
          }
        },
        "ageBalanceAvg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 0
}
# "field": "gender.keyword" gender是txt没法聚合 必须加.keyword精确替代

结果(数据太多,只取了2条)

{
  "took" : 11,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1000,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "ageAgg" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 879,
      "buckets" : [
        {
          "key" : 31,
          "doc_count" : 61,
          "genderAgg" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "M",
                "doc_count" : 35,
                "balanceAvg" : {
                  "value" : 29565.628571428573
                }
              },
              {
                "key" : "F",
                "doc_count" : 26,
                "balanceAvg" : {
                  "value" : 26626.576923076922
                }
              }
            ]
          },
          "ageBalanceAvg" : {
            "value" : 28312.918032786885
          }
        },
        {
          "key" : 39,
          "doc_count" : 60,
          "genderAgg" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "F",
                "doc_count" : 38,
                "balanceAvg" : {
                  "value" : 26348.684210526317
                }
              },
              {
                "key" : "M",
                "doc_count" : 22,
                "balanceAvg" : {
                  "value" : 23405.68181818182
                }
              }
            ]
          },
          "ageBalanceAvg" : {
            "value" : 25269.583333333332
          }
        }
      ]
    }
  }
}

七、Mapping映射

https://www.elastic.co/guide/en/elasticsearch/reference/7.13/mapping.html

1. Mapping介绍

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

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

查看mapping信息

GET bank/_mapping
{
  "bank" : {
    "mappings" : {
      "properties" : {
        "account_number" : {
          "type" : "long"
        },
        "address" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "age" : {
          "type" : "long"
        },
        "balance" : {
          "type" : "long"
        },
        "email" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "firstname" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "gender" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "lastname" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "state" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}

2. 新版本type移除

ElasticSearch7-去掉type概念

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

3. 属性类型

参考:官方属性类型

常见:

类型数据类型
字符串text、keyword
整型byte、short、integer、long
浮点float、double
日期date
布尔值boolean
二进制binary
  • text 类型:当一个字段是要被全文搜索的,比如Email内容、产品描述,应该使用text类型。设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分词器分成一个一个词项。text类型的字段
    不用于排序,很少用于聚合。
  • keyword类型:适用于索引结构化的字段,比如email地址、主机名、状态码和标签。如果字段需要进行过滤(比如查找已发布博客中 status 属性为 published 的文章)、排序、聚合。keyword 类型的字段只能通过精确值搜索到

映射操作

参考:创建映射操作

创建索引映射

创建索引并指定属性的映射规则(相当于新建表并指定字段和字段类型)

PUT /my_index
{
  "mappings": {
    "properties": {
      "age": {
        "type": "integer"
      },
      "email": {
        "type": "keyword"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

结果:

{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "my_index"
}

给已有映射增加字段

https://www.elastic.co/guide/en/elasticsearch/reference/7.x/explicit-mapping.html#add-field-mapping

PUT /my_index/_mapping
{
  "properties": {
    "employee-id": {
      "type": "keyword",
      "index": false
    }
  }
}
# 这里的 "index": false,表明新增的字段不能被检索。默认是true
# https://www.elastic.co/guide/en/elasticsearch/reference/7.x/mapping-index.html

结果:

{
  "acknowledged" : true
}

查看映射

https://www.elastic.co/guide/en/elasticsearch/reference/7.x/explicit-mapping.html#view-mapping

GET /my_index/_mapping
# 查看某一个字段的映射
GET /my_index/_mapping/field/employee-id

结果:

{
  "my_index" : {
    "mappings" : {
      "properties" : {
        "age" : {
          "type" : "integer"
        },
        "email" : {
          "type" : "keyword"
        },
        "employee-id" : {
          "type" : "keyword",
          "index" : false
        },
        "name" : {
          "type" : "text"
        }
      }
    }
  }
}
# index false 表示不能被索引找到

更新映射

https://www.elastic.co/guide/en/elasticsearch/reference/7.x/explicit-mapping.html#update-mapping
对于已经存在的字段映射,我们不能更新。更新必须创建新的索引,进行数据迁移。

数据迁移

数据迁移步骤:

  1. 创建新的索引,并创建 Mapping 映射
  2. 使用下面的方法进行数据迁移

迁移方式分为两种,一种是7和7之后去掉type的情况,一种是包含type 迁移的情况。

无type数据迁移
POST reindex [固定写法]
{
  "source":{
      "index":"twitter"	# 指定原索引
   },
  "dest":{
      "index":"new_twitters" #指定目标索引
   }
}
有type数据迁移
POST reindex [固定写法]
{
  "source":{
      "index":"twitter", # 指定原索引
      "twitter":"twitter"	# 指定type
   },
  "dest":{
      "index":"new_twitters"	#指定目标索引
   }
}

八、IK分词器

1. 简介

https://www.elastic.co/guide/en/elasticsearch/reference/7.x/analysis.html

一个 tokenizer(分词器)接收一个字符流,将之分割为独立的 tokens(词元,通常是独立的单词),然后输出 tokens 流。

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

POST _analyze
{
  "analyzer": "standard",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}
# "analyzer": "standard" 指定分词器类型,standard 为默认的标准分词器

默认的分词器一般都是针对于英文,对于中文我们需要安装额外的分词器来进行分词。

2. 安装IK分词器

  1. 下载
  • IK 分词器属于 Elasticsearch 的插件,所以 IK 分词器的安装目录是 Elasticsearch 的 plugins 目录,在我们使用Docker启动 Elasticsearch 时,已经将该目录挂载到主机的 /mydata/elasticsearch/plugins 目录。
  • IK 分词器的版本需要跟 Elasticsearch 的版本对应,当前选择的版本为 7.4.2(tag 为7.4.2),下载地址为:Github Release 或访问:镜像地址
# 进入挂载的插件目录 /mydata/elasticsearch/plugins
cd /mydata/elasticsearch/plugins
# 安装 wget 下载工具
 yum install -y wget
# 下载对应版本的 IK 分词器(这里是7.4.2)
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
# 解压到 plugins 目录下的 ik 目录
unzip elasticsearch-analysis-ik-7.4.2.zip -d ik
# 删除下载的压缩包
 rm -f elasticsearch-analysis-ik-7.4.2.zip 
# 修改文件夹访问权限
chmod -R 777 ik/
# 重启 Elasticsearch
docker restart elasticsearch
  1. 测试 ik 分词器
GET my_index/_analyze
{
   "analyzer": "ik_max_word", 
   "text":"蔡徐坤"
}

3. 自定义扩展分词库

我们在 nginx 中自定义分词文件,通过配置 es 的 ik 配置文件来远程调用 nginx 中的分词文件来实现自定义扩展词库。
注:默认 nginx 请求的是 数据目录的 html 静态目录
nginx 安装参考:docker 安装 nginx

  1. nginx 中自定义分词文件

    echo "蔡徐坤" > /mydata/nginx/html/fenci.txt
    

    nginx 默认请求地址为 ip:port/fenci.txt

    如果想要增加新的词语,只需要在该文件追加新的行并保存新的词语即可。

  2. 给 es 配置自定义词库

# 1. 打开并编辑 ik 插件配置文件
vim /mydata/elasticsearch/plugins/ik/config/IKAnalyzer.cfg.xml
修改为以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict"></entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords"></entry>
        <!-- 用户可以在这里配置远程扩展字典 -->
        <!-- 在这里指定nginx中的文件 -->
        <entry key="remote_ext_dict">http://192.168.163.131/fenci.txt</entry>
        <!--用户可以在这里配置远程扩展停止词字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
  1. 重启 elasticsearch 容器
docker restart elasticsearch
  1. 测试自定义词库
GET my_index/_analyze
{
   "analyzer": "ik_max_word", 
   "text":"蔡徐坤"
}

九、集成 SpringBoot

官方文档:戳这里,进去后选择自己 ES 相应的版本,然后选择 Java High Level REST Client 查看文档

这里以 7.4 版本的 es 为例:

1. 引入依赖

https://www.elastic.co/guide//en/elasticsearch/client/java-rest/7.4/java-rest-high-getting-started-maven.html

<properties>
	<!--springboot父依赖中有默认的elasticsearch版本,改成自己相应的版本-->
    <!--改springboot中默认管理的es版本-->
    <elasticsearch.version>7.4.2</elasticsearch.version>
</properties>

<!-- 导入es的high-level-client 注意要改spring-boot中的es管理版本-->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.2</version>
</dependency>

配置 Elasticsearch 客户端:
https://www.elastic.co/guide//en/elasticsearch/client/java-rest/7.13/java-rest-high-getting-started-initialization.html
配置 Elasticsearch 选项:https://www.elastic.co/guide//en/elasticsearch/client/java-rest/7.13/java-rest-high-getting-started-request-options.html

@Configuration
public class ElasticsearchConfig {

	//注入初始化客户端,方便之后使用
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                		// 当有多个主机时,这里配置多个主机,
                        new HttpHost("localhost", 9200, "http"),
                        new HttpHost("localhost", 9300,"http")));
        return client;
    }

	// 配置操作es时的选项
	private static final RequestOptions COMMON_OPTIONS;

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

2. 测试数据存储

官方文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-document-index.html

@Autowired
RestHighLevelClient client;

@Test
void indexData() throws IOException {
    IndexRequest indexRequest = new IndexRequest("users");
    indexRequest.id("1");
    // json 字符串
    indexRequest.source("{" +
            "\"user\":\"kimchy\"," +
            "\"postDate\":\"2013-01-30\"," +
            "\"message\":\"trying out Elasticsearch\"" +
            "}", XContentType.JSON);

    // KV 键值对
    // indexRequest.source("username", "zhangsan", "age", 12, "address", "sz");

    // 同步执行
    client.index(indexRequest, MallElasticSearchConfig.COMMON_OPTIONS);
}

3. 测试检索

官方文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html

检索地址中带有 mill 的人员年龄分布和平均薪资

@Autowired
RestHighLevelClient client;

@Test
void searchData() throws IOException {
    // 1. 创建检索请求
    SearchRequest searchRequest = new SearchRequest();
    // 指定索引
    searchRequest.indices("bank");
    // 指定 DSL 检索条件
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 1.1 构建检索条件 address 包含 mill
    searchSourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));
    // 1.2 按照年龄值分布进行聚合
    TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
    searchSourceBuilder.aggregation(ageAgg);
    // 1.3 计算平均薪资
    AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
    searchSourceBuilder.aggregation(balanceAvg);

    System.out.println("检索条件:" + searchSourceBuilder.toString());
    searchRequest.source(searchSourceBuilder);


    // 2. 执行检索, 获得响应
    SearchResponse searchResponse = client.search(searchRequest, MallElasticSearchConfig.COMMON_OPTIONS);

    // 3. 分析结果
    // 3.1 获取所有查到的记录
    SearchHits hits = searchResponse.getHits();
    SearchHit[] searchHits = hits.getHits();
    for (SearchHit hit : searchHits) {
        // 数据字符串
        String jsonString = hit.getSourceAsString();
        System.out.println(jsonString);
        // 可以通过 json 转换成实体类对象
        // Account account = JSON.parseObject(jsonString, Account.class);
    }

    // 3.2 获取检索的分析信息(聚合数据等)
    Aggregations aggregations = searchResponse.getAggregations();
    // for (Aggregation aggregation : aggregations.asList()) {
    //     System.out.println("当前聚合名:" + aggregation.getName());
    // }
    Terms ageAgg1 = aggregations.get("ageAgg");
    for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
        String keyAsString = bucket.getKeyAsString();
        System.out.println("年龄:" + keyAsString + " 岁的有 " + bucket.getDocCount() + " 人");
    }

    Avg balanceAvg1 = aggregations.get("balanceAvg");
    System.out.println("平均薪资: " + balanceAvg1.getValue());
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值