【ElasticSearch 01】elasticsearch入门详解

概述

es是一个基于Lucene的搜索引擎。对于初学者来说,可以将其看作一款NoSQL。es一般可以用作项目中的搜索、检索模块,提供关键词检索、条件过滤、聚合等功能。

es在单独使用时,可以实现的功能有比如:淘宝、京东等的商品搜索,根据品牌、分类的商品过滤,根据价格等的排序。数据聚合则是可以统计如:某一价格段的商品的销量,等类似的数据。

es中的数据是以文档(docment)为单位保存的,可以看作是数据库中的一行数据。文档保存在类型(type)中,可以看作是数据库中的表。而存储以上这些的称为索引(index),可以看作是数据库中的某一个库。 但是需要注意的是,es7后就不推荐使用type了,默认每个索引只有一个type,名字为“_doc”,而在es8中则会删除type。

这里主要是对于es的入门使用的介绍,以帮助初学者。

对于一些基础概念,则推荐阅读官方文档:https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
中文官方文档对应版本较低,但是底层的一些概念是没有改变的。

分片、副本:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_add-an-index.html
聚合:https://www.elastic.co/guide/cn/elasticsearch/guide/current/aggregations.html

安装

需要提前安装:elasticsearch7.x、kibana,教程太多,这里不贴出。

kibana可以可视化的对es进行操作。

使用

增删改查

es提供了非常多的Rest风格的接口,可以直接通过http请求对其进行操作,而请求的数据格式则为json。

添加

put请求为添加。使用postman对一下地址进行请求,结果如下。
http://localhost:9200/customer/external/1
在这里插入图片描述

也可以使用post请求,不加id(标识)的话每次请求会随机生成一个唯一id,加了就和put一样
在这里插入图片描述

查询

  • 使用get请求即可

get:http://localhost:9200/customer/external/1

更新

更新有两种方法:1、就是使用添加的api进行请求,如果请求的id相同,则为添加。在此不进行介绍。

2、post请求:http://localhost:9200/customer/external/1/_update

而此种方法请求时,请求的json格式要如下:

{
    "doc":{
        "name":"1111"
    }
}

两种方法的区别在于,第二种方式,若数据相同,则不会执行更新,而第一种,无论数据与否,都会用新数据将旧数据覆盖,那么这个不覆盖如何体现呢,我们看一下es返回的数据。

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 8,
    "result": "noop",
    "_shards": {
        "total": 0,
        "successful": 0,
        "failed": 0
    },
    "_seq_no": 16,
    "_primary_term": 29
}

可以看到第6行,result为noop,代表没有执行更新。

另外,还有两个字段值得注意: _seq_no、 _primary_term。

  • _seq_no是数据每更新一次就会+1的字段

  • _primary_term是es服务端每重启一次会+1的字段

而第二种方法更新请求的若为已有的数据,则_seq_no不会进行+1操作。

而且我们可以根据这两个字段,来为更新请求加上乐观锁。

先将数据查出,再将查出的_seq_no和 _primary_term 放入请求中:

使用put请求: http://localhost:9200/customer/external/1?if_seq_no=14&if_primary_term=29,若_seq_no和 _primary_term对应不上,则会更新失败。

删除

发送DELETE请求即可

  • DELETE: http://localhost:9200/customer/external/1

也可以删除整个index,但是删除type只能是删除type下的所有doc

  • DELETE: http://localhost:9200/customer

批量

一个个的添加、删除非常耗时耗力,我们还可以进行批量的操作,使用bluk接口

此时需要使用kibana 的 devtools

语法格式:

两行为一个整体(删除操作因为不需要请求的json数据,所以一行)

{action:{metadata}}\n
{request body  }\n

{action:{metadata}}\n
{request body  }\n

这里的批量操作,当发生某一条执行发生失败时,其他的数据仍然能够接着执行,也就是说彼此之间是独立的。

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

示例1:

可以在请求的时候,在path里指定index和type

POST: customer/external/_bulk

{"index":{"_id":"1"}}
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"John Doe"}

以上的请求数据,两行为一个整体,第一行为操作:比如index为添加、delete为删除。第二行为操作的数据。

{“index”:{"_id":“1”}}
{“name”:“John Doe”}

即表示,添加{"name":"John Doe"}数据 ,id为1。

示例2:

也可以在action的metadata中指定index、type

index和create是同一个操作,delete不需要请求体,所以为一行

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"}}

Query DSL

以上的简单查询,是无法帮助我们实现检索等功能的,所以就引出了DSL查询语句。

首先,先导入es官方的测试数据:https://github.com/elastic/elasticsearch/blob/6d42e197b82aa1c98a1796d547e373f7029ee27a/docs/src/test/resources/accounts.json

POST请求bank/account/_bulk 地址,向bank索引中批量添加数据

DSL语句的基本结构为:

 {
  QUERY_NAME:{
     FIELD_NAME:{
       ARGUMENT:VALUE,
       ARGUMENT:VALUE,...
      }   
   }
}

查询

查询后,默认会根据匹配程度生成一个评分_score,也会根据此评分进行排序

match_all 查询全部
GET /bank/_search
{
  "query": {
    "match_all": {}
  }
}
match 关键词查询
  • 查询非字符串(keyword、long)的会查询相等的
GET bank/_search
{
  "query": {
    "match": {
      "account_number": 20
    }
  }
}

match返回account_number=20的数据。

  • 查询字符串(text)的会查询包含的
GET bank/_search
{
  "query": {
    "match": {
      "address": "kings"
    }
  }
}

全文检索,最终会按照评分进行排序,会对检索条件进行分词匹配。

  • 查询text时以keyword的特性查询,即匹配完全相等的
GET bank/_search
{ 
  "query": { 
    "match": { 
      "address.keyword": "440 King Street"
    }
  }
}

字段.keyword:则会匹配这个字段相等的,而不是包含

match_phrase 短句匹配

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

match的区别在于:如下两个查询,match_phrase会查询包括Kings Place这两个连续单词的,而match会查询地址包括KingsPlace的,而包括Kings Place这两个连续单词的评分会更高

GET bank/_search
{
  "query": {
    "match_phrase": {
       "address": "Kings Place"
    }
  }
}
GET bank/_search
{
  "query": {
    "match": {
       "address": "Kings Place"
    }
  }
}
multi_math 多字段匹配
GET bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill",
      "fields": [
        "state",
        "address"
      ]
    }
  }
}

state或者address中包含mill,并且在查询过程中,会对于查询条件进行分词。

term 精确值查找

只对非text字段生效,必须完全匹配才会被查找到。

GET bank/_search
{
  "query": {
    "term": {
       "age" : 20
    }
  }
}
bool 复杂查询

一个 bool 过滤器由这几部分组成:

{
   "bool" : {	
      "must" :     	[],
      "should" :  	[],
      "must_not" : 	[],
      "filter":		[],
   }
}

must

所有的语句都 必须(must) 匹配

must_not

所有的语句都 不能(must not) 匹配

should

应该包含(如果到达会增加相关文档的评分,并不会改变查询的结果。如果query中只有should且只有一种匹配规则,那么should的条件就会被作为默认匹配条件二区改变查询结果。

filter

过滤条件,使用上和查询一样,但是更匹配的不计入分数。

举例:

// 查询 address中有Street,年龄不为18,最好lastname有Wallace的(有的加分,没有的不影响),最后产生的结果再过滤(不记分数)gender为M的(此处使用的是term,term只对非text生效  .keyword是提前设置的,后面会说到,这里的作用是此次检索当作keyword来使用,所以才可以用term)
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "address": "Street"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "age": "18"
          }
        }
      ],
      "should": [
        {
          "match": {
            "lastname": "Wallace"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "gender.keyword": "M"
          }
        }
      ]
    }
  }
}
filter 过滤

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

{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "balance": {
              "gte": 10000,
              "lte": 20000
            }
          }
        },
        {
          "match": {
            "address": "Street"
          }
        }
      ]
    }
  }
}
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
      
        {
          "match": {
            "address": "Street"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "balance": {
              "gte": 10000,
              "lte": 20000
            }
          }
        }
      ]
    }
  }
}

以上两种查询方式,一个是使用filter来匹配工资10000-20000的 最终查询出的分数为:

{
    "_index" : "bank",
    "_type" : "account",
    "_id" : "51",
    "_score" : 0.95395315, // 《------
    "_source" : {
        "account_number" : 51,
        "balance" : 14097,
        "firstname" : "Burton",
        "lastname" : "Meyers",
        "age" : 31,
        "gender" : "F",
        "address" : "334 River Street",
        "employer" : "Bezal",
        "email" : "burtonmeyers@bezal.com",
        "city" : "Jacksonburg",
        "state" : "MO"
    }
}

一个是通过must匹配工资,最终查询出的分数:

{
    "_index" : "bank",
    "_type" : "account",
    "_id" : "51",
    "_score" : 1.9539531, // 《------
    "_source" : {
        "account_number" : 51,
        "balance" : 14097,
        "firstname" : "Burton",
        "lastname" : "Meyers",
        "age" : 31,
        "gender" : "F",
        "address" : "334 River Street",
        "employer" : "Bezal",
        "email" : "burtonmeyers@bezal.com",
        "city" : "Jacksonburg",
        "state" : "MO"
    }
}

分页

在与query平行的一层,使用from 和 size 即可

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 5,
  "_source": ["balance","firstname"] 
}
// 表示从0开始,展示5个,即分页的第一页、页面大小为5
// _source的意思是,只展示firstname和balance两个字段

排序

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 5,
  "sort": [
    {
      "account_number": {
        "order": "desc"
      }
    }
  ],
  "_source": ["balance","firstname"] 
}

mapping 映射

指定映射

说白了就是字段的类型,主要有:keyword、text、long、integer、date、object几种

上面的数据,是直接添加数据,es根据数据自动生成的映射

我们还可以在创建索引时指定映射

## 创建index时指定
PUT /my_index
{
    "mappings": {
        "properties": {
            "age": {
                "type": "integer"
            },
            "email": {
                "type": "keyword"
            },
            "name": {
                "type": "text",
                "fields" : {
                    // 指定 name.keyword时,字段以下面配置进行检索,此处的keyword无意义,可以任意更换其他名字
                    "keyword" : {
                        // 指定还可以作为keyword类型进行检索
                        "type" : "keyword",
                        // 表示作为keyword类型时,长度最多为256
                        "ignore_above" : 256
                    }
                }
            }
        }
    }
}
// ------------------------------------------------------------
{
    "mappings" : {
        "properties" : {
            "createTime" : {
                // type为date时需要指定日期的格式
                // 具体见文档:https://www.elastic.co/guide/reference/mapping/date-format/
                "type" : "date",
                "format" : "yyyy-MM-dd HH:mm:ss"
            },
            "id" : {
                "type" : "keyword"
            },
            // 这里的memeber就是object类型,注意其下一级有“properties”
            "member" : {
                "properties" : {
                    "id" : {
                        "type" : "keyword"
                    },
                    "name" : {
                        "type" : "text",
                        "analyzer" : "standard"
                    }
                }
            },
            "title" : {
                "type" : "text",
                // 此处为指定分词器,一般需要安装ik分词器,后面会提到
                "analyzer" : "ik_smart"
            }
        }
    }
}

类型介绍

keyword

使用比较多的一个,主要可以用来储存不分词的字段,比如电话、邮箱这些只可以被整个检索到的

"id" : {
    "type" : "keyword"
}
text

一般的字符串类型,主要用来储存分词检索的字段,比如商品标题、博客简介等。可以指定分词器,比如搜索“小米手机”,就可能被分词为“小米”、“手机”两个词语,同事拥有“小米手机”的标题会评分较高,排位靠前,当然也可能你指定的分词器不会将“小米”和“手机分开”。

"title" : {
    "type" : "text",
    // 此处为指定分词器,一般需要安装ik分词器,后面会提到
    "analyzer" : "ik_smart"
}
date

日期时间类型,搭配format字段,统一日期格式,一般可以用于排序

"createTime" : {
    // type为date时需要指定日期的格式
    // 具体见文档:https://www.elastic.co/guide/reference/mapping/date-format/
    "type" : "date",
    "format" : "yyyy-MM-dd HH:mm:ss"
}
arrays

数组类型

  • an array of strings: ["one","two"]
  • an array of integers: [1,2]
  • an array of arrays: [1, [2,3]] which is the equivalent of [1,2,3]
  • an array of objects: [{ "name": "Mary", "age": 12 },{ "name": "John", "age": 10 }]
PUT my-index-000001/_doc/1
{
  "message": "some arrays in this document...",
  "tags":  [ "elasticsearch", "wow" ], 
  "lists": [ 
    {
      "name": "prog_list",
      "description": "programming list"
    },
    {
      "name": "cool_list",
      "description": "cool stuff list"
    }
  ]
}

PUT my-index-000001/_doc/2 
{
  "message": "no arrays in this document...",
  "tags":  "elasticsearch",
  "lists": {
    "name": "prog_list",
    "description": "programming list"
  }
}

GET my-index-000001/_search
{
  "query": {
    "match": {
      "tags": "elasticsearch" 
    }
  }
}

以上第三个请求,会检索tags中有elasticsearch的,数组内的数据也可以被检索到,所以最终两个doc都会被检索出来

object

对象类型

// 这里的memeber就是object类型,注意其下一级有“properties”
"member" : {
    "properties" : {
        "id" : {
            "type" : "keyword"
        },
        "name" : {
            "type" : "text",
            "analyzer" : "standard"
        }
    }
}

检索的时候通过 对象名.字段名进行检索。如 "member.age":20来检索 年龄==20的

boolean

布尔类型 储存true false

其他类型

建议查看官方文档。

https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html

Aggregation聚合

官方中文文档写的非常好https://www.elastic.co/guide/cn/elasticsearch/guide/current/aggregations.html

概述

先说聚合是干嘛的

聚合可以用来查询诸如:2021-06-01这一天有多少注册人数,这些注册人数的男女性别分别为多少,他们的平均工资,以及男女的平均工资。

这类需要统计、计算的问题。(当然这是比较简单的使用,还有更加复杂的聚合

再说几个概念

桶聚合(Buckets):满足特定条件的文档的集合,如根据男女、月份、工资范围进行分桶。

指标(度量)聚合(Metrics):对桶内的文档进行统计计算,如计算count、平均、sum等。

桶在概念上类似于 SQL 的分组(GROUP BY),而指标(度量)则类似于 COUNT()SUM()MAX() 等统计方法。

桶聚合

概述

简单来说就是满足特定条件的文档的集合:

  • 一个员工属于 男性 桶或者 女性
  • 故宫属于 北京 桶,也可以属于中国
  • 日期2020-10-28属于 十月 桶,当然也可以属于21世纪

当聚合时,通过一条数据中的某个字段的值来确定该条数据属于哪个桶。

比如,我们创建一个条件为分桶字段为gender的,若gender字段只保存了两个值 男or女 ,则该桶则会分为两个,男和女

桶也可以进行嵌套,比如上面的两个男女桶,经过嵌套可以再根据年龄划分,得出不同年龄的桶。当然桶内也可以嵌套度量聚合,比如求不同年龄的平均工资。

terms

terms聚合的条件,就是根据这个字段有多少种不同的值,则生成多少个桶,如:性别的男女,生成两个桶,若保存的数据种还有“人妖”,则还会生成人妖桶

## 查询全部的人,根据年龄进行桶聚合
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age"
      }
    }
  },
  "size": 0
}
## 返回结果:
"aggregations" : {
    "ageAgg" : {
        "doc_count_error_upper_bound" : 0,
        "sum_other_doc_count" : 463,
        "buckets" : [
            {	
                # 31岁桶
                "key" : 31,
                # 数量61
                "doc_count" : 61
            },
            {
                "key" : 39,
                "doc_count" : 60
            }
            # 此处省略一些其他年龄
        ]
    }
}

以上的查询聚合结果,可以看到 “buckets” 为一个数组,其中有多个对象。而对象的key字段的意思即为分桶的依据,如第一个意思是31岁的数据有61条

range/date range

根据范围来分桶

GET /_search
{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 100.0 },
          { "from": 100.0, "to": 200.0 },
          { "from": 200.0 }
        ]
      }
    }
  }
}

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-range-aggregation.html

根据时间范围来分桶

POST /blog/_search?size=0
{
  "aggs": {
    "range": {
      "date_range": {
        "field": "createTime",
        "format": "yyyy-MM-dd",
        "ranges": [
          { "from": "2020-01-10" } ,
          { "to": "2021-06-10" }
        ]
      }
    }
  }
}

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-daterange-aggregation.html

histogram

直方图聚合

请求:

interval 字段就是每间隔多少生成一个桶

min_doc_count 字段就是最小的数量,如果低于这个数量就不显示

POST /sales/_search?size=0
{
  "aggs": {
    "prices": {
      "histogram": {
        "field": "price",
        "interval": 50,
        "min_doc_count": 1
      }
    }
  }
}

最后看返回结果:

可以根据key作为x轴,doc_count作为y轴,生成一张直方图

{
  ...
  "aggregations": {
    "prices": {
      "buckets": [
        {
          "key": 0.0,
          "doc_count": 1
        },
        {
          "key": 50.0,
          "doc_count": 1
        },
        {
          "key": 150.0,
          "doc_count": 2
        },
        {
          "key": 200.0,
          "doc_count": 3
        }
      ]
    }
  }
}

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-histogram-aggregation.html

度量聚合

顾名思义,即求和、求平均之类的聚合

可以和桶聚合一起使用

avg
POST /exams/_search?size=0
{
  "aggs": {
    "avg_agg": { "avg": { "field": "grade" } }
  }
}
sum
POST /sales/_search?size=0
{
  "query": {
    "constant_score": {
      "filter": {
        "match": { "type": "hat" }
      }
    }
  },
  "aggs": {
    "hat_prices": { "sum": { "field": "price" } }
  }
}

嵌套

## 查出所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "genderAgg": {
          "terms": {
            "field": "gender.keyword",
            "size": 2
          },
          "aggs": {
            "blanceAvg": {
              "avg": {
                "field": "balance"
              }
            }
          }
        },
        "blanceAvg":{
          "avg": {
            "field": "balance"
          }
        }
      }
      
    }
  },
  "size": 0
}

分词器

安装分词器

安装常用的ik分词器

1、下载

https://github.com/medcl/elasticsearch-analysis-ik/releases

注意要下载与你的es对应版本的分词器。

2、解压

解压到es目录下的plugins文件夹即可,如图所示

在这里插入图片描述

使用

安装完成后重启es

GET my_index/_analyze
{
   "analyzer": "ik_smart", 
   "text":"我是中国人"
}
## 分出 我、是、中国人 ,三个词
GET my_index/_analyze
{
   "analyzer": "ik_max_word", 
   "text":"我是中国人"
}
## 分出 我、是、中国人、中国、国人 ,五个词

大部分时候创建索引时指定mapping为text需要分词的字段的分词所用的分词器。

自定义词库

有的比较新的网络用语没办法分词,需要自定义词库

1、进入plugins–>ik–>config目录,新建一个分词的文件,如:fenci.txt并在这一个文件中写入词汇,每个词以 回车 分割,可以参考 main.dic文件(自定义分词的文件后缀也可以是dic)

2、在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">fenci.txt</entry>
	<!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<!-- <entry key="remote_ext_dict">http://127.0.0.1/es/fenci.txt</entry>-->
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值