【你真的会用ES吗】ES基础介绍(一)

前言

ES的功能真的非常强大,其背后有很多默认值,或者默认操作。这些默认操作优劣并存,优势在于我们可以迅速上手使用ES,劣势在于,其实这些默认值的背后涉及到很多底层原理,怎么做更合适,只有数据使用者知道。用ES的话来说,你比ES更懂你的数据,一些配置信息,限制信息,还是需要在了解了ES的功能之后进行人工限制。

你是否遇到:在使用了一段时间ES之后,你期望使用ES的其他功能,但因为字段类型而受限的问题?所有针对字段类型的修改都需要reindex。

在遇到一些问题后,我发现用ES很简单,但是会用ES很难。这让我下定决心一定好好了解ES,也就出现了本系列文章

前期会对ES的基本概念,常用功能的底层原理进行介绍,在对ES有了一定的了解之后,再来讨论ES到底适合应用在什么场景之下。

题主的理解若有偏差,欢迎指正?

ES(全称 Elastic Search)是一款开源的、近实时、高性能的分布式搜索引擎, 在DBRaking热门测评中,在搜索引擎类,ES在近3年的统计数据中都霸居榜首,可见的其深受大家的喜爱。

image.png

随着ES的功能越来越强大,其和数据库的边界也越来越小,除了进行全文检索,ES也支持聚合/排序。ES底层基于Lucene开发,针对Lucene的局限性,ES提供了RESTful API风格的接口、支持分布式、可水平扩展,同时它可以被多种编程语言调用。

ES很多基础概念以及底层实现其本质是Lucene的概念。

ps:本文所有的dsl查询、结果展示均基于ES v7.7

历史背景

Lucene的历史背景

下图这个人叫Doug Cutting,他是Hadoop语言和Lucene工具包的创始人。Doug Cutting毕业于斯坦福大学,在Xerox积累了一定的工作经验后,从1997年开始,利用业余时间开发出了Lucene。Lucene面世于1999年,并于2005年成为Apache顶级开源项目。

image.png

Lucene的特点:

  • Lucene是基于java编写的,开源的全文检索引擎工具包
  • Lucene具有高性能:在相同的硬件环境下,基于 Hadoop 的 webmap(Lucene的第一个应用) 的反应速度是之前系统的 33 倍

Lucene的局限性:

  • 仅限于java开发。
  • 类库的接口学习成本高:本质上Lucene就是一个编程库,可以按原始接口来调用,但是如果在应用程序中直接使用Lucene,需要覆盖大量的集成框架工作。
  • 原生并不支持水平扩展,若需实现海量数据的搜索引擎,需在此基础上格外开发以支持分布式。

ES的历史背景

image.png

Shay Banon是ElasticSearch的创始人。2004年,Shay Banon基于Lucene开发了Compass,在考虑Compass的第三个版本时,他意识到有必要重写Compass的大部分内容,以“创建一个可扩展的搜索解决方案”。因此,他创建了“一个从头构建的分布式解决方案”,并使用了一个公共接口,即HTTP上的JSON,它也适用于Java以外的编程语言。

2010年,Shay Banon在发布了Elasticsearch的第一个版本。

在使用ES之前,一定要先了解ES的版本历史,这里只是列出来,可以在了解了基本概念之后再看。

ES多个版本可能出现破坏性变更,例如,在6.x,ES不允许一个Index中出现多个Type。在ES的官网,每个版本都对应着一个使用文档,下面列出一些比较重大的更新版本:

  • 初始版本 0.7.0 2010年5月14日
    • Zen Discovery 自动发现模块
    • Groovy Client支持
    • 简单的插件管理机制
    • 更好支持ICU分词器
  • 1.0.0 2014年2月14日
    • 支持聚合分析Aggregations
    • CAT API 支持
    • 支持联盟查询
    • 断路器支持
    • Doc values 引入
  • 2.0.0 2015年10月28日
    - 增加了 pipleline Aggregations
    - query/filter 查询合并,都合并到query中,根据不同的context执行不同的查询
    在 ES 中,有 Query 和 Filter 两种 Context
    - Query Context :相关性算分
    - Filter Context :不需要算分(YES OR NO), 可以利用 Cache 获得更好的性能
    - 存储压缩可配置
    - Rivers 模块被移除
    - Multicast 组播发现成为组件
  • 5.0.0 2016年10月26日
    • Lucene 6.x 的支持,磁盘空间少一半;索引时间少一半;查询性能提升25%;支持IPV6。
    • Internal engine级别移除了用于避免同一文档并发更新的竞争锁,带来15%-20%的性能提升
    • Shrink API ,它可将分片数进行收缩成它的因数,如之前你是15个分片,你可以收缩成5个或者3个又或者1个,那么我们就可以想象成这样一种场景,在写入压力非常大的收集阶段,设置足够多的索引,充分利用shard的并行写能力,索引写完之后收缩成更少的shard,提高查询性能
    • 提供了 Painless 脚本,代替Groovy脚本
    • 新增 Sliced Scroll类型,现在Scroll接口可以并发来进行数据遍历了。每个Scroll请求,可以分成多个Slice请求,可以理解为切片,各Slice独立并行,利用Scroll重建或者遍历要快很多倍。
    • 引入新的字段类型 Text/Keyword 来替换 String
    • 限制索引请求大小,避免大量并发请求压垮 ES
    • 限制单个请求的 shards 数量,默认 1000 个
  • 6.0.0 2017年8月31日
    • Index sorting,即索引阶段的排序。
    • 顺序号的支持,每个 es 的操作都有一个顺序编号(类似增量设计)
    • 无缝滚动升级
    • 逐步废弃type,在 6.0 里面,开始不支持一个 index 里面存在多个 type
    • Index-template inheritance,索引版本的继承,目前索引模板是所有匹配的都会合并,这样会造成索引模板有一些冲突问题, 6.0 将会只匹配一个,索引创建时也会进行验证
    • Load aware shard routing, 基于负载的请求路由,目前的搜索请求是全节点轮询,那么性能最慢的节点往往会造成整体的延迟增加,新的实现方式将基于队列的耗费时间自动调节队列长度,负载高的节点的队列长度将减少,让其他节点分摊更多的压力,搜索和索引都将基于这种机制。
    • 已经关闭的索引将也支持 replica 的自动处理,确保数据可靠。
  • 7.0.0 2019年4月10日
    • 集群连接变化:TransportClient被废弃 以至于,es7的java代码,只能使用restclient
    • 重大改进-正式废除单个索引下多Type的支持
      • es6时,官方就提到了es7会删除type,并且es6时已经规定每一个index只能有一个type。在es7中使用默认的_doc作为type,官方说在8.x版本会彻底移除type。 api请求方式也发送变化,如获得某索引的某ID的文档:GET index/_doc/id其中index和id为具体的值
  • Lucene9.0
  • 引入了真正的内存断路器,它可以更精准地检测出无法处理的请求,并防止它们使单个节点不稳定
  • Zen2 是 Elasticsearch 的全新集群协调层,提高了可靠性、性能和用户体验,变得更快、更安全,并更易于使用
  • 性能优化
    - Weak-AND算法提高查询性能
    - 默认的Primary Shared数从5改为1,避免Over Sharding
    - shard也是一种资源,shard过多会影响集群的稳定性。因为shard过多,元信息会变多,这些元信息会占用堆内存。shard过多也会影响读写性能,因为每个读写请求都需要一个线程。
    所以如果index没有很大的数据量,不需要设置很多shard。
    - 更快的前 k 个查询
  • 间隔查询(Intervals queries) 某些搜索用例(例如,法律和专利搜索)引入了查找单词或短语彼此相距一定距离的记录的需要。 Elasticsearch 7.0中的间隔查询引入了一种构建此类查询的全新方式,与之前的方法(跨度查询span queries)相比,使用和定义更加简单。 与跨度查询相比,间隔查询对边缘情况的适应性更强。

基础概念介绍

在开始介绍之前想碎碎念几句~ES的功能真的非常强大,本文仅讲述了题主接触过的部分;ES背后有很多默认值,或者默认操作,用ES的话来说,你比ES更懂你的数据,一些配置信息,限制信息,还是需要在了解了ES的功能之后进行人工限制。

index

Index翻译过来是索引的意思。在ES里,索引有两个含义:

  • 名词:一个索引相当于关系型数据库中的一个表(在6.x以前,一个index可以被认为是一个数据库,可以承载相同scheme的数据。
  • 动词:将一份document保存在一个index里,这个过程也可以称为索引。

type

在6.x之前,index可以被理解为关系型数据库中的【数据库】,而type则可以被认为是【数据库中的表】,ES在8.x完全废弃了**type**。

使用type允许我们在一个index里存储多种类型的数据,数据筛选时可以指定typetype的存在从某种程度上可以减少index的数量,但是type存在以下限制:

  • 不同type里的字段需要保持一致。例如,一个index下的不同type里有两个名字相同的字段,他们的类型(string, date 等等)和配置也必须相同。
  • 只在某个type里存在的字段,在其他没有该字段的 type 中也会消耗资源。
  • 得分是由index内的统计数据来决定的。也就是说,一个 type 中的文档会影响另一个 type 中的文档的得分。

以上限制要求我们,只有同一个index的中的 type 都有类似的映射 (mapping) 时,才勉强适用 type。否则,使用多个type可能比使用多个index消耗的资源更多。

这大概也是为什么ES决定废弃type这个概念,个人感觉type的存在,就像是一个语法糖,但是并未带来太大的收益,反而增加了复杂度。

document

index中的单条记录称为document(文档),可以理解为表中的一行数据(本质差别在于document的存储方式上,它并不是行存储)。

多条document组成了一个index

"hits" : {
    "total" : {
      "value" : 140,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "cards_info_test",
        "_type" : "_doc",
        "_id" : "1092",
        "_score" : 1.0,
        "_source" : {
          "title" : "秦时明月世界黑曜卡5000元"
           ...
        }
      }
     ]
	}

上图为ES一条文档数据,其中:

_index: 文档所属索引名称。

_type :文档所属类型名(此处已默认为_doc)。

_id :Doc的主键。在写入的时候,可以指定该Doc的ID值,如果不指定,则系统自动生成一个唯一的UUID值。

_score:顾名思义,得分,也可称之为相关性,在查询是ES会 根据一些规则计算得分,并根据得分进行倒排。除此之外,ES支持通过Function score query在查询时自定义score的计算规则。

_source: 文档的原始JSON数据。

filed

一个document会由一个或多个filed组成,filed是ES中数据索引的最小定义单位,下面仅列举部分常用的类型。

在ES中,没有数组类型,任何字段都可以变成数组。

string

text

  • 索引全文值的字段,例如电子邮件正文产品描述
  • 如果您需要索引结构化内容,例如电子邮件地址、主机名、状态代码或标签,您可能应该使用keyword字段。
  • 出于不同目的,我们期望以不同方式索引同一字段,这就是 multi-fields

例如,可以将string字段映射为用于全文搜索的text字段,并映射为用于排序或聚合的keyword字段:

PUT my_index
{
  "mappings": {
    "properties": {
      "city": {
        "type": "text",
        "fields": {
          "raw": { 
            "type":  "keyword"
          }
        }
      }
    }
  }
}
  • ⚠️纯**text**字段默认无法进行排序或聚合
  • ⚠️只用test字段一定要使用合理的分词器,scr上的ES已经默认只是了ik中文分词。

keyword

  • 用于索引结构化内容的字段,例如 ID、电子邮件地址、主机名、状态代码、邮政编码或标签。

如果您需要索引全文内容,例如电子邮件正文或产品描述,你应该使用text字段。

  • 它们通常用于过滤(查找所有发布状态的博客文章)、排序和聚合keyword字段只能精确匹配
  • 如果你能预知你的数据一定不会进行排序和聚合,那么在创建映射关系时,可以指定"doc_values": false

doc_values是为了实现排序和聚合引入的,列式存储的数据格式,指定为false可以一定程度上节约空间。

Numeric

long, integer, short, byte, double, float, half_float, scaled_float...
  • 就整数类型(byteshortintegerlong)而言,应该选择足以满足用例的最小类型。
  • 对于浮点类型,使用缩放因子将浮点数据存储到整数中通常更有效,这就是 scaled_float 类型的实现。
  • 下面这个case,scaling_factor缩放因子设置为100,对于所有的API来说,price看起来都像是一个双精度浮点数。但是对于ES内部,他其实是一个整数long
"price": {
        "type": "scaled_float",
        "scaling_factor": 100
      }
  • 如果scaled_float无法满足精度要求,可以使用doublefloathalf_float

Type

Minimum value

Maximum value

Significant bits / digits

double

2-1074

(2-2-52)·21023

53 / 15.95

float

2-149

(2-2-23)·2127

24 / 7.22

half_float

2-24

65504

11 / 3.31

  • 不是所有的字段都适合存储为numbericnumberic********类型更擅长********range**************类查询**,精确查询可以尝试使用keyword

mapping

mapping是一个定义document结构的过程,mapping中定义了一个文档所包含的所有filed信息。

定义字段索引过多会导致爆炸的映射,这可能会导致内存不足错误和难以恢复的情况,mapping提供了一些配置对filed进行限制,下面列举几个可能会比较常见的:

  • index.mapping.total_fields.limit

限制mapping中filed的最大数量,默认值是1000(filed和object内的所有字段,都会加入计数)。

  • index.mapping.depth.limit

限制mapping中object的最大深度,默认值是20。

  • index.mapping.field_name_length.limit

限制mapping中字段名的长度,默认是没有限制。

dynamic mapping

在索引 document时,ES的动态mapping会将新增内容中不存在的字段,自动的加入到映射关系中。ES会自动检测新增字段的逻辑,并赋予其默认值。

  • One of the most important features of Elasticsearch is that it tries to get out of your way and let you start exploring your data as quickly as possible.
  • You know more about your data than Elasticsearch can guess, so while dynamic mapping can be useful to get started, at some point you will want to specify your own explicit mappings.

截取了部分ES官方文档中的话术,ES认为一些自动化的操作会让新手上手更容易。但是同时,又提出,你肯定比ES更了解你的数据,可能刚开始使用起来觉得比较方便,但是最好还是自己明确定义映射关系。(?️个人认为,这些自动操作是在用户对ES没有太多了解的情况下进行的,如果刚开始依赖了这些默认的操作,例如:新增字段使用了ES赋予的默认值,如果后续有分析、排序、聚合等操作可能会有一定限制)

⚠️在ES中,删除/变更filed定义,需要进行reindex,所以在构建mapping结构时记得评估好字段的用途,以使用最合适的字段类型。

部分查询关键字介绍

match&&match_phrase

  • match:用于执行全文查询的标准查询,包括模糊匹配和短语或接近查询。

重要参数:控制Token之间的布尔关系:operator:or/and

  • match_phrase:与match查询类似

但用于匹配确切的短语或单词接近匹配。重要参数:Token之间的位置距离:slop 参数,默认为0

GET /_analyze
{
  "text": ["这是测试"],
  "analyzer": "ik_smart"
}
//Result
{
  "tokens" : [
    {
      "token" : "这是",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "测试",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    }
  ]
}


//match+analyzer:ik_smart
//可以查询到所有describe中包含【这是测试】、【这是】、【测试】的doc
GET /my_index/_search
{
  "query": {
    "match": {
      "describe":{
      "query": "这是测试",
      "analyzer": "ik_smart"
      }
    }
  }
}



//match_phrase + analyzer:ik_smart + slop=0(默认)
//可以查询到所有describe中包含【这是】+【测试】token间隔为0的doc(说人话就是:模糊匹配【这是测试】)
GET /my_index/_search
{
  "_source": "describe", 
  "query": {
    "match_phrase": {
      "describe": "这是测试"
    }
  }
}

//match_phrase + analyzer:ik_smart + slop=1
//可以查询到所有describe中包含【这是】+【测试】token间隔为1的doc
//例如某个doc中describe为【这是一个测试】,【这是一个测试】分词后的token分别为【这是】【一个】【测试】
//【这是】和【测试】之间间隔了1个token【一个】,所以可以被查询到;同理【这是一个我的测试】查询不到
GET /my_index/_search
{
  "query": {
    "match_phrase": {
      "describe":{
        "query": "这是测试",
        "analyzer": "ik_smart",
        "slop": 1
      }
    }
  }
}

term

term是进行精确查找的关键;在Lucene中,term是中索引和搜索的最小单位。一个filed会由一个或多个term组成,term是由filed经过Analyzer(分词)产生。Term Dictionaryterm词典,是根据条件查找term的基本索引。

  • 避免对text字段使用术语查询。 默认情况下,ES 会在分析过程中更改文本字段的值。

这会使查找text字段值的精确匹配变得困难。 要搜索text字段值,强烈建议改用match查询。

  • ⚠️默认分词情况下,无论是term还是match,都无法判断string类型字段是否为空字符串

以上两点均是因为text字段存储的是分词结果,如果字段值为空,分词结果将不会存储term信息,keyword字段存储的是原始内容。

GET /my_index/_termvectors/1657216298432499712?fields=content
{
  "_index" : "my_index",
  "_type" : "_doc",
  "_id" : "1657216298432499712",
  "_version" : 2,
  "found" : true,
  "took" : 0,
  "term_vectors" : { }
}

GET /my_index/_termvectors/1672819694014398464?fields=card_pic
{
  "_index" : "my_index",
  "_type" : "_doc",
  "_id" : "1672819694014398464",
  "_version" : 1,
  "found" : true,
  "took" : 0,
  "term_vectors" : {
    "card_pic" : {
      "field_statistics" : {
        "sum_doc_freq" : 183252,
        "doc_count" : 183252,
        "sum_ttf" : 183252
      },
      "terms" : {
        "" : {
          "term_freq" : 1,
          "tokens" : [
            {
              "position" : 0,
              "start_offset" : 0,
              "end_offset" : 0
            }
          ]
        }
      }
    }
  }
}

参考

https://www.jianshu.com/p/1a737a3dde86

https://www.modb.pro/db/130339

https://www.cnblogs.com/qdhxhz/p/11448451.html

https://blog.csdn.net/tengxing007/article/details/100663530

https://www.elastic.co/guide/en/elasticsearch/reference/7.7/index.html

https://zhuanlan.zhihu.com/p/35469104

https://zhuanlan.zhihu.com/p/142641300

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
【你真的会用ES吗】ES基础介绍(一)
ES的功能真的非常强大,其背后有很多默认值,或者默认操作。这些默认操作优劣并存,优势在于我们可以迅速上手使用ES,劣势在于,其实这些默认值的背后涉及到很多底层原...
<<上一篇
下一篇>>