【ElasticSearch从入门到放弃系列 零】ElasticSearch看这一篇就够了(三)

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【ElasticSearch从入门到放弃系列 零】ElasticSearch看这一篇就够了(三)

搜索操作流程

其实我们在ES的大多数使用场景都是检索,那么检索的原理是什么呢?这里举个例子,例如我有三条数据,分别叫:E-Node1_S3tml超级帅、E-Node1_S2tml很丑、E-Node2_S1tml很丑、E-Node3_S0tml其实挺帅的。前缀表明他们存储的主分片。我们搜索【帅】这个关键字:

  1. 客户端发送请求到E-Node2节点,该节点成为协调节点
  2. 协调节点将请求转发到所有分片上【主分片或副本分片,采取随机轮询的方式】,假设这里都是主分片处理请求,那么请求被转发到了E-Node1、E-Node1和E-Node3两个节点
  3. query phase:每个分片将自己的搜索结果(其实就是一些doc id),返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果,这里【帅】命中了两条数据:E-Node1的S3分片上【E-Node1_S3tml超级帅】,E-Node3的S0分片上【E-Node3_S0tml其实挺帅的】,这两条数据的doc id被返回给协调节点E-Node2
  4. fetch phase:协调节点E-Node2根据doc id去各个节点上拉取实际的document数据,最终返回给客户端

这里需要注意,第一次只检索数据id返回给协调节点,并不是真正的取数据,整合之后再取数据。

删除操作流程

删除其实是假删除,先进行标记删除,然后在段合并(这个概念后文提到)的时候再进行彻底删除,删除时给定了文档id,这样按照我们的流程,要删除文档【E-Node1_S3tml超级帅】

  1. 客户端发送请求到E-Node2节点,该节点成为协调节点
  2. 协调节点对document id进行路由,路由规则同上,将请求转发到路由主分片E-Node1_S3的节点E-Node1,此时不直接删除文档,而是把文档id标记到.del的删除文件
  3. 将删除请求路由到所有副本节点【E-Node2_R3、E-Node3_R3】执行同样的操作
  4. 协调节点向客户端报告成功

下面会讲到,只有在进行段合并的时候才会真正的删除文件,其它时候只是检索到后将结果集过滤了一遍.del文件。

更新操作流程

更新其实是一次删除加一次写入,已标记删除的V1版本,在检索时可能会被检索到,然后在结果集里被过滤掉,所以.del文件还需要记录文档的版本,这样按照我们的流程,要更新文档【E-Node1_S3tml超级帅】

  1. 客户端发送请求到E-Node2节点,该节点成为协调节点
  2. 协调节点对document id进行路由,路由规则同上,将请求转发到路由主分片E-Node1_S3的节点E-Node1,此时不直接删除文档,而是把文档id标记到.del的删除文件,然后索引一个文档的新版本
  3. 将更新请求路由到所有副本节点对分片【E-Node2_R3、E-Node3_R3】执行同样的操作
  4. 协调节点向客户端报告成功

那么在具体的更新流程中,版本合并如何处理呢?在下边的更新版本合并策略中介绍到。

ElasticSearch存储原理及策略

上面介绍了在ES内部索引的CRSUD处理流程,这个流程是在ES的内存中执行的,数据被分配到特定的分片和副本上之后,最终持久化到磁盘上,这样断电的时候就不会丢失数据。具体的存储路径可在配置文件../config/elasticsearch.yml中进行设置,默认存储在安装目录的data文件夹下。建议不要使用默认值,因为若ES进行了升级,则可能导致数据全部丢失

path.data: /path/to/data  //索引数据
path.logs: /path/to/logs  //日志记录

分段存储

索引文档以段的形式存储在磁盘上,索引文件被拆分为多个子文件,则每个子文件叫作段【其实和Kafka将Partion切分为段类似】, 每一个段本身都是一个倒排索引,并且段具有不变性,一旦索引的数据被写入硬盘,就不可再修改。在底层采用了分段的存储模式,使它在读写时几乎完全避免了锁的出现,大大提升了读写性能。

段在不同存储模式下拥有不同的读写能力,可以避免使用锁的开销,提升读写性能

  1. 当索引在内存中时,就只有写的权限,而不具备读数据的权限,意味着不能被检索【只能写不能读】
  2. 当索引被刷到文件缓存系统里成为段之后才能被检索【只能读不能写】
  3. 段被写入到磁盘后会生成一个提交点(checkpoint),提交点是一个用来记录所有提交后段信息的文件。一个段一旦拥有了提交点,就说明这个段的数据在断电时也不会丢失了

为什么要有段呢?早期全文检索为整个文档集合建立了一个很大的倒排索引,并将其写入磁盘中。如果索引有更新,就需要重新全量创建一个索引来替换原来的索引。这种方式在数据量很大时效率很低,并且由于创建一次索引的成本很高,所以对数据的更新不能过于频繁,也就不能保证时效性,索引文件分段存储并且不可修改,那么新增、更新和删除如何处理呢?

  • 新增,新增很好处理,由于数据是新的,所以只需要对当前文档新增一个段就可以了。
  • 删除,由于不可修改,所以对于删除操作,不会把文档从旧的段中移除而是通过新增一个.del文件,文件中会列出这些被删除文档的段信息。这个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除
  • 更新,不能修改旧的段来进行反映文档的更新,其实更新相当于是删除和新增这两个动作组成。会将旧的文档在.del文件中标记删除,然后文档的新版本被索引到一个新的段中。可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就会被移除

段被设定为不可修改具有一定的优势也有一定的缺点。

段不变性的优势

段不变性的优势主要体现在:

  1. 不需要锁。如果不更新索引,就不需要担心多进程同时修改数据的问题,也就是单纯读写时不会有锁的问题
  2. 一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求文件系统缓存【内存】,而不会命中磁盘。这提供了很大的性能提升。
  3. filter缓存在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
  4. 写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和 需要被缓存到内存的索引的使用量。

总结而言就是不需要考虑读写的并发,以及高性能的读数据和压缩数据能力。

段不变性的缺点

段的不变性的缺点如下:

  1. 新增时:每次新增数据时都需要新增一个段来存储数据。当段的数量太多时,对服务器的资源例如文件句柄的消耗会非常大。
  2. 删除时:当对旧数据进行删除时,旧数据不会马上被删除,而是在.del文件中被标记为删除。而旧数据只能等到段更新时才能被移除,这样会造成大量的空间浪费。
  3. 更新时:若有一条数据频繁的更新,每次更新都是新增新的标记旧的,则会有大量的空间浪费。
  4. 查询时:在查询的结果中包含所有的结果集,需要排除被标记删除的旧数据,这增加了查询的负担

我们说没有任何一种结构或算法是万能的,空间和时间一定有其平衡性,段其实就是牺牲了空间成就了时间。

索引操作策略

在介绍了索引的CRSUD以及ES的存储结构后我们来看看在CRSUD过程中,结合存储,有什么策略让索引的使用更高效呢?

延迟写策略

如果直接写入磁盘,磁盘的I/O消耗上会严重影响性能,写数据量大的时候会造成ES停顿卡死,查询也无法做到快速响应。如果真是这样ES也就不会称之为近实时全文搜索引擎了,为了提升写的性能,ES并没有每新增一条数据就增加一个段到磁盘上,而是采用延迟写的策略:

  1. 写入内存:每当有新增的数据时,就将其先写入到内存中,在内存和磁盘之间是文件系统缓存。这里的内存使用的是ES的JVM内存,新的数据会继续的被写入内存,但内存中的数据并不是以段的形式存储的,因此不能提供检索功能
  2. 写入文件系统缓存:当达到默认的时间(1秒钟)或者内存的数据达到一定量时,会触发一次刷新(Refresh),将内存中的数据生成到一个新的段上并缓存到文件缓存系统 上,文件缓存系统使用的是操作系统的内存,由内存刷新到文件缓存系统的时候会生成了新的段,并将段打开以供搜索使用,而不需要等到被刷新到磁盘
  3. 写入磁盘:稍后再被刷新到磁盘中并生成提交点。

在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh (即内存刷新到文件缓存系统)。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是近实时搜索,因为文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。我们也可以手动触发 refresh,POST /_refresh 刷新所有索引,POST /nba/_refresh刷新指定的索引。

虽然通过延时写的策略可以减少数据往磁盘上写的次数提升了整体的写入能力,但是我们知道文件缓存系统也是内存空间,属于操作系统的内存,只要是内存都存在断电或异常情况下丢失数据的危险。为了避免丢失数据,Elasticsearch添加了事务日志(Translog),事务日志记录了所有还没有持久化到磁盘的数据。添加了事务日志后整个写索引的流程如下图所示:

添加了事务日志后的优化流程如下:

  1. 写入内存及事务日志,先将数据写入buffer【ES的内存】,在buffer里的时候数据是搜索不到的;同时将数据写入translog日志文件,这时新数据还不能被检索和查询。需要注意的是:当Elasticsearch作为NoSQL数据库时,查询方式是GetById,这种查询可以直接从TransLog中查询,这时候就成了RT(Real Time)实时系统
  2. 写入文件系统缓存(os cache), 如果buffer快满了,或者到一定时间(每隔1s),就会将buffer数据refresh到一个新的segment file中(此时数据不是直接进入segment file的磁盘文件的,而是先进入os cache)并且将buffer清空。只要数据进入os cache,此时就可以让这个segment file的数据对外提供搜索了,为什么叫es是准实时的?NRT,near real-time,准实时。默认是每隔1秒refresh一次的,所以es是准实时的,因为写入的数据1秒之后才能被看到。
  3. 重复1~2步骤,新的数据不断进入buffer和translog,不断将buffer数据写入一个又一个新的segment file中去,每次refresh完buffer清空,translog保留。随着这个过程推进,translog会变得越来越大。当translog达到一定长度的时候,就会触发commit操作。
  4. 写入磁盘并删除事务日志,当日志数据大小超过512M或者时间超过30分钟时,会触发一次 flush,也就是commit操作分为如下几步:
  1. 就是将buffer中现有数据refresh到os cache中去,清空buffer
  2. 将一个commit point写入磁盘文件,里面标识着这个commit point对应的所有segment file
  3. 强行将os cache中目前所有的数据都fsync到磁盘文件中去
  4. 清空translog日志文件然后再次重启启用一个translog

也可以通过es api,手动执行flush操作,手动将os cache中的数据fsync强刷到磁盘上去,记录一个commit point,清空translog日志文件。

通过这种方式当断电或需要重启时,ES不仅要根据提交点去加载已经持久化过的段,还需要查看Translog里的记录,把未持久化的数据重新持久化到磁盘上,避免了数据丢失的可能。到这里,其实ES1秒延迟的问题有两个不成熟的解决方案

  1. 可以通过es的restful api或者java api,在写入内存后,手动执行一次refresh操作,就是手动将buffer中的数据刷入os cache中,让数据立马就可以被搜索到
  2. 查询方式是GetById时,直接从TransLog中查询

当然这肯定会牺牲性能,所以并不是完备的解决方案,但其实是解决问题的入口,值得一提的是,translog其实也是先写入os cache的,默认每隔5秒刷一次到磁盘中去,因为日志每隔5秒从文件缓存系统flush一次到磁盘,所以最多会丢5秒的数据。

段合并策略

由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段然后合并查询结果,所以段越多,搜索也就越慢。

Elasticsearch通过在后台定期进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段

  • 段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档不会被拷贝到新的大段中
  • 合并的过程中不会中断索引和搜索

这可以解决段不可变带来的空间占用问题

段合并时机和步骤:

  1. 段合并在进行索引和搜索时会自动进行,合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中
  2. 待合并的段既可以是未提交的也可以是已提交【文件操作系统缓存里的或磁盘里的】的。合并结束后老的段会被删除,新的段被 flush 到磁盘,同时写入一个包含新段(已排除旧的被合并的段)的新提交点
  3. 新的段被打开可以用来搜索

段合并的计算量庞大, 而且还要吃掉大量磁盘 I/O,段合并会拖累写入速率,如果任其发展会影响搜索性能。Elasticsearch在默认情况下会对合并流程进行资源限制,所以搜索仍然有足够的资源很好地执行。整体执行流程如下图所示:

ElasticSearch检索

ES最核心的内容就是检索了,重头戏往往放到后边,在了解了集群的工作原理和存储机制后,正式了解ElasticSearch检索操作。

分词器

中文在stander中的切分是按照单字的,例如服务器三个字在标准分词器下会被分为服、务、器,所以我们在term查询的时候检索服务的时候检索不到数据,因为没有服务这个分词,所以我们需要使用IK分词器,IK提供了两个分词算法ik_smart 和 ik_max_word,其中 ik_smart 为最少切分,ik_max_word为最细粒度划分。

  • 最小切分不切分到单字,这样querstring搜【服务】就不再能搜到了,因为querstring切分的关键词不包含,term搜【服务器】就可以搜到了。
  • 最大切分的情况下,有可能被单字切分,粒度更细一些。此时term搜【服务】就可以搜到了

其实不光有ik、stander两种分词器,分词器种类有很多,核心是搜索的内容是如何被切分的

查询语句及关键字

按照查询条件的多少,从网上找了一些文章,看起来真的是不知所云,分类为所欲为,于是想对整个的ES查询方式做个整理,分为如下几个维度去讨论,防止大家读起来混乱。

查询大类 查询小类 细分关键词
普通查询 ID查询【通过ID精确匹配】 id
语法查询【QueryString查询】 query_string
DSL查询【 ES领域专用】 term、terms、much、much_all、match_phrase、muti_match、range、exists、prefix、wildcard、regexp、fuzzy
复合查询 Boolean复合查询 must、should、must_not、filter【四种子句关系类型】
聚合查询 桶聚合 terms、range
聚合查询 指标聚合 min、max、avg、sum、cardinalit、stats、percentile、 percentile_rank、top hits

以上所有的查询方式,都支持查询结果的:排序、分页【fromsize+scroll】和高亮查询 这些基本特性,所以拜托各位大佬的文章不要把这些当做分类带进去,很容易误导人。详细的查询方式见blogElasticsearch常用查询方式讨论及实践,这里只简单记录部分关键字的使用方式:

普通查询

1, ID查询顾名思义就是通过ID进行查询,例如这里我们想查询ID为9的数据,通过如下的Get查询请求即可

GET:localhost:9200/tml-userinfo/_doc/9

2,语法查询相当于我的查询语句会进行分词

{
    "query": {
        "query_string": {
            "default_field": "name",  //要查询的字段
            "query": "林玲"
        }
    }
}

3,term查询用来查询某个关键字在文档里是否存在,所以Term需要是文档切分的一个关键字

{
    "query": {
        "term": {
            "sex": "男"
        }
    }
}

4,terms查询用来查询某几个个关键字在文档里是否存在,Terms可以同时对一个字段检索多个关键字

{
    "query": {
        "terms": {
            "age": [18,28]
        }
    }
}

4, match查询和queryString有点类似,就是先对查询内容做分词,然后再去进行匹配

{
    "query": {
        "match": {
            "sex": "男女"
        }
    }
}

5,match_all的查询方式简单粗暴,就是匹配所有,不需要传递任何参数

{
    "query": {
        "match_all": {
        }
    }
}

6,match_phrase属于短语匹配,能保证分词间的邻近关系,相当于对文档的关键词进行重组以匹配查询内容,对于匹配了短语"森 小 林"的文档,下面的条件必须为true:

  • 森 、小、 林必须全部出现在某个字段中
  • 的位置必须比的位置大1
  • 的位置必须比的位置大2

我们来尝试下对姓名进行检索,请求头和上边完全一样,就不再赘述,直接看请求体,先来看一个不按顺序的

{
    "query": {
        "match_phrase": {
            "name": "森小林"
        }
    }
}


相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
6月前
|
安全 Linux 开发工具
Elasticsearch 搜索入门技术之一
Elasticsearch 搜索入门技术之一
257 1
|
6月前
|
JSON 自然语言处理 数据库
数据库-ElasticSearch入门(索引、文档、查询)
数据库-ElasticSearch入门(索引、文档、查询)
386 0
|
存储 关系型数据库 数据库
ElasticSearch深度解析入门篇:高效搜索解决方案的介绍与实战案例讲解,带你避坑
ElasticSearch深度解析入门篇:高效搜索解决方案的介绍与实战案例讲解,带你避坑
ElasticSearch深度解析入门篇:高效搜索解决方案的介绍与实战案例讲解,带你避坑
|
1月前
|
存储 Java API
Elasticsearch 7.8.0从入门到精通
这篇文章详细介绍了Elasticsearch 7.8.0的安装、核心概念(如正排索引和倒排索引)、RESTful风格、各种索引和文档操作、条件查询、聚合查询以及在Spring Boot中整合Elasticsearch的步骤和示例。
128 1
Elasticsearch 7.8.0从入门到精通
|
2月前
|
数据可视化 Java Windows
Elasticsearch入门-环境安装ES和Kibana以及ES-Head可视化插件和浏览器插件es-client
本文介绍了如何在Windows环境下安装Elasticsearch(ES)、Elasticsearch Head可视化插件和Kibana,以及如何配置ES的跨域问题,确保Kibana能够连接到ES集群,并提供了安装过程中可能遇到的问题及其解决方案。
Elasticsearch入门-环境安装ES和Kibana以及ES-Head可视化插件和浏览器插件es-client
|
2月前
|
存储 关系型数据库 MySQL
浅谈Elasticsearch的入门与实践
本文主要围绕ES核心特性:分布式存储特性和分析检索能力,介绍了概念、原理与实践案例,希望让读者快速理解ES的核心特性与应用场景。
|
5天前
|
存储 JSON Java
ELK 圣经:Elasticsearch、Logstash、Kibana 从入门到精通
ELK是一套强大的日志管理和分析工具,广泛应用于日志监控、故障排查、业务分析等场景。本文档将详细介绍ELK的各个组件及其配置方法,帮助读者从零开始掌握ELK的使用。
|
3月前
|
JSON 搜索推荐 数据挖掘
ElasticSearch的简单介绍与使用【入门篇】
这篇文章是Elasticsearch的入门介绍,涵盖了Elasticsearch的基本概念、特点、安装方法以及如何进行基本的数据操作,包括索引文档、查询、更新、删除和使用bulk API进行批量操作。
ElasticSearch的简单介绍与使用【入门篇】
|
2月前
|
JSON 监控 Java
Elasticsearch 入门:搭建高性能搜索集群
【9月更文第2天】Elasticsearch 是一个分布式的、RESTful 风格的搜索和分析引擎,基于 Apache Lucene 构建。它能够处理大量的数据,提供快速的搜索响应。本教程将指导你如何从零开始搭建一个基本的 Elasticsearch 集群,并演示如何进行简单的索引和查询操作。
217 3
|
3月前
|
JSON 测试技术 API
黑马商城 Elasticsearch从入门到部署 RestClient操作文档
这篇文章详细介绍了如何使用Java的RestHighLevelClient客户端与Elasticsearch进行文档操作,包括新增、查询、删除、修改文档以及批量导入文档的方法,并提供了相应的代码示例和操作步骤。
下一篇
无影云桌面