ElasticSearch快速入门

一、Es简介

1.1、什么是ElasticSearch

ElasticSearch是一个分布式、RESTful风格的搜索和数据分析引擎,其是通过Java开发并使用Lucene作为其核心来实现索引和搜索的功能,它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得更简单。 ElasticSearch本身的扩展性很好,允许多台服务器协同工作,每台服务器可以运行多个实例,单个实例成为一个节点,一组节点构成一个集群。分片是底层的工作单元,文档保存在分片内,分片又被分配到集群中的各个节点里,每个分片仅保保存全部数据的一部分。当ElasticSearch的节点启动后,它会通过多播或单播(需要用户配置)的方式寻找集群中的其它节点。

当然,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确地形容:

  • 一个分布式的实时文档存储,每个字段可以被索引与搜索;
  • 一个分布式实时分析搜索引擎;
  • 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。

1.2、ElasticSearch基本概念

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
 在全文搜索的世界中,存在着几个庞大的帝国,也就是主流工具,主要有:

  • Apache Lucene
  • Elasticsearch
  • Solr
  • Ferret

倒排索引(Inverted Index)

该索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,而不是像传统索引那样通过属性来确定属性值,通俗的说就是不在是通过key寻找value,而是通过value寻找key。Elasticsearch能够实现快速、高效的搜索功能,正是基于倒排索引原理。倒排索引详细解释https://www.cnblogs.com/zlslch/p/6440114.html

ElasticSearch中的几个基本术语

  • 索引(Index):Elasticsearch 数据管理的顶层单位就叫做 Index(索引),类比关系型数据库中的数据库,另外每个Index的名字必须是小写。否则会报错: invalid_index_name_exception

    image-20210112153933748

  • 类型(type):类比关系型数据库中的表,一个数据库下可以有多张表,一个索引下也可以有多个类型,不同的 Type 应该有相似的结构(Schema),性质完全不同的数据(比如 products 和 logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。不过type在ES7中已经被标记过时,在8中将直接遗弃

  • 文档(document):类比关系型数据库表中的每一行数据相,一个表中有多行记录,一个type下也有多个文档,Document 使用 JSON 格式表示,同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。

  • 文档元数据(Document metadata):文档元数据为_index, _type, _id, 这三者可以唯一表示一个文档,_index表示文档在哪存放,_type表示文档的对象类别,_id为文档的唯一标识。

  • 字段(field):每个Document都类似一个JSON结构,它包含了许多字段,每个字段都有其对应的值,多个字段组成了一个 Document,可以类比关系型数据库数据表中的字段。

ElasticSearch官方文档https://www.elastic.co/guide/en/elasticsearch/reference/7.4/index.html

二、docker下载安装es和kibana

  • 安装Elasticsearch

    # 下载es镜像
    docker pull elasticsearch:7.6.2
    
    # 创建外部配置文件 -p递归创建文件夹
    # es的配置目录
    mkdir -p /mydata/elasticsearch/config
    # es的数据目录
    mkdir -p /mydata/elasticsearch/data
    # es的插件目录
    mkdir -p /mydata/elasticsearch/plugins
    # 向elasticsearch.yml配置文件中写入配置http.host: 0.0.0.0 '>'重定向
    echo "http.host: 0.0.0.0" > /mydata/elasticsearch/config/elasticsearch.yml
    # 改变目录的执行权限所有用户可读可写,root用户可执行
    chmod -R 755 /mydata/elasticsearch/
    
    # 启动es(es默认占用内存1G这里设置为最大512兆
    docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
    -e  "discovery.type=single-node" \
    -e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
    -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
    -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
    -v  /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
    -d elasticsearch:7.6.2 
    
    

访问http://ip:9200出现一下结果说明启动成功

image-20210112133153231

  • 安装kibana

    # 安装kibana,注意kibana的版本要和ElasticSearch版本一致
    docker pull kibana:7.6.2
    # 启动 kibana(增加配置-e "I18N_LOCALE=zh-CN"可以汉化,ELASTICSEARCH_HOSTS指定es的地址)
    docker run --name kibana -e ELASTICSEARCH_HOSTS=http://Es的ip:9200 -p 5601:5601 -d kibana:7.6.2
    

    访问http://ip:5160出现以下结果说明启动成功

    image-20210112135852472

三、ES基本命令

  • 查看es的基本信息(GET请求)
# 查看节点信息
http://47.112.183.82:9200/_cat/nodes
# 返回结果 * 表示集群中的主节点
127.0.0.1 68 96 2 0.06 0.06 0.05 dilm * 08346bcf4d6b
# 查看健康状态
http://47.112.183.82:9200/_cat/health
# 返回结果 green 健康值100%
1610432519 06:21:59 elasticsearch green 1 1 3 3 0 0 0 0 - 100.0%
# 查看主节点
http://47.112.183.82:9200/_cat/master
# 返回结果
8NsO1v_fRViV681G83zVbg 127.0.0.1 127.0.0.1 08346bcf4d6b
# 查看所有的索引
http://47.112.183.82:9200/_cat/indices
# 返回结果当前es的所有索引
green open .kibana_task_manager_1   ETFjPrTpReiE-pUTj_KlZg 1 0 2 0 38.3kb 38.3kb
green open .apm-agent-configuration u8KhR124QOuGoWL0zBhnCA 1 0 0 0   283b   283b
green open .kibana_1                W7_Q99-cRs2CjVXID0wAtw 1 0 8 0 21.9kb 21.9kb

3.1、新增文档(PUT或者POST请求)

指定索引为custom、类型为example,唯一标识ID为1

image-20210112161208350

返回结果是一个JSON串,带有'_'表示是元数据

{
    "_index": "custom", # 数据在哪个索引下索引 
    "_type": "example", # 类型
    "_id": "1", # 数据的唯一id
    "_version": 4,# 保存数据的版本(新增则是1) 在旧版本中用作乐观锁,属于当前文档,对其他文档的修改不会影响当前文档的version
    "result": "updated", # 表示是修改数据,如果是创建数据就是created
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 3, # 并发控制字段(新增是0),每次更新+1,用来做乐观锁,seq_no的递增属于整个index,而不是单个文档,对于当前index下的任一文档修改都会+1,而每个文档也拥有自己的seq_no
    "_primary_term": 1 # 同上用做乐观锁,表示文档所在主分片的编号,主分片重新分配,如重启就会发生变化
}

PUT和POST都可以POST新增和修改数据。POST如果不指定id,则是新增会自动生成id。指定id如果有数据就是更新,并新增版本号,如果没有数据则是新增数据。PUT必须指定id;由于PUT需要指定id,我们一般用来做修改操作,不指定id会报错。

对seq_no的解释

在custom索引example下新建id为1的文档此时version为1,,seq_no为0

image-20210112180351656

再在custom索引example类型下新建id为2的文档,此时发现当前文档的version还是为1,但seq_no却是1不是0

image-20210112180639980

再在custom索引example类型下新建id为3的文档,此时发现当前文档的version还是为1,但seq_no却是2

image-20210112180822286

查询id为1,和id为2的文档,返现他们的seq_version分别为0,和1

image-20210112180924551

image-20210112180940265

但是假设你修改id为1的文档,就会发现version版本+1,没毛病,但是seq_no却不是在当前seq_no(0)的基础上+1,而是2的基础上+1变为了3

image-20210112181119485

这是因为在我们创建id为3的文档时,seq_no已经加到2了,可以想象如果此时再次修改id为2的文档时seq_no会变为4

image-20210112181338116

如需使用乐观锁则请求如下http://47.112.183.82:9200/custom/example/5?if_seq_no=9&if_primary_term=1,在请求后边拼接参数?if_seq_no=&if_primary_term=

image-20210112192036956

3.2、更新文档_update

更新一定要带上doc{}

image-20210112192711232

成功修改,version+1,但是你在不修改任何内容的情况下再次update时,version并没有改变seq_no也没有改变,result显示noop

image-20210112192826215

上述结果是因为带_opdate的更新会检查更新的值与旧值是否相等,如果相等就不更新,而不带_update的更新并不会检查,而是会一直更新version会一直+1,seq_no也会改变。

3.3、删除文档或索引(DELETE)

# 删除文档
DELETE customer/external/1
# 删除索引
DELETE customer

注:elasticsearch并没有提供删除类型的操作,只提供了删除索引和文档的操作。

删除文档

image-20210112193832048

删除索引

image-20210112193920814

3.4、bulk批量操作

以下使用kibana

语法格式:

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

这里的批量操作,当发生某一条执行发生失败时,其他的数据仍然能够接着执行,也就是说彼此之间是独立的。bulk api以此按顺序执行所有的action(动作)。如果一个单个的动作因任何原因失败,它将继续处理它后面剩余的动作。当bulk api返回时,它将提供每个动作的状态(与发送的顺序相同)。

批量添加

POST custom/example/_bulk # 指定cutom索引下类型为example
{"index":{"_id":1}} # "index"
{"name":"彭于晏"}
{"index":{"_id":2}}
{"name":"胡歌晏"}
{"index":{"_id":3}}
{"name":"彭于晏"}

结果成功插入3条数据(图没截完)

image-20210112200229802_bulk可以批量执行删除,修改和增加操作如下,可以灵活变通,

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"}} # 相当于_update
{"doc":{"title":"my updated blog post"}} # 记得加上doc

注意:"index“和"create"都可以添加文档但是如果添加的文档的已经存在,则使用"create"就会报错,而"index"不会,如下无论执行多少次都不会报错

image-20210112201937443

但是将"index"改为"create"再次执行就会报错

image-20210112202046874

报错提示document已经存在,但是下边两条使用"index"的语句并没有报错

四、ES复杂检索

4.1导入测试数据

测试数据https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json

# 导入测试数据
post  /bank/account/_bulk
 {上边连接的内容}

4.2_search检索

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

  • 通过REST request uri 发送搜索参数 (uri +检索参数);

    查询bank索引下的所有文档按照account_number降序

    get bank/_search?q=*&sort=account_number:asc
    

    返回结果如下默认分页每页10条数据

    {
      "took" : 2,  # Elasticsearch运行查询多长时间(以毫秒为单位)
      "timed_out" : false, # 搜索请求是否超时
      "_shards" : { #  _shards –搜索了多少个分片,以及成功,失败或跳过了多少个分片。
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : { 
          "value" : 1000, # 找到了多少个匹配的文档
          "relation" : "eq"
        },
        "max_score" : null, # –找到的最相关文件的分数
        "hits" : [
          {
            "_index" : "bank",  # 索引
            "_type" : "account", # 类型
            "_id" : "1",  # 文档id
            "_score" : null, # 文档的相关性得分(使用时不适用match_all)
            "_source" : { # 文档下的field
              "account_number" : 1,
              "balance" : 39225,
              "firstname" : "Amber",
              "lastname" : "Duke",
              "age" : 32,
              "gender" : "M",
              "address" : "880 Holmes Lane",
              "employer" : "Pyrami",
              "email" : "amberduke@pyrami.com",
              "city" : "Brogan",
              "state" : "IL"
            },
             "sort" : [ # 文档的排序位置(不按相关性得分排序时)
              0  
            ]
         .....
          }
        ]
      }
    }
    
  • 通过REST request body 来发送它们(uri+请求体)称为Query DSL推荐

    • 一个查询语句的典型结构
    QUERY_NAME:{
       ARGUMENT:VALUE,
       ARGUMENT:VALUE,...
    }
    

    例子:查询所有并将结果安装account_number进行升序

    GET bank/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "account_number": {
            "order": "asc"
          }
        }
      ]
    }
    
    • 可以手动通过“from”,“size”指定分页大小
    GET bank/_search
    {
      "query": {
        "match_all": {}
      },
      "from": 0,
      "size": 5,
      "sort": [
        {
          "account_number": {
            "order": "asc"
          }
        }
      ]
    }
    
    • _source查询指定的字段
    GET bank/_search
    {
      "query": {
        "match_all": {}
      },
      "from": 0,
      "size": 5,
      "sort": [
        {
          "account_number": {
            "order": "desc"
          }
        }
      ],
      "_source": ["firstname","balance"]
     
    }
    
    • 通过match匹配查询

      查询的字段为非文本类型,则是精确查找,如下只会查询account_number=20的结果

    GET bank/_search
    {
      "query": {
        "match": {
          "account_number": "20"
        }
      }
    }
    

    ​ 结果

    {
      "took" : 0,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1,
          "relation" : "eq"
        },
        "max_score" : 1.0,
        "hits" : [
          {
            "_index" : "bank",
            "_type" : "account",
            "_id" : "20",
            "_score" : 1.0,
            "_source" : {
              "account_number" : 20,
              "balance" : 16418,
              "firstname" : "Elinor",
              "lastname" : "Ratliff",
              "age" : 36,
              "gender" : "M",
              "address" : "282 Kings Place",
              "employer" : "Scentric",
              "email" : "elinorratliff@scentric.com",
              "city" : "Ribera",
              "state" : "WA"
            }
          }
        ]
      }
    }
    

    ​ 如果查询字段为文本类型,则会进行分词匹配并将结果按照得分排序

    GET bank/_search
    {
      "query": {
        "match": {
          "address":"mill lane" 
        }
      }
    }
    

    结果如下: 只要address包括 mill或者lane的都会匹配上

    {
      "took" : 5,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 19,
          "relation" : "eq"
        },
        "max_score" : 9.578883,
        "hits" : [
          {
            "_index" : "bank",
            "_type" : "account",
            "_id" : "136",
            "_score" : 9.578883,
            "_source" : {
              "account_number" : 136,
              "balance" : 45801,
              "firstname" : "Winnie",
              "lastname" : "Holland",
              "age" : 38,
              "gender" : "M",
              "address" : "198 Mill Lane",
              "employer" : "Neteria",
              "email" : "winnieholland@neteria.com",
              "city" : "Urie",
              "state" : "IL"
            }
          },
          {
            "_index" : "bank",
            "_type" : "account",
            "_id" : "970",
            "_score" : 5.4598455,
            "_source" : {
              "account_number" : 970,
              "balance" : 19648,
              "firstname" : "Forbes",
              "lastname" : "Wallace",
              "age" : 28,
              "gender" : "M",
              "address" : "990 Mill Road",
              "employer" : "Pheast",
              "email" : "forbeswallace@pheast.com",
              "city" : "Lopezo",
              "state" : "AK"
            }
          },
          {
            "_index" : "bank",
            "_type" : "account",
            "_id" : "345",
            "_score" : 5.4598455,
            "_source" : {
              "account_number" : 345,
              "balance" : 9812,
              "firstname" : "Parker",
              "lastname" : "Hines",
              "age" : 38,
              "gender" : "M",
              "address" : "715 Mill Avenue",
              "employer" : "Baluba",
              "email" : "parkerhines@baluba.com",
              "city" : "Blackgum",
              "state" : "KY"
            }
          }
            ...
    
    • 通过match_phrase执行词组搜索而不是匹配单个词,查询条件不会进行分词
    GET bank/_search
    {
      "query": {
        "match_phrase": {
          "address":"mill lane" 
        }
      }
    }
    

    结果如下,只匹配地址包含"mill lane"的文档

    {
      "took" : 9,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1,
          "relation" : "eq"
        },
        "max_score" : 9.578883,
        "hits" : [
          {
            "_index" : "bank",
            "_type" : "account",
            "_id" : "136",
            "_score" : 9.578883,
            "_source" : {
              "account_number" : 136,
              "balance" : 45801,
              "firstname" : "Winnie",
              "lastname" : "Holland",
              "age" : 38,
              "gender" : "M",
              "address" : "198 Mill Lane",
              "employer" : "Neteria",
              "email" : "winnieholland@neteria.com",
              "city" : "Urie",
              "state" : "IL"
            }
          }
        ]
      }
    }
    
    • 使用keyword精确匹配

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

      结果如下,只会匹配address等于Mill Road的文档,一条也没有

      {
        "took" : 0,
        "timed_out" : false,
        "_shards" : {
          "total" : 1,
          "successful" : 1,
          "skipped" : 0,
          "failed" : 0
        },
        "hits" : {
          "total" : {
            "value" : 0,
            "relation" : "eq"
          },
          "max_score" : null,
          "hits" : [ ]
        }
      }
      
      • 使用multi_math进行多字段分词匹配只要address或者employer中包含mill就匹配,查询条件一样会分词
      GET bank/_search
      {
        "query": {
          "multi_match": {
            "query": "mill",
            "fields": [
              "address",
               "employer"
              ]
          }
        }
      }
      

      就不贴结果了。。。

      • 通过bool来做复合查询,复合语句可以合并,任何其他查询语句,包括复合语句。这也就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。
        • must:必须达到must所列举的所有条件
        • must_not:必须不匹配must_not所列举的所有条件。不会贡献score
        • should:应该满足should所列举的条件如果到达会增加相关文档的评分,并不会改变查询的结果。如果query查询条件只有should则会被当做匹配条件
        • filter:对查询结果进行过滤,子句(查询)必须出现在匹配的文档中。filter不会贡献score

      bool查询例子如下

      get bank/_search?q=*&sort=account_number:asc
      
      GET bank/_search
      {
        "query": {
          "bool": {
            "must": [
              {
                "match": {
                  "gender": "M"
                }
              },
              {
                "match": {
                  "address": "mill"
                }
              }
            ],
            "must_not": [
              {
                "match": {
                  "age": "18"
                }
              }
            ],
            "should": [
              {
                "match": {
                  "state": "AK"
                }
              }
            ],
            "filter": {
              "range": {
                "balance": {
                  "gte": 10000,
                  "lte": 20000 
                }
              }
            }
          }
        }
      }
      
      • term检索:term是代表完全匹配,只能查单个词搜,索前不会再对搜索词进行分词拆解。

        通过term检索address包含mill的文档

        GET bank/_search
        {
          "query": {
            "term": {
              "address": "mill"
            }
          }
        }
        

        结果有四条数据

        {
          "took" : 0,
          "timed_out" : false,
          "_shards" : {
            "total" : 1,
            "successful" : 1,
            "skipped" : 0,
            "failed" : 0
          },
          "hits" : {
            "total" : {
              "value" : 4,
              "relation" : "eq"
            },
            "max_score" : 5.4598455,
            "hits" : [
              {
                "_index" : "bank",
                "_type" : "account",
                "_id" : "970",
                "_score" : 5.4598455,
                "_source" : {
                  "account_number" : 970,
                  "balance" : 19648,
                  "firstname" : "Forbes",
                  "lastname" : "Wallace",
                  "age" : 28,
                  "gender" : "M",
                  "address" : "990 Mill Road",
                  "employer" : "Pheast",
                  "email" : "forbeswallace@pheast.com",
                  "city" : "Lopezo",
                  "state" : "AK"
                }
              }
                ...
        

        但是我们通过term匹配多个单词时,就不会查询到数据

        GET bank/_search
        {
          "query": {
            "term": {
              "address": "Mill Road"
            }
          }
        }
        

        结果如下:

        {
          "took" : 0,
          "timed_out" : false,
          "_shards" : {
            "total" : 1,
            "successful" : 1,
            "skipped" : 0,
            "failed" : 0
          },
          "hits" : {
            "total" : {
              "value" : 0,
              "relation" : "eq"
            },
            "max_score" : null,
            "hits" : [ ]
          }
        }
        

        如果想用term来匹配多个词就需要使用terms

        GET bank/_search
        {
          "query": {
            "terms": {
              "address": ["mill","road"]
            }
          }
        }
        

        结果如下 有32条数据,

        {
          "took" : 0,
          "timed_out" : false,
          "_shards" : {
            "total" : 1,
            "successful" : 1,
            "skipped" : 0,
            "failed" : 0
          },
          "hits" : {
            "total" : {
              "value" : 32,
              "relation" : "eq"
            },
            "max_score" : 1.0,
            "hits" : [
              {
                "_index" : "bank",
                "_type" : "account",
                "_id" : "431",
                "_score" : 1.0,
                "_source" : {
                  "account_number" : 431,
                  "balance" : 13136,
                  "firstname" : "Laurie",
                  "lastname" : "Shaw",
                  "age" : 26,
                  "gender" : "F",
                  "address" : "263 Aviation Road",
                  "employer" : "Zillanet",
                  "email" : "laurieshaw@zillanet.com",
                  "city" : "Harmon",
                  "state" : "WV"
                }
              }
                ...
        

        只要address包含mill或者road的文档都会匹配出来,但是这里要注意mill和road都是小写,如果写成

        ["Mill","Road"]则一条数据都没有,

        GET bank/_search
        {
          "query": {
            "terms": {
              "address": ["Mill","Road"]
            }
          }
        }
        

        结果

        {
          "took" : 0,
          "timed_out" : false,
          "_shards" : {
            "total" : 1,
            "successful" : 1,
            "skipped" : 0,
            "failed" : 0
          },
          "hits" : {
            "total" : {
              "value" : 0,
              "relation" : "eq"
            },
            "max_score" : null,
            "hits" : [ ]
          }
        }
        
        

        发生上述结果的原因请看这篇文章https://www.jianshu.com/p/d5583dff4157

        推荐对文本类型配时使用match,对非文本类型匹配时使用term

五、ES聚合操作

聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于SQL Group by和SQL聚合函数。在es中可以同时执行性查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的API避免网络往返。

执行聚合的语法

"aggs":{ # aggs标识聚合操作
    "aggs_name":{ # 集合的名称
        "aggs_type":{} # aggs_type哪种聚合
    }
}
  • avg聚合,以下查询平均年龄,"size":0表示不显示匹配结果,只会显示聚合结果

    GET bank/_search
    {
      "query": {
        "match_all": {
        }
      },
      "aggs": {
        "ageAggs": {
          "avg": {
            "field": "age"
          }
        }
      },
      "size": 0
    }
    
  • terms聚合(分组),以下按年龄分组并展示前5个分组

    GET bank/_search
    {
      "query": {
        "match_all": {
        }
      },
      "aggs": {
        "ageTerms": {
          "terms": {
            "field": "age",
            "size": 5
          }
        }
      },
      "size": 0
    }
    

    结果如下

    {
      "took" : 1,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1000,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [ ]
      },
      "aggregations" : {
        "ageTerms" : {
          "doc_count_error_upper_bound" : 0,
          "sum_other_doc_count" : 716,
          "buckets" : [
            {
              "key" : 31, # 年龄31
              "doc_count" : 61 # 有61条数据
            },
            {
              "key" : 39,
              "doc_count" : 60
            },
            {
              "key" : 26,
              "doc_count" : 59
            },
            {
              "key" : 32,
              "doc_count" : 52
            },
            {
              "key" : 35,
              "doc_count" : 52
            }
          ]
        }
      }
    }
    
  • 聚合可以嵌套,如下在按年龄分组的情况下求每一组聚合数据的balance的平均值

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

    结果如下

    {
      "took" : 5,
      "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" : 716,
          "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
              }
            },
            {
              "key" : 32,
              "doc_count" : 52,
              "ageAvg" : {
                "value" : 23951.346153846152
              }
            },
            {
              "key" : 35,
              "doc_count" : 52,
              "ageAvg" : {
                "value" : 22136.69230769231
              }
            }
          ]
        }
      }
    }
    

    es有非常多的聚合,具体的可以参见官方文档

六、Mapping映射

Maping是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和索引的,可以通过

GET 索引/_mapping来查看该索引下的映射映射信息,

GEt /bank/_mapping # 查看bank索引下的mapping信息

结果如下:

{
  "bank" : {
    "mappings" : {
      "properties" : {
        "account_number" : {
          "type" : "long"
        },
        "address" : {
          "type" : "text", # 字段的类型
          "fields" : {
            "keyword" : {
              "type" : "keyword", # text文本类型默认拥有一个keyword的子类型用于精确匹配
              "ignore_above" : 256
            }
          }
        },
        "age" : {
          "type" : "long"
        },
        "balance" : {
          "type" : "long"
        },
        "city" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "email" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "employer" : {
          "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
            }
          }
        }
      }
    }
  }
}

前边已经提到在ElasticSearch7-type已经被标记为过时,不在推荐我们是用,而在8中将直接被删除。在SQL数据库中,表彼此独立。一个表中的列与另一表中具有相同名称的列无关。而ES映射类型的字段不是这种情况。elasticsearch是基于Lucene开发的搜索引擎,而ES中不同type下名称相同的filed最终在Lucene中的处理方式是一样的。两个不同type下的两个user_name,在ES同一个索引下其实被认为是同一个filed,你必须在两个不同的type中定义相同的filed映射。否则,不同type中的相同字段名称就会在处理中出现冲突的情况,导致Lucene处理效率下降。去掉type就是为了提高ES处理数据的效率。

  1. 创建索引并指定该索引下的映射

    PUT /twitter
    {
      "mappings":{
          "properties": {
            "name": { "type": "text" },
            "user_name": { "type": "keyword" },
            "email": { "type": "keyword" }
        }
      }
    }
    
  2. 查看索引映射

    GET /twitter/_mapping
    GET /twitter
    
  3. 向索引中添加映射字段

    PUT /twitter/_mapping
    {
      "properties":{
        "age":{
          "type":"long",
          "index": false
        }
      }
    }
    

    其中 "index":false表示不能被检索

    增加数据

    POST /twitter/_doc/1
    {
        "age": 30,
        "email": "1769959702@qq.com",
        "name": "彭于晏",
        "user_name": "于晏"
    }
    

    结果

    {
      "_index" : "twitter",
      "_type" : "_doc",
      "_id" : "1",
      "_version" : 1,
      "result" : "created",
      "_shards" : {
        "total" : 2,
        "successful" : 1,
        "failed" : 0
      },
      "_seq_no" : 0,
      "_primary_term" : 1
    }
    

    查询数据

    GET twitter/_search
    {
      "query": {
        "match_all": {}
      }
    }
    
    

    结果

    {
      "took" : 0,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1,
          "relation" : "eq"
        },
        "max_score" : 1.0,
        "hits" : [
          {
            "_index" : "twitter",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 1.0,
            "_source" : {
              "age" : 30,
              "email" : "1769959702@qq.com",
              "name" : "彭于晏",
              "user_name" : "于晏"
            }
          }
        ]
      }
    }
    
    

    注意 age不可以被检索,当我们使用age作为检索条件时会报错

    GET twitter/_search
    {
      "query": {
        "match": {
          "age":30
        }
      }
    }
    

    结果如下

    {
      "error": {
        "root_cause": [
          {
            "type": "query_shard_exception",
            "reason": "failed to create query: {\n  \"match\" : {\n    \"age\" : {\n      \"query\" : \"30\",\n      \"operator\" : \"OR\",\n      \"prefix_length\" : 0,\n      \"max_expansions\" : 50,\n      \"fuzzy_transpositions\" : true,\n      \"lenient\" : false,\n      \"zero_terms_query\" : \"NONE\",\n      \"auto_generate_synonyms_phrase_query\" : true,\n      \"boost\" : 1.0\n    }\n  }\n}",
            "index_uuid": "zTjwQtxATCaVivg8cEShaw",
            "index": "twitter"
          }
        ],
        "type": "search_phase_execution_exception",
        "reason": "all shards failed",
        "phase": "query",
        "grouped": true,
        "failed_shards": [
          {
            "shard": 0,
            "index": "twitter",
            "node": "8NsO1v_fRViV681G83zVbg",
            "reason": {
              "type": "query_shard_exception",
              "reason": "failed to create query: {\n  \"match\" : {\n    \"age\" : {\n      \"query\" : \"30\",\n      \"operator\" : \"OR\",\n      \"prefix_length\" : 0,\n      \"max_expansions\" : 50,\n      \"fuzzy_transpositions\" : true,\n      \"lenient\" : false,\n      \"zero_terms_query\" : \"NONE\",\n      \"auto_generate_synonyms_phrase_query\" : true,\n      \"boost\" : 1.0\n    }\n  }\n}",
              "index_uuid": "zTjwQtxATCaVivg8cEShaw",
              "index": "twitter",
              "caused_by": {
                "type": "illegal_argument_exception",
                "reason": "Cannot search on field [age] since it is not indexed."
              }
            }
          }
        ]
      },
      "status": 400
    } 
    
    

    04 不能修改索引中已经存在的mapping字段,只能新建一个索引,将该索引下的所有数据转义到新的索引下

    基本步骤如下

    PUT tweets
    {
      "mappings": {
          "properties": {
            "name": {
              "type": "text"
            },
            "user_name": {
              "type": "keyword"
            },
            "email": {
              "type": "keyword"
            },
            "age":{
              "type":"integer"
            }
              
          }
      }
    }
    
    PUT tweets2
    {
      "mappings": {
        "properties": {
          "name": {
            "type": "text"
          },
          "user_name": {
            "type": "text"
          },
          "email": {
            "type": "text"
          },
          "age": {
            "type": "long"
          }
        }
      }
    }
    
    # 将tweets索引中的数据转移到tweets2中
    POST _reindex
    {
      "source": {
        "index": "tweets"
         # 如果存在type 则可以指定type
      },
      "dest": {
        "index": "tweets2"
         # 如果存在type 则可以指定type
      }
    }
    

    七、分词

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

    例如:whitespace tokenizer遇到空白字符时分割文本。它会将文本“Quick brown fox!”分割为[Quick,brown,fox!]。

    该tokenizer(分词器)还负责记录各个terms(词条)的顺序或position位置(用于phrase短语和word proximity词近邻查询),以及term(词条)所代表的原始word(单词)的start(起始)和end(结束)的character offsets(字符串偏移量)(用于高亮显示搜索的内容)。

    elasticsearch提供了很多内置的分词器,默认使用standard分词器,但是对中文不太友好我们一把会选择开源的ik分词器,并且可以指定自己的词库

    • 使用标准分词器查看分词效果
    POST _analyze
    {
      "analyzer":"standard",
      "text":"I am Chinese"
    
    }
    

    结果如下,会按照空格切分每个单词,但是如果是中文的话则会切分每个字

    {
      "tokens" : [
        {
          "token" : "i",
          "start_offset" : 0,
          "end_offset" : 1,
          "type" : "<ALPHANUM>",
          "position" : 0
        },
        {
          "token" : "am",
          "start_offset" : 2,
          "end_offset" : 4,
          "type" : "<ALPHANUM>",
          "position" : 1
        },
        {
          "token" : "chinese",
          "start_offset" : 5,
          "end_offset" : 12,
          "type" : "<ALPHANUM>",
          "position" : 2
        }
      ]
    }
    
    
    • 安装ik分词器

      github下载地址https://github.com/medcl/elasticsearch-analysis-ik/releases/download

      下载解压后放在es的plugs/ik目录下,并重启es即可

      ik支持两种分词

      1. ik_smart:会做最粗粒度的拆分
      2. ik_max_word::会将文本做最细粒度的拆分

      测试ik分词

      POST _analyze
      {
        "analyzer":"ik_max_word",
        "text":"不爱国不是中国人"
      
      }
      
      

      结果如下,ik_max_word会尽可能细的拆分

      {
        "tokens" : [
          {
            "token" : "不爱国",
            "start_offset" : 0,
            "end_offset" : 3,
            "type" : "CN_WORD",
            "position" : 0
          },
          {
            "token" : "不爱",
            "start_offset" : 0,
            "end_offset" : 2,
            "type" : "CN_WORD",
            "position" : 1
          },
          {
            "token" : "爱国",
            "start_offset" : 1,
            "end_offset" : 3,
            "type" : "CN_WORD",
            "position" : 2
          },
          {
            "token" : "不是",
            "start_offset" : 3,
            "end_offset" : 5,
            "type" : "CN_WORD",
            "position" : 3
          },
          {
            "token" : "中国人",
            "start_offset" : 5,
            "end_offset" : 8,
            "type" : "CN_WORD",
            "position" : 4
          },
          {
            "token" : "中国",
            "start_offset" : 5,
            "end_offset" : 7,
            "type" : "CN_WORD",
            "position" : 5
          },
          {
            "token" : "国人",
            "start_offset" : 6,
            "end_offset" : 8,
            "type" : "CN_WORD",
            "position" : 6
          }
        ]
      }
      
    • 自定义词库

      修改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">mydic.dic</entry>
      	<!--用户可以在这里配置远程扩展字典 -->
      	<entry key="remote_ext_dict">http://192.168.137.14/es/fenci.txt</entry> 
      	<!--用户可以在这里配置远程扩展停止词字典-->
      	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
      </properties>
      
      

      可以配置远程的扩展字典,也可以配置本地的扩展字典,上边我们配置的本地的扩展字典,再到config目录中新建一个mydic.dic文件内容就是我们扩展的词语

      image-20210113203633371

      重启es即可.值得注意的是,扩展的分词对历史数据并不生效,如果想要历史数据重新分词则需参照其他办法.。

参考简书https://www.jianshu.com/p/d5583dff4157

参考简书https://www.jianshu.com/p/d48c3242378

参考CSDN苏州打工人https://blog.csdn.net/winterking3/article/details/108095851

参考CSDNhttps://blog.csdn.net/hancoder/article/details/107612746

posted @ 2021-01-13 21:21  下海搬砖  阅读(430)  评论(0编辑  收藏  举报