Elasticsearch学习系列(一)——基本使用、核心数据类型、分析器

263 阅读14分钟

es全称Elasticsearch是一个基于Lucene的高扩展的分布式搜索服务器,隐藏了Lucene的复杂性,对外提供Restful 接口来操作索引、搜索,支持开箱即用。支持分布式,扩展性好,可部署上百台服务器集群,可以实现近实时的获取索引数据、搜索数据。

一、怎样使用

不以满足实际应用需求为出发点的理论基础都是耍流氓,话不多说,先看看使用姿势。Mysql关系型数据库大家使用的比较多,这里以Mysql为例子进行对比方便大家理解

Mysql和Es类比
MysqlElastic Search
数据库(Database)索引(Index)
表(Table)类型(Type)
表结构(Schema)映射(Mapping)
行(Row)文档(Document)
列(Column)字段(Field)
SqlDSL

平台:Kibana是一个开源的分析与可视化平台

1.1 创建数据库、创建表

创建索引时需要设置settings、mappings,settings配置索引的信息,可以配置主分片数、副本分片数、刷新周期(写入数据刷新到缓存文件中)、整个索引的分析器(或自定义分析器)等信息,配置信息分为静态配置动态配置,静态配置信息创建索引时一旦配置不能修改,动态配置信息配置后可以进行修改;mappings设置索引的属性,如字段、数据类型、格式、字段的分析器、是否可以搜索等信息,映射可分为静态映射动态映射

// 格式
PUT /my_index    // 索引
{
    "settings": { ... any settings ... },    // 配置信息
    "mappings": {
        // es7之前 ①
        "type_one": { ... any mappings ... },
        "type_two": { ... any mappings ... },
        ...
        // es7之后
        { ... any mappings ... }            // 字段属性
    }
}

// 具体示例
PUT tl_test1 
{
  "settings": {    
    "number_of_shards": 10,    // 主分片数(默认为5,静态配置,不能进行修改)
    "number_of_replicas": 1,   // 副本分片数
    "refresh_interval": "1s"   // 刷新周期(单位ms、s、m),不带单位默认ms
  },
  "mappings": {"properties": { 
      "uid": {                 
        "type": "long"         
      },
      "brief": {                 
          "type": "text"         
      },
      "message": {
        "type": "keyword"
      },
      "sendtime": {            // 字段
        "type": "date",        // 类型
        "format": "yyyy-MM-dd HH:mm:ss"    // 格式  
      }
    }
  }
}

值得一提的是针对于类型(Type)的创建,与Mysql数据库表之间的相互独立的关系不同,Es类型之间不是相互独立的,不同的映射类型中存在相同的属性名,他们底层都是使用同一个Lucene属性,要求数据类型也是要相同的。如同一个索引中Type1表示老师,Type2表示学生,Type1、Type2中都有一个字段name,那么这个字段在Type1和Type2中的数据类型必须是一致的。

es7之前是允许一个索引下有多个类型的,es7使用默认的_doc来代替也就是一个索引中只有一个_doc类型,不过可以通过设置来支持一个索引多个类型,但是es8之后就禁止了这种行为,一个索引只包含一种类型,可以理解为库表合一,可以把一个索引看成一个库也可以作为一张表,索引之间是相互独立的。

到这里Es索引已经创建完毕了,可以直接使用了。这里拓展介绍一下Es的映射,Es的映射也是非常灵活的,分为静态映射动态映射

  1. 静态映射

  2. 动态映射

  • 动态映射配置
PUT /my_index
{
    "mappings": {
        "dynamic":      "true", 
    }
}
// true——动态添加新的字段—缺省
// false——忽略新的字段
// strict——如果遇到新字段抛出异常

  • 默认动态映射
Json数据类型Es数据类型
布尔值boolean
浮点数float
整数long
对象object
数组第一个非空值的类型
字符串1.如果满足日期类型的格式,映射为日期类型2.如果满足数字型格式,映射为long或者float3.如果是字符串,会映射为一个text类型和一个keyword类型,keyword类型会作为text类型的子字段
  • 自定义动态映射

为了打破默认动态映射的限制,Es支持自定义动态映射,按照一定格式进行字段输入可以映射为配置好的类型

PUT /tl_test1
{
    "mappings": {
        "dynamic": true,
        "dynamic_templates": [
            {
                "keyword_match": {                    // es字段名称
                    "match_mapping_type": "string",   // 输入字段的类型 
                    "match": "*_keyword",             // 输入字段后缀为_Keyword会定义为string类型
                    "mapping": {
                        "type": "keyword"             // es字段类型
                    }
                }
            },
            {
                "nested_match": {
                    "match_mapping_type": "object",
                    "match": "*_nested",    // 输入字段后缀为_nested会定义为nested类型
                    "mapping": {
                        "type": "nested"
                    }
                }
            }
        ]
    }
}

1.2 增删改查

1.2.1 增/改

// 创建(id唯一,不能重复创建)
POST test1/_create/1 
{
    "uid" : "1012988874",
    "message" : "feishu",
    "sendtime" : "2022-02-22 22:22:22",
    "brief": "Legends Never Die"
}

// 更新文档中部分字段
POST tl_test1/_update/1
{
  "doc": {
    "message": "feishu"
  }
}

// 通过脚本更新字段
// 通过id更新部分字段
POST tl_test1/_update/1
{
  "script": {"source": "ctx._source['message'] = 'FeiShu'"
  }
}

// 通过匹配更新字段
POST tl_test1/_update_by_query
{
  "query": {
    "term": { // query搜索参数
      "message": "feishu"
    }
  } ,
  "script": {
    // 将message字段内容修改为xuwujing
    "source": "ctx._source['message'] = 'FeiShu'"
  }
}

Elasticsearch支持脚本对数据进行操作,使用的脚本是script,目前脚本的资料也基本都来源于官方文档,官方文档也明确指出了使用脚本会导致性能降低。普通业务中基本的增删改查就能满足需求,但不能否认在特别复杂的业务需求中也是需要用到脚本来解决的,这也是脚本存在的意义。感兴趣的同学可以看一下官方文档:How to write scripts | Elasticsearch Guide [master] | Elastic

1.2.2 删除

// 根据唯一键删除
DELETE tl_test1/1

// 根据匹配条件进行删除
POST tl_test1/_delete_by_query
{
  "query": {
    "term": {
      "message": "feishu"        
    }
  }
}

// 删库跑路
POST tl_test1/_delete_by_query
{
  "query": {
    "match_all": {}
  }
}

1.2.3 查

搜索是我们的核心需求,也是Es的核心功能,根据条件搜索、聚合、排序功能非常强大,Es提供了40多种查询关键字,这里我们简单列举基础且常用的操作供大家了解,深入研究可以参考官方文档,官方文档指路:Query DSL | Elasticsearch Guide [master] | Elastic

1.2.3.1 基本查询

// 根据唯一键查询
GET tl_test1/_doc/1

// 根据条件进行搜索
GET tl_test1/_search
{
  "query": {
    "match_all": {}
  }
}

根据条件搜索就要介绍一下Es支持的query查询语法,官方将其分为两大类,一类是特定搜索,一类是组合搜索

  • 特定搜索,在特定字段中查找特定值,如:term、match、range等。
// term用来进行精准匹配
// 查询message为feishu的所有记录
GET tl_test1/_search
{
  "query": {
    "term": {
      "message": "feishu"    
    }
  }
}

// terms查询类似sql中的in
GET tl_test1/_search
{
  "query": {
    "terms": {
      "uid": [               
        1012988874,       
        1012988873
      ]
    }
  }
}

// match用来进行全文检索(首先会进行分词)
GET tl_test1/_search
{
  "query": {
    "match": {
      "brief": "Legends Die, Oh My God"
    }
  }
}

// wildcard用来进行模糊匹配
GET tl_test1/_search
{
  "query": {
    "wildcard": {
      "message": {
        "value": "*ei*"
      }
    }
  }
}

// range用来进行范围匹配,gt大于,lt小于,gte大于等于,lte小于等于
GET  tl_test1/_doc/_search
{
  "query": {
   "range": { 
      "uid": { 
        "gt": 1012988872,
        "lte": 1012988874
      } 
    } 
  }
}
  • 组合查询,如:bool、dis_max等。

bool可选组合参数

must查询子句条件必须满足,相当于mysql的and,并将有助于得分
filter查询子句条件必须满足,相当于mysql的and,与 must查询不同,filter查询过滤文档不会对文档的匹配程度进行评分。因此需求场景没有评分排序推荐使用filter提高搜索性能
should查询子句条件应该满足,相当于mysql的or
must_not查询子句条件不能满足,忽略评分
minimum_should_match可以控制当前语句匹配should数量或者控制匹配语句百分比
// bool查询
GET tl_test1/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "message": {
              "value": "qq"
            }
          }
        },
        {
          "range": {
            "uid": {
              "gt": 1012988872,
              "lte": 1012988874
            }
          }
        }
      ],
      "should": [
        {
          "terms": {
            "uid": [
              "1012988873"
            ]
          }
        }
      ]
    }
  }
}

1.2.3.2 聚合、排序

  • 聚合

Es聚合(类似Mysql中的group by),步骤也是分为两步:1:分组;2:组内聚合。在Es中分组也可以称为分桶。把满足特定条件的所有文档集合叫做桶,文档按照特定条件分成不同的集合也就是分成一个个桶,在桶中进行一系列聚合操作。Es也是提供了50多种聚合关键字,这里我们介绍几种常用的。

// Value Count——类似sql的count函数,统计总数
// terms——类似sql的group by,会统计每个分组的数量
// min——取最小值
// max——取最大值
// avg——去平均值
// sum——求和
GET tl_test1/_search
{
  "aggs": {
    "uid_count": {
      "value_count": {
        "field": "uid"
      }
    },
    "uid_terms": {
      "terms": {
        "field": "uid"
      }
    },
    "uid_min": {
      "min": {
        "field": "uid"
      }
    },
    "uid_max": {
      "max": {
        "field": "uid"
      }
    },
    "uid_avg": {
      "avg": {
        "field": "uid"
      }
    },
    "uid_sum": {
      "sum": {
        "field": "uid"
      }
    }
  }
}
  • 排序

Es中使用sort关键词进行排序(类似Mysql中的order by)

// 按照message字典序升序排序,若相同按照uid倒序排序
GET tl_test1/_search
{
  "sort": [
    {
      "message": {
        "order": "asc"
      },
      "uid": {
        "order": "desc",
        "mode": "min"}
    }
  ]
}

排序时可以加入metric聚合方式,如一个字段为数组形式,可以使用如下聚合方式先进行聚合再对聚合结果进行排序:sum、min、max、avg等。以min为例含义为若uid字段为数组类型,则取数组中最小值进行排序。其余聚合方式含义类似。

二、核心数据类型

2.1 字符串类型

  • keyword:这种字符串也称之为 not-analyzed 字段,可以用作过滤、排序、聚合等。如邮箱、电话。

  • text:这种字符串称为 analyzed 字段,如果一个字段需要被全文检索,可以选择 text 类型,如新闻内容、论文。使用 text 字段,内容会被指定的分词器进行分词,生成倒排索引。text字段不用于排序和聚合。

2.2 数字类型

  • 整型

  • 浮点型

①精度:单精度浮点数占4字节即32位,双精度浮点数占8字节即64位,各个位作用如下

单精度占位

双精度占位

精度主要取决于尾数部分的位数,float为23位,除去全部为0的情况以外,最小为2的-23次方,约等于1.19乘以10的-7次方,所以float小数部分只能精确到后面6位,加上小数点前的一位,即有效数字为7位。 类似,double 尾数部分52位,最小为2的-52次方,约为2.22乘以10的-16次方,所以精确到小数点后15位,有效位数为16位。

2.3 日期类型

  • date:日期类型,可以指定格式。

2.4 布尔类型

  • boolean

2.5 对象类型

  • object:对象类型是指字段的值是一个JSON文档,JSON文档本质上是分层的,文档包含内部对象,内部对象本身还可以包含内部对象。
// 对象定义
PUT tl_test2 
{
  "mappings": {
    "properties": { 
      "name": {
        "type": "object", 
        "properties": {
          "first": {
            "type": "keyword"
          },
          "last": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

// 放入一个文档
PUT tl_test2/_doc/1
{
  "name": [
    {
      "first": "John",
      "last": "Red"
    },
    {
      "first": "Alice",
      "last": "Blue"
    }
  ]
}

// 迷惑性搜索:搜索结果为能够将上述添加数据搜索出来
GET tl_test2/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "name.first": "John"
          }
        },
        {
          "term": {
            "name.last": "Blue"
          }
        }
      ]
    }
  }
}

明明John对应的是Red,为什么搜索John对应Blue也能够搜索出?结论就是Es为了优化搜索性能,将对象类型进行了扁平化处理,在Es中对象是这么存储的。

name.first:["John","Alice"]
name.last:["Red","Blue"]

这样的存储就丢失了对象数组中每个个体中内容的关联性,这种情况怎么处理呢?就用到了另一种类型nested类型。

2.6 Nested类型

  • nested:嵌套类型,用来处理数组对象信息存在关联关系的情况,使用方法类似object类型

2.7 数组类型

ElasticSearch没有专门的数组类型,任何字段类型都可以变成数组,数组中的类型必须为同一类型

三、分析器

3.1 分析器简介

分析器就是ES支持全文搜索的基础。比如我们搜索"Quick fox jumps",想要获取如下文档"A quick brown fox jumps over the lazy dog"。很明显,如果把字符串作为一个整体进行匹配无法达到我们的目的。如果我们搜索"foxes"或者"leap"也想获取目标搜索结果呢?ES都能够办到吗?如果能办到是怎么办到的?

ES的分析器包含三大部分,分别为分词器(Tokenizer)、令牌过滤器(token filter)、字符过滤器(character filter)。

  • 分词器:分析器只能包含一个分词器,分词器它会将文本 "Quick brown fox!"转换为[Quick, brown, fox!]。

  • 令牌过滤器:分析器可能有零个或多个令牌过滤器,它们按顺序应用。令牌过滤器接收令牌流并可以添加、删除或更改令牌。例如,lowercase过滤器将所有令牌转换为小写,stop令牌过滤器从令牌流中删除常用词(停用词synonym令牌过滤器将同义词引入令牌流。

①:去除单词a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, , or, such, that, the, their, then, there, these, they, this, to, was,willwith

  • 字符过滤器:分析器可能有零个或多个 字符过滤器,它们按顺序应用。字符过滤器将原始文本作为字符流接收,并可以通过添加、删除或更改字符来转换流。例如,字符过滤器可用于将印度-阿拉伯数字 (٠١٢٣٤٥٦٧٨٩) 转换为等价数字 (0123456789),或从字符流中去除HTML元素。

以上述例子为例测试ES的分析器。text field:"A quick brown fox jumps over the lazy dog",query string:"Quick fox jumps"。分析器首先会对文本做分词

TokenQuery stringtext field
A
quick
brown
fox
jumps
over
the
lazy
dog

可以看到如果对目标结果和搜索词使用分词器后只做了简单的分词,针对搜索词Quick没有做到很好的匹配,如果请求词fox->foxes,jumps->leap,这样只做分词的话是没有办法进行任何匹配的。这时我们就可以使用令牌过滤器,这里我们lowercase过滤器将所有令牌转换为小写,选用Stemmer令牌过滤器(提词干)和Stop令牌过滤器(停词)来对文本做过滤

TokenQuery stringtext field
quick
brown
fox
jump
over
lazi
dog

可以看到所有的关键词都进行了匹配,大大增加了搜索词和目标结果的匹配程度,并且通过stop停词器对"a","the"单词进行了过滤,减少了无意义词项的匹配效果影响。针对上述"A quick brown fox jumps over the lazy dog"实例我们可以直接看一下分析器分析结果

GET /_analyze
{
  "tokenizer": {        // 分词器
    "type": "standard"
  },
  "filter": [           // 令牌过滤器
    "lowercase",
    "stemmer",
    "stop"
  ],
  "char_filter": [],    // 字符过滤器
  "text": "A quick brown fox jumps over the lazy dog"
}

// 输出
{
  "tokens" : [
    {
      "token" : "quick",     // 命中字符
      "start_offset" : 2,    // 起始位置
      "end_offset" : 7,      // 结束位置
      "type" : "<ALPHANUM>", // 字符类型。eg-ALPHANUM:字母;HANGUL:韩文;NUM:数字。可以通过filter对这些类型进行过滤
      "position" : 1         // 分割位置
    },
    {
      "token" : "brown",
      "start_offset" : 8,
      "end_offset" : 13,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "fox",
      "start_offset" : 14,
      "end_offset" : 17,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "jump",
      "start_offset" : 18,
      "end_offset" : 23,
      "type" : "<ALPHANUM>",
      "position" : 4
    },
    {
      "token" : "over",
      "start_offset" : 24,
      "end_offset" : 28,
      "type" : "<ALPHANUM>",
      "position" : 5
    },
    {
      "token" : "lazi",
      "start_offset" : 33,
      "end_offset" : 37,
      "type" : "<ALPHANUM>",
      "position" : 7
    },
    {
      "token" : "dog",
      "start_offset" : 38,
      "end_offset" : 41,
      "type" : "<ALPHANUM>",
      "position" : 8
    }
  ]
}

注:一般情况下,目标结果和搜索内容需要用相同的分析器(除非有特殊的使用场景例子

3.2 自定义分析器

ES自带8种内置分析器可以直接进行使用内置分析器,当然如果对这些分析器的都不是很满意,那么我们可以使用ES内置的分词器、令牌过滤器、字符过滤器自己组装一个分析器来使用。官方文档已经给了很好的示例,这里就不照搬了创建一个自定义分析器

基本使用ES已经不成问题,接下来系列学习会介绍ES的数据流,ES是怎样输入输出、怎样存储?在存储的过程中怎样处理写入冲突的问题?ES的分布式又是怎么实现的呢?

参考文档

官方文档:What is Elasticsearch?

【Elasticsearch7.0】为什么不在使用映射type - 此木

Elasticsearch之Mapping设置详解_程大帅气的博客-CSDN博客_elasticsearch 设置mapping

Elasticsearch(二)--Es 数据存储细节(动态映射、静态映射、类型推断)、核心类型、二十三种映射参数、官方文档地址_sp_snowflake的博客-CSDN博客_es存储数据结构

理解Elasticsearch分析器 analyzer - 掘金