Elasticsearch 教程

Elasticsearch
Author

Shitao5

Published

2023-09-01

Modified

2023-09-05

Progress

Learning Progress: Completed.😀

Elasticseach

1 简介

  • Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎, 国内简称ES,Elasticsearch是用java开发的,底层基于Lucene, Lucene是一种全文检索的搜索库,直接使用Lucene还是比较麻烦的,Elasticsearch在Lucene的基础上开发了一个强大的搜索引擎。

  • ELK代表了Elasticsearch + Logstash + Kibana 三套软件,他们的作用如下:

    • Elasticsearch:前面简介提到过,解决海量数据搜索问题。

    • Logstash:解决数据同步问题,因为我们数据一般存储在Mysql之类的数据库中,需要将数据导入到ES中,Logstash就支持数据同步、数据过滤、转换功能。

    • Kibana:Elasticsearch数据可视化支持,例如:通过各种图表展示ES的查询结果,也可以在Kibana通过ES查询语句分析数据,起到类似ES Web后台的作用。

2 存储结构

Warning

MYSQL 是关系数据库,Elasticsearch是NOSQL类型的数据库,虽然他们都是数据库,但是他们定位不一样,也不是同一类型的数据库,拿来做对比,是因为一方面对MYSQL比较熟悉,另外从使用角度ES的存储结构跟MYSQL比较相似。

  • 在Elasticsearch中索引(index)类似mysql的表,代表文档(Document)数据的集合,文档指的是ES中存储的一条数据。

  • Elasticsearch是面向文档的数据库,文档是最基本的存储单元,文档类似mysql表中的一行数据。简单的说在ES中,文档指的就是一条JSON数据。Elasticsearch中文档使用json格式存储,因此存储上比Mysql要灵活的多,Elasticsearch支持任意格式的json数据。

  • 文档中的任何json字段都可以作为查询条件。文档的json格式没有严格限制,可以随意增加、减少字段,甚至每一个文档的格式都不一样也可以。

  • 文档由多个json字段(Field)组成, 这里的字段类似mysql中表的字段。当然Elasticsearch中字段也有类型的,下面是常用的字段类型:

    • 数值类型(包括: long、integer、short、byte、double、float)

    • text:支持全文搜索

    • keyword:不支持全文搜索,例如:email、电话这些数据,作为一个整体进行匹配就可以,不需要分词处理。

    • date:日期类型

    • boolean

  • Elasticsearch的mapping(映射)类似mysql中的表结构定义,每个索引都有一个映射规则,我们可以通过定义索引的映射规则,提前定义好文档的json结构和字段类型,如果没有定义索引的映射规则,Elasticsearch会在写入数据的时候,根据我们写入的数据字段推测出对应的字段类型,相当于自动定义索引的映射规则。

Table 2.1: 类比MYSQL存储结构
Elasticsearch存储结构 MYSQL存储结构
index(索引)
document(文档) 行,一行数据
Field(字段) 表字段
mapping (映射) 表结构定义

3 文档 CRUD

  • 文档元数据,指的是插入JSON文档的时候,Elasticsearch为这条数据,自动生成的系统字段。元数据的字段名都是以下划线开头的。常见的元数据如下:

    • _index:代表当前JSON文档所属的索引名字

    • _type:代表当前JSON文档所属的类型,虽然新版ES废弃了type的用法,但是元数据还是可以看到。

    • _id:文档唯一Id, 如果我们没有为文档指定id,系统会自动生成

    • _source:代表我们插入进去的JSON数据

    • _version:文档的版本号,每修改一次文档数据,字段就会加1, 这个字段新版的ES已经不使用了

    • _seq_no:文档的版本号, 替代老的_version字段

    • _primary_term:文档所在主分区,这个可以跟_seq_no字段搭配实现乐观锁。

  • 在Elasticsearch插入一个JSON文档,又叫索引文档, 注意这里的索引跟前面提到的文档所属的索引名,不是一回事,很晕吧,其实主要翻译问题,我们将数据插入到ES的过程,其实就是创建索引的过程,所以插入文档,也叫做索引文档,这里索引是动词, 而文档属于哪个索引(index),这里的索引代表一个分类,有数据库的概念,是个名词。搞不清楚也没关系,知道索引文档的意思,其实就是往ES插入数据就行。

4 文档类型定义

  • 精确值通常指的就是数值类型、时间、布尔值、字符串的keyword类型,这些不可分割的数据类型,精确值搜索效率比较高,精确值匹配类似MYSQL中根据字段搜索,例如:拿一个手机号去搜索数据,对于每一个文档的手机号字段,要么相等,要么不等,不会做别的计算。

  • 全文类型,指的就是text类型,会涉及分词处理,存储到ES中的数据不是原始数据,是一个个关键词。例如:我们有一个title字段,数据类型是text,我们插入”上海复旦大学”这个字符串,经过分词处理,可能变成:“上海”、“复旦大学”、“大学” 这些关键词,然后根据这些关键词建倒排索引。

  • 查看索引映射规则:

    GET /order/_mapping

查询

5 基本语法结构

GET /{索引名}/_search
{
    "from" : 0,  // 返回搜索结果的开始位置
    "size" : 10, // 分页大小,一次返回多少数据
    "_source" :[ ...需要返回的字段数组... ],
    "query" : { ...query子句... },
    "aggs" : { ..aggs子句..  },
    "sort" : { ..sort子句..  }
}
  • query子句主要用来编写类似SQL的Where语句,支持布尔查询(and/or)、IN、全文搜索、模糊匹配、范围查询(大于小于)。

  • aggs子句,主要用来编写统计分析语句,类似SQL的group by语句。

  • sort子句,用来设置排序条件,类似SQL的order by语句。

  • ES查询的分页主要通过from和size参数设置,类似MYSQL 的limit和offset语句。

  • _source用于设置查询结果返回什么字段,类似Select语句后面指定字段。

6 query 查询

6.1 匹配单个字段

GET /{索引名}/_search
{
  "query": {
    "match": {
      "{FIELD}": "{TEXT}"
    }
  }
}

如果title字段的数据类型是text类型,搜索关键词会进行分词处理。

6.2 精确匹配单个字段

如果我们想要类似SQL语句中的等值匹配,不需要进行分词处理,例如:订单号、手机号、时间字段,不需要分值处理,只要精确匹配。通过term实现精确匹配语法:

GET /{索引名}/_search
{
  "query": {
    "term": {
      "{FIELD}": "{VALUE}"
    }
  }
}

6.3 通过terms实现SQL的in语句

如果我们要实现SQL中的in语句,一个字段包含给定数组中的任意一个值就匹配。terms语法:

GET /order_v2/_search
{
  "query": {
    "terms": {
      "{FIELD}": [
        "{VALUE1}",
        "{VALUE2}"
      ]
    }
  }
}

6.4 范围查找

通过range实现范围查询,类似SQL语句中的>, >=, <, <=表达式。range语法:

GET /{索引名}/_search
{
  "query": {
    "range": {
      "{FIELD}": {
        "gte": 10, 
        "lte": 20
      }
    }
  }
}

范围参数:

  • gt:大于(>)

  • gte:大于且等于(>=)

  • lt:小于(<)

  • lte:小于且等于(<=)

6.5 bool组合查询

GET /{索引名}/_search
{
  "query": {
    "bool": { // bool查询
      "must": [], // must条件,类似SQL中的and, 代表必须匹配条件
      "must_not": [], // must_not条件,跟must相反,必须不匹配条件
      "should": [] // should条件,类似SQL中or, 代表匹配其中一个条件
    }
  }
}

可以任意选择must、must_not和should条件的参数都是一个数组,意味着他们都支持设置多个条件。

7 全文搜索

ES通过分词处理、相关度计算解决不同文章相关度对比问题。ES内置了一些相关度算法,例如:TF/IDF算法,大体上思想就是,如果一个关键词在一篇文章出现的频率高,并且在其他文章中出现的少,那说明这个关键词与这篇文章的相关度很高。

分词就是为了提取搜索关键词,理解搜索的意图,我们平时在百度搜索内容的时候,输入的内容可能很长,但不是每个字都对搜索有帮助,所以通过分词算法,我们输入的搜索关键词,会进一步分解成多个关键词。

在ES中测试分词效果:

GET /_analyze
{
  "text": "需要分词的内容",
  "analyzer": "分词器"
}

目前中文分词器比较常用的有:smartcn和ik两种。smartcn是目前ES官方推荐的中文分词插件,不过目前不支持自定义词库;ik支持自定义扩展词库。

8 排序

ES的默认排序是根据相关性分数排序,如果我们想根据查询结果中的指定字段排序,需要使用sort Processors处理。

GET /{索引名}/_search
{
  "query": {
    ...查询条件....
  },
  "sort": [
    {
      "{Field1}": { // 排序字段1
        "order": "desc" // 排序方向,asc或者desc, 升序和降序
      }
    },
    {
      "{Field2}": { // 排序字段2
        "order": "desc" // 排序方向,asc或者desc, 升序和降序
      }
    }
    ....多个排序字段.....
  ]
}

聚合分析

9 统计分析概念

ES聚合查询类似SQL的GROUP BY,一般统计分析主要分为两个步骤:

  • 分组

  • 组内聚合

9.1 核心概念

满足特定条件的文档的集合,叫做。桶的就是一组数据的集合,对数据分组后,得到一组组的数据,就是一个个的桶。

ES中桶聚合,指的就是先对数据进行分组,ES支持多种分组条件,例如:支持类似SQL的GROUP BY根据字段分组,当然ES比SQL更强大,支持更多的分组条件,以满足各种统计需求。

Note

桶等同于组,分桶和分组是一个意思,ES使用桶代表一组相同特征的数据。

9.2 ES聚合查询语法

{
  "aggregations" : {
    "<aggregation_name>" : {
        "<aggregation_type>" : {
            <aggregation_body>
        }
        [,"aggregations" : { [<sub_aggregation>]+ } ]? // 嵌套聚合查询,支持多层嵌套
    }
    [,"<aggregation_name_2>" : { ... } ]* // 多个聚合查询,每个聚合查询取不同的名字
  }
}

说明:

  • aggregations:代表聚合查询语句,可以简写为aggs

  • <aggregation_name>:代表一个聚合计算的名字,可以随意命名,因为ES支持一次进行多次统计分析查询,后面需要通过这个名字在查询结果中找到我们想要的计算结果。

  • <aggregation_type>:聚合类型,代表我们想要怎么统计数据,主要有两大类聚合类型,桶聚合和指标聚合,这两类聚合又包括多种聚合类型,例如:指标聚合:sum、avg, 桶聚合:terms、Date histogram等等。

  • <aggregation_body>:聚合类型的参数,选择不同的聚合类型,有不同的参数。

  • aggregation_name_2:代表其他聚合计算的名字,意思就是可以一次进行多种类型的统计。

例子:

GET /order/_search
{
    "size" : 0, // 设置size=0的意思就是,仅返回聚合查询结果,不返回普通query查询结果。
    "aggs" : { // 聚合查询语句的简写
        "popular_colors" : { // 给聚合查询取个名字,叫popular_colors
            "terms" : { // 聚合类型为,terms,terms是桶聚合的一种,类似SQL的group by的作用,根据字段分组,相同字段值的文档分为一组。
              "field" : "color" // terms聚合类型的参数,这里需要设置分组的字段为color,根据color分组
            }
        }
    }
}

等价SQL如下:

SELECT COUNT(color) 
FROM order
GROUP BY color

10 指标聚合

10.1 统计函数

ES指标聚合,就是类似SQL的统计函数,指标聚合可以单独使用,也可以跟桶聚合一起使用。

常用的统计函数如下:

  • Value Count:类似sql的count函数,统计总数

    GET /sales/_search?size=0
    {
      "aggs": {
        "types_count": { // 聚合查询的名字,随便取个名字
          "value_count": { // 聚合类型为:value_count
            "field": "type" // 计算type这个字段值的总数
          }
        }
      }
    }
  • Cardinality:类似SQL的count(DISTINCT 字段),统计不重复的数据总数

    POST /sales/_search?size=0
    {
        "aggs" : {
            "type_count" : { // 聚合查询的名字,随便取一个
                "cardinality" : { // 聚合查询类型为:cardinality
                    "field" : "type" // 根据type这个字段统计文档总数
                }
            }
        }
    }
  • Avg:求平均值

    POST /exams/_search?size=0
    {
      "aggs": {
        "avg_grade": { // 聚合查询名字,随便取一个名字
          "avg": { // 聚合查询类型为: avg
            "field": "grade" // 统计grade字段值的平均值
          }
        }
      }
    }
  • Sum:求和(同Avg)

  • Max:求最大值(同Avg)

  • Min:求最小值(同Avg)

10.2 综合例子

实际应用中经常先通过query查询,搜索索引中的数据,然后对query查询的结果进行统计分析。

GET /sales/_search
{
  "size": 0, // size = 0,代表不想返回query查询结果,只要统计结果
  "query": { // 设置query查询条件,后面的aggs统计,仅对query查询结果进行统计
    "constant_score": {
      "filter": {
        "match": {
          "type": "hat"
        }
      }
    }
  },
  "aggs": { // 统计query查询结果, 默认情况如果不写query语句,则代表统计所有数据
    "hat_prices": { // 聚合查询名字,计算price总和
      "sum": {
        "field": "price"
      }
    },
    "min_price": { // 聚合查询名字,计算price最小值
      "min": { 
        "field": "price" 
      }
    },
    "max_price": { // 聚合查询名字,计算price最大值
      "max": { 
        "field": "price"
      }
    }
  }
}

11 分组统计

Elasticsearch桶聚合,目的就是数据分组,先将数据按指定的条件分成多个组,然后对每一个组进行统计。 组的概念跟桶是等同的,在ES中统一使用桶(bucket)这个术语。

ES桶聚合的作用跟SQL的group by的作用是一样的,区别是ES支持更加强大的数据分组能力,SQL只能根据字段的唯一值进行分组,分组的数量跟字段的唯一值的数量相等,例如: group by 店铺id, 去掉重复的店铺ID后,有多少个店铺就有多少个分组。

11.1 聚合类型

ES常用的桶聚合如下:

  • Terms聚合:类似SQL的group by,根据字段唯一值分组

    GET /order/_search?size=0
    {
      "aggs": {
        "shop": { // 聚合查询的名字,随便取个名字
          "terms": { // 聚合类型为: terms
            "field": "shop_id" // 根据shop_id字段值,分桶
          }
        }
      }
    }
  • Histogram聚合:根据数值间隔分组,例如: 价格按100间隔分组,0、100、200、300等等

    POST /sales/_search?size=0
    {
        "aggs" : {
            "prices" : { // 聚合查询名字,随便取一个
                "histogram" : { // 聚合类型为:histogram
                    "field" : "price", // 根据price字段分桶
                    "interval" : 50 // 分桶的间隔为50,意思就是price字段值按50间隔分组
                }
            }
        }
    }
  • Date histogram聚合:根据时间间隔分组,例如:按月、按天、按小时分组

    POST /sales/_search?size=0
    {
        "aggs" : {
            "sales_over_time" : { // 聚合查询名字,随便取一个
                "date_histogram" : { // 聚合类型为: date_histogram
                    "field" : "date", // 根据date字段分组
                    "calendar_interval" : "month", //     分组间隔:month代表每月、支持minute(每分钟)、hour(每小时)、day(每天)、week(每周)、year(每年)
                    "format" : "yyyy-MM-dd" // 设置返回结果中桶key的时间格式
                }
            }
        }
    }
  • Range聚合:按数值范围分组,例如: 0-150一组,150-200一组,200-500一组。

    GET /_search
    {
       "aggs" : {
            "price_ranges" : { // 聚合查询名字,随便取一个
               "range" : { // 聚合类型为: range
                    "field" : "price", // 根据price字段分桶
                    "ranges" : [ // 范围配置
                       { "to" : 100.0 }, // 意思就是 price <= 100的文档归类到一个桶
                        { "from" : 100.0, "to" : 200.0 }, // price>100 and price<200的文档归类到一个桶
                        { "from" : 200.0 } // price>200的文档归类到一个桶
                   ]
               }
            }
        }
    }

11.2 综合例子

GET /cars/_search
{
    "size": 0, // size=0代表不需要返回query查询结果,仅仅返回aggs统计结果
    "query" : { // 设置查询语句,先赛选文档
        "match" : {
            "make" : "ford"
        }
    },
    "aggs" : { // 然后对query搜索的结果,进行统计
        "colors" : { // 聚合查询名字
            "terms" : { // 聚合类型为:terms 先分桶
              "field" : "color"
            },
            "aggs": { // 通过嵌套聚合查询,设置桶内指标聚合条件
              "avg_price": { // 聚合查询名字
                "avg": { // 聚合类型为: avg指标聚合
                  "field": "price" // 根据price字段计算平均值
                }
              },
              "sum_price": { // 聚合查询名字
                "sum": { // 聚合类型为: sum指标聚合
                  "field": "price" // 根据price字段求和
                }
              }
            }
        }
    }
}

12 多桶排序

类似terms、histogram、date_histogram这类桶聚合都会动态生成多个桶,如果生成的桶特别多,我们如何确定这些桶的排序顺序,如何限制返回桶的数量。

默认情况,ES会根据doc_count文档总数,降序排序。

ES桶聚合支持两种方式排序:

  • 内置排序

    • _count:按文档数排序。对 terms 、 histogram 、 date_histogram 有效

    • _term:按词项的字符串值的字母顺序排序。只在 terms 内使用

    • _key:按每个桶的键值数值排序, 仅对 histogram 和 date_histogram 有效

    例子:

    GET /cars/_search
    {
       "size" : 0,
       "aggs" : {
            "colors" : { // 聚合查询名字,随便取一个
                "terms" : { // 聚合类型为: terms
                  "field" : "color", 
                  "order": { // 设置排序参数
                   "_count" : "asc"  // 根据_count排序,asc升序,desc降序
                  }
               }
            }
       }
    }
  • 按度量指标排序

    通常情况下,我们根据桶聚合分桶后,都会对桶内进行多个维度的指标聚合,所以我们也可以根据桶内指标聚合的结果进行排序。

    GET /cars/_search
    {
       "size" : 0,
       "aggs" : {
           "colors" : { // 聚合查询名字
                "terms" : { // 聚合类型: terms,先分桶
                  "field" : "color", // 分桶字段为color
                  "order": { // 设置排序参数
                    "avg_price" : "asc"  // 根据avg_price指标聚合结果,升序排序。
                  }
                },
                "aggs": { // 嵌套聚合查询,设置桶内聚合指标
                    "avg_price": { // 聚合查询名字,前面排序引用的就是这个名字
                        "avg": {"field": "price"} // 计算price字段平均值
                    }
                }
            }
        }
    }

如果分桶的数量太多,可以通过给桶聚合增加一个size参数限制返回桶的数量:

GET /_search
{
    "aggs" : {
        "products" : { // 聚合查询名字
            "terms" : { // 聚合类型为: terms
                "field" : "product", // 根据product字段分桶
                "size" : 5 // 限制最多返回5个桶
            }
        }
    }
}

SQL

13 SQL 简介

我们可以直接通过REST API执行SQL语句,语法格式如下:

POST /_sql?format=txt
{
    "query": "这里书写SQL语句"
}

14 SQL 语法

ES 支持的 SQL 命令:

  • 查询ES索引的字段和类型

    POST /_sql?format=txt
    {
       "query": "SHOW COLUMNS FROM library"
    }
  • 将ES中所有的索引都列出来

    POST /_sql?format=txt
    {
        "query": "SHOW TABLES"
    }
  • 展示ES支持的SQL函数有哪些

    POST /_sql?format=txt
    {
        "query": "SHOW FUNCTIONS"
    }

15 SQL 全文搜索

ES SQL语法虽然支持like语句,但是like并不是使用全文搜索算法,ES SQL语句中主要通过MATCH函数实现全文搜索。

POST /_sql?format=txt
{
    "query": "SELECT author, name FROM library WHERE MATCH(author, 'frank')"
}

通过SCORE()函数实现相关度排序:

POST /_sql?format=txt
{
    "query": "SELECT author, name FROM library WHERE MATCH(author, 'frank') ORDER BY SCORE()"
}
Back to top