es全称Elasticsearch是一个基于Lucene的高扩展的分布式搜索服务器,隐藏了Lucene的复杂性,对外提供Restful 接口来操作索引、搜索,支持开箱即用。支持分布式,扩展性好,可部署上百台服务器集群,可以实现近实时的获取索引数据、搜索数据。
一、怎样使用
不以满足实际应用需求为出发点的理论基础都是耍流氓,话不多说,先看看使用姿势。Mysql关系型数据库大家使用的比较多,这里以Mysql为例子进行对比方便大家理解
| Mysql和Es类比 | |
|---|---|
| Mysql | Elastic Search |
| 数据库(Database) | 索引(Index) |
| 表结构(Schema) | 映射(Mapping) |
| 行(Row) | 文档(Document) |
| 列(Column) | 字段(Field) |
| Sql | DSL |
平台: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的映射也是非常灵活的,分为静态映射、动态映射
-
静态映射
-
动态映射
- 动态映射配置
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"。分析器首先会对文本做分词
| Token | Query string | text field |
|---|---|---|
| A | ✅ | |
| quick | ✅ | |
| brown | ✅ | |
| fox | ✅ | ✅ |
| jumps | ✅ | ✅ |
| over | ✅ | |
| the | ✅ | |
| lazy | ✅ | |
| dog | ✅ |
可以看到如果对目标结果和搜索词使用分词器后只做了简单的分词,针对搜索词Quick没有做到很好的匹配,如果请求词fox->foxes,jumps->leap,这样只做分词的话是没有办法进行任何匹配的。这时我们就可以使用令牌过滤器,这里我们lowercase过滤器将所有令牌转换为小写,选用Stemmer令牌过滤器(提词干)和Stop令牌过滤器(停词)来对文本做过滤
| Token | Query string | text 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的分布式又是怎么实现的呢?
参考文档
【Elasticsearch7.0】为什么不在使用映射type - 此木
Elasticsearch之Mapping设置详解_程大帅气的博客-CSDN博客_elasticsearch 设置mapping
Elasticsearch(二)--Es 数据存储细节(动态映射、静态映射、类型推断)、核心类型、二十三种映射参数、官方文档地址_sp_snowflake的博客-CSDN博客_es存储数据结构