Elasticsearch

因工作原因接触到es,正好没怎么了解过,来玩一下

如果你的目标是珠穆朗玛,华山自然就不在话下

阅读前提醒

这篇文章里面主要记录了本人对elasticsearch这个app的理解,并没有涉及实际查询和配置操作,不具有实用性,如果你来这里是为了查询到一些能够很好使用elasticsearch的教程,或者一些对elasticsearch进行配置的信息,请前往elasticsearch官网获取帮助。

正文

elasticsearch(以下简称es)官方定义是一个搜索和分析引擎,那么搜索引擎的定义是什么?以下是度娘的答案:

搜索引擎是指根据一定的策略、运用特定的计算机程序从互联网上采集信息,在对信息进行组织和处理后,为用户提供检索服务,将检索的相关信息展示给用户的系统。它是根据用户需求与一定算法,运用特定策略从互联网检索出制定信息反馈给用户的一门检索技术。

那么可以理解为,es是一个根据一定算法和优化技术,对数据进行分析和检索的软件。当然这个理解可以说是过于广泛,接下来就进一步细化一下。

  • 主要概念

    其实概念还挺多的,所以我根据官方文档顺序一个个解释,大部分是官方文档的原话,按照自身理解增删一些内容,为避免歧义,有些地方我会附上英文原话

    es存储格式:文档(documents)

    Elasticsearch是面向文档的,文档是所有可搜索数据的最小单位,它不会将信息储存为列数据行,而是储存已序列化为 JSON 文档的复杂数据结构。当你在一个集群中有多个节点时,储存的文档分布在整个集群里面,并且立刻可以从任意节点去访问。
    

    关于文档:es文档的元数据

    img

    元数据,用于标注文档的相关信息 
    _index-文档所属的索引名 
    _type-文档所属的类型名 
    _id-文档唯一 ld 
    _source:文档的原始Json 数据 
    _all:整合所有字段内容到该字段,已被废除 
    _version:文档的版本信息 
    _score:相关性打分
    

    es对文档的优化:索引(index)

    An index can be thought of as an optimized collection of documents and each document is a collection of fields
    可以把索引理解为对文档优化后的集合,而文档是对字段的集合,Elasticsearch对每个字段中的所有数据建立索引,并且每个索引字段都具有专用的优化数据结构。比如文本字段存储在倒排索引中,数字字段和地理字段存储在BKD树中。使用按字段数据结构组合并返回搜索结果的方式使Elasticsearch如此之快。
    
    es中的索引是一个或多个物理分片的逻辑分组,每个物理分片是一个独立索引(an Elasticsearch index is really just a logical grouping of one or more physicashards, where each shard is actually a self-contained index. )
    

    es的扩展和弹性:集群(cluster),节点(node),分片(shards)

    这三个概念关联性比较大,所以我也仿照官方文档,放在一块来解释了
    首先要了解一个基础知识:es是分布式的,并且天生就是分布式的,在设计时就加入了分布式的元素
    
    节点:每个节点就是你起的一个es服务
    
    分片:每个分片是一个Lucene实例**,一个分片(shard)是一个最小级别的“工作单元(worker unit)”;
    将文档分布在分片的索引中,再将分片分布在多个节点中,实现冗余和负载均衡。(By distributing the documents in an index across multiple shards, and distributing those shards across multiple nodes)【这里如果不太明白没关系,下文会结合Lucene进行解释】
    
    集群:一个集群可包含一个或多个节点,配置es时指定cluster.name相同的所有启动了的es服务属于一个集群(这个其实没啥好说的,就是普通集群的概念)
    
    节点,分片,索引之间关系:
    一个node对应一个es instance
    一个node可以有多个index
    一个index可以有多个shard
    一个shard是一个lucene index
    
    **注:Lucene是apache开源的全文检索引擎工具包,es是对Lucene的进一步封装和优化得来的,下面我也会简单展开一些lucene,不过不会太深入。
    
  • es的功能模块

    这里的功能模块旨在解释和说明es进行的优化操作,而不会太详细的去介绍其中的内容,如果有意愿去深入了解,请参考官方文档https://www.elastic.co/guide/en/elasticsearch/reference/

    • 索引模块

      作用:控制与索引相关的所有方面

      因为es的索引是基于lucene做的,所以接下来要先简单普及下lucene的内部原理

      lucene

      lucene是 Doug Cutting用java开发的一套用于全文检索和搜寻的开源程序库,是es搜索引擎的底层调用,通常用于java环境中的全文索引和搜索。

      首先先说一下全文检索是什么
      
      我们生活中的数据总体分为两种:结构化数据 和非结构化数据。
      结构化数据: 指具有固定格式或有限长度的数据,如数据库,元数据等。
      非结构化数据: 指不定长或无固定格式的数据,如邮件,word文档等。
      当然有的地方还会提到第三种,半结构化数据,如XML,HTML等,当根据需要可按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。非结构化数据又一种叫法叫全文数据。
      
      按照数据的分类,搜索也分为两种:
      对结构化数据的搜索 :如对数据库的搜索,用SQL语句。再如对元数据的搜索,如利用windows搜索对文件名,类型,修改时间进行搜索等。
      对非结构化数据的搜索 :如利用windows的搜索也可以搜索文件内容,Linux下的grep命令,再如用Google和百度可以搜索大量内容数据。
      
      对非结构化数据也即对全文数据的搜索主要有两种方法:
      
      一种是顺序扫描法 (SeriaScanning): 所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。如果你有一个80G硬盘,如果想在上面找到一个内容包含某字符串的文件,不花他几个小时,怕是做不到。Linux下的grep命令也是这一种方式。大家可能觉得这种方法比较原始,但对于小数据量的文件,这种方法还是最直接,最方便的。但是对于大量的文件,这种方法就很慢了。
      
      有人可能会说,对非结构化数据顺序扫描很慢,对结构化数据的搜索却相对较快(由于结构化数据有一定的结构可以采取一定的搜索算法加快速度),那么把我们的非结构化数据想办法弄得有一定结构不就行了吗?
      这种想法很天然,却构成了全文检索的基本思路,也即将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。
      
      这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引 。
      
      这种说法比较抽象,举几个例子就很容易明白,比如字典,字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的,如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——也即对字的解释。
      
      这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search) 。
      
      其实就是把非结构化数据建立索引,再对它进行类似结构化搜索的过程
      全文检索大体分两个过程,索引创建 (Indexing) 和搜索索引 (Search) 。
      
      索引创建:将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。
      搜索索引:就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。
      

      其实此时索引的创建就是类似k,v形式的存储词典及其对应文档链表

      img

      而查找的过程就是根据词典中的词去取出倒排表中的链表而已

      1. 取出包含字符串“lucene”的文档链表。

      2. 取出包含字符串“solr”的文档链表。

      3. 通过合并链表,找出既包含“lucene”又包含“solr”的文件。

        img

      当然其中涉及的还有很多比如如何去分词形成一个词典,如何形成倒排链表,查找的相关性如何计算,这些太过详细就不在叙述了,这里推荐一下这个blog可以看下 blog.csdn.net/fatshaw/article/details/51959020,这大佬也是转的,转的谁的就不太清楚了。

      es基于lucene,相应的继承了lucene的优点,使用倒排索引快速的进行全文搜索。

      那么现在我们开始填介绍概念时候留的坑,通过上面的过程我们了解到lucene创建索引和全文检索的一个大概流程,那么es的index,和lucene的index有什么区别呢?

      就如概念中阐述的,es的索引(index)是一个逻辑空间(es中的索引是一个或多个物理分片的逻辑分组,每个物理分片是一个独立索引),其实这是对lucene的index的进一步优化;上面也说过,一个分片(shard)是一个lucene实例,其实就意味着一个分片就是单独的一组lucene的索引,里面包含了文档(document),而一个es的索引有一个或多个分片 (默认是 5 个),es的索引还包含了"type"字段(就像数据库中的表),用来逻辑划分和隔离索引中的数据。

      img

      盗个图,实在不想画了,不过这个图还是有点表达不完善,每个es里面只画了一个shard,大致是这个逻辑,这个图中还引入了副本(replica)的概念,其实这没啥好说的,就类似数据库的主从。

      倒排索引具有不变性的特性,被写入磁盘后是 不可改变 的:它永远不会被修改。 不变性有重要的价值:

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

      当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档 可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。

      如何解决不变性带来的缺点——动态更新索引

      当有了不变性的特性,那么想要对数据进行CUD操作就会变得困难,es对此的解决方案是——新建更多的索引;通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮询——从最早开始直到查询完,之后再对结果进行合并。

      A Lucene index with a commit point and three segments

      ​ 上图表示的是一个lucene节点,其中有三个段(segment),还有一个提交点(commit point

      ​ 每个段自身都是一个倒排索引,要注意的是这里的索引并不是lucene的索引,lucene的索引是所有的集合,外加提交点组成。

      A Lucene index with new documents in the in-memory buffer, ready to commit

      After a commit, a new segment is added to the index and the buffer is cleared

      上两图展示了新增索引流程,

      1. 新文档被收集到内存索引缓存(memory buffer)
      2. 不时地, 缓存被 提交
        • 一个新的段(一个追加的倒排索引)被写入磁盘。
        • 一个新的包含新段名字的 提交点 被写入磁盘。(注意,实际更新的是提交点)
        • 磁盘进行 同步 — 所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件。
      3. 新的段被开启,让它包含的文档可见以被搜索。
      4. 内存缓存被清空,等待接收新的文档。

      那么对于删除和更新操作又是怎样的?

      解决方案是在commit point里面附加了一个.del文件,文档的删除和更新其实都是逻辑层面,.del文件作用在返回查询结果的阶段,将需要更新 or 移除的文档在返回前从结果中移除

      上面的流程叙述中有没有什么技术性问题?

      其实不知道大家注意到没有,按照上述新增索引流程的第三条(新的段被开启,让它包含的文档可见以被搜索)需要在写入物理文件之后,那么就有一个疑问,也就是意味着直到段被加载到硬盘,之间的阶段其实是不允许被搜索到的,而段加载到内存建立索引,再到硬盘存储,这是个磁盘IO操作,可能效率会很慢。那么就会导致无法实时搜索的问题。

      lucene对此做的优化是在(磁盘进行 同步 — 所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件)这一步之前进行(新的段被开启,让它包含的文档可见以被搜索)这个操作,当新的段加入lucene-index文件缓冲区时且还没有写入磁盘就默认已经可搜索,这样就大大缩短了搜索的延迟时间。

      想想,是否还有问题?

      上面说,新文档的添加每次都会被创建一个段索引,而且这个过程是自动的(默认是1s添加一回),那么就会造成lucene索引里面有大量的段,这里面每个段都会占用一些资源,并且搜索请求也会轮询这些段获取结果,这会造成资源的大量占用和查询效率低下。

      所以lucene引入了段合并的概念,

      Two commited segments and one uncommited segment in the process of being merged into a bigger segment

      • 当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用。

      • 合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中。这并不会中断索引和搜索。

      一旦合并结束,老的段被删除

      • 新的段被刷新(flush)到了磁盘。 ** 写入一个包含新段且排除旧的和较小的段的新提交点。
      • 新的段被打开用来搜索。
      • 老的段被删除。

      当然,合并也是一个IO操作,需要耗费大量系统资源,所以es对其进行了资源限制,留出足够的搜索资源。

    • 分析与分析器

      作用:

      分析 包含两个过程:

      • 首先,将一块文本分成适合于倒排索引的独立的 词条

      • 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall

      从上面索引的介绍我们知道,建立索引的时候需要进行分词,依据分词结果形成词典并作为索引创建索引表,es封装了相应的分析工具:

      字符过滤器
      字符串按顺序通过字符过滤器 。在分词前整理字符串。字符过滤器可以用来去掉HTML,或者将 & 转化成and。
      分词器
      其次,字符串被分词器分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。
      Token 过滤器
      最后,词条按顺序通过每个token过滤器 。这个过程可能会改变词条(例如,小写化 Quick 为quick),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。
      

      分析的过程不仅仅是针对索引创建过程,且也针对索引查找过程,两个过程进行之前进行一系列的分析和更改,从而完成对数据可搜索性的增强。

      es提供了分析器的自定义模块,可以按需进行词条的预处理,具体方法就不说了,都查得到。

      es内置分词器有以下几类:

      Elasticsearch 的内置分词器 
      Standard Analyzer 一默认分词器,按词切分,小写处理 
      Simple Analyzer —按照非字母切分(符号被过滤),小写处理 
      Stop Analyzer-小写处理,停用词过滤(the, a, is) 
      Whitespace Analyzer 按照空格切分,不转小写 
      Keyword Analyzer — 不分词,直接将输入当作输出 
      Patter Analyzer — 正则表达式,默认 \W+(非字符分隔) 
      Language-提供了30多种常见语言的分词器 
      Customer Analyzer 自定义分词器
      
    • 映射(mapping)

      作用:文档的形式多种多样,不一定全是字符串形式的,映射的作用就是把文档的类型和es的类型进行关联和对应。Ø Mapping 定义文档字段的名称/类型/ 倒排索引的相关配置

      Elasticsearch 支持如下简单域类型:
      
      字符串: string
      整数 : byte, short, integer, long
      浮点数: float, double
      布尔型: boolean
      日期: date
      当你索引一个包含新域的文档(之前未曾出现)-- Elasticsearch会使用动态映射,通过JSON中基本数据类型,尝试猜测域类型
      

      es也提供了映射模块的自定义功能,这里不再展开。

    • 内部对象的索引

      作用:有些时候我们提供的是具有嵌套结构的JSON数据集,lucene不能够理解内部对象,es在此之上做了扁平化处理,将内部对象提取出来。

      例如

      {
        "gb": {
          "tweet": { 
            "properties": {
              "tweet":            { "type": "string" },
              "user": { 
                "type":             "object",
                "properties": {
                  "id":           { "type": "string" },
                  "gender":       { "type": "string" },
                  "age":          { "type": "long"   },
                  "name":   { 
                    "type":         "object",
                    "properties": {
                      "full":     { "type": "string" },
                      "first":    { "type": "string" },
                      "last":     { "type": "string" }
                    }
                  }
                }
              }
            }
          }
        }
      }
      

      转化为了如下⬇

      {
          "tweet":            [elasticsearch, flexible, very],
          "user.id":          [@johnsmith],
          "user.gender":      [male],
          "user.age":         [26],
          "user.name.full":   [john, smith],
          "user.name.first":  [john],
          "user.name.last":   [smith]
      }
      

      再例如如果内部嵌套是一个数组

      {
          "followers": [
              { "age": 35, "name": "Mary White"},
              { "age": 26, "name": "Alex Jones"},
              { "age": 19, "name": "Lisa Smith"}
          ]
      }
      

      转化为⬇

      {
          "followers.age":    [19, 26, 35],
          "followers.name":   [alex, jones, lisa, smith, mary, white]
      }
      
  • 文档的CRUD

    Type名,约定都用_doc

    Create-如果ID已经存在,会失败

    Index-如果ID不存在,创建新的 文档。否则,先删除现有的文档,

    再创建新的文档,版本会增加

    Update-文档必须已经存在,更新只会对相应字段做增量修改

    Delete-删除文档

    img

    *Create一个文档*

    img

    支持自动生成文档Id和指定文档Id两种方式

    通过调用“post /users/ _doc”

    Ø 系统会自动生成document ld

    使用HTTP PUT user/_create/1创建时URI中显示指定_create,此时如果该id的文档已经存在,操作失败

    *Get一个文档*

    找到文档,返回HTTP 200

    Ø 文档元信息

    ² _index / _type

    ² 版本信息,同一个Id的文档,即使被删除, Version 号也会不断增加

    ² _source 中默认包含了文档的所有原始信息

    找不到文档,返回 HTTP 404

    img

    *Index文档*

    Index 和 Create不一样的地方:如果文档不存在,就索引新的文档。否则现有文档会被删除,新的文档被索引。版本信息+1

    *Update文档*

    Update方法不会删除原来的文档,而是实现真正的数据更新

    *BULK API*

    支持在一次AP|调用中,对不同的索引进行操作

    支持四种类型操作

    Ø Index

    Ø Create

    Ø Update

    Ø Delete

    可以再UR中指定Index,也可以在请求的Payload中进行

    操作中单条操作失败,并不会影响其他操作

    返回结果包括了每一条操作执行的结果

    img

    *常见错误返回*

    img

    *正排索引和倒排索引*

    img

    *倒排索引的核心组成*

    倒排索引包含两个部分:

    单词词典(Term Dictionary),记录所有文档的单词,记录单词到倒排列表的关联关系

    Ø 单词词典一般比较大,可以通过B+树或哈希拉链法实现,以满足高性能的插入与查询

    倒排列表(Posting List)-记录了单词对应的文档结合,由倒排索引项组成

    Ø 倒排索引项 (Posting)

    ² 文档ID

    ² 词频TF-该单词在文档中出现的次数,用于相关性评分

    ² 位置(Position)-单词在文档中分词的位置。用于语句搜索 (phrase queny

    ² 偏移(Offset)-记录单词的开始结束位登,实现高亮显示

    img

    *Elasticsearch 的倒排索引*

    Elasticsearch 的 JSON文档中的每个字段,都有自己的倒排索引

    可以指定对某些字段不做索引

    Ø 优点:节省存储空间

    Ø 缺点:字段无法被搜索

免责声明

这篇文章里面有本人的理解,但是主要概念的阐述和功能点优化参考了多篇博客,以及官方文档,并不是全部原创内容,如有雷同,那一定是我抄过来的 😛。

posted @ 2021-02-04 17:35  seas  阅读(276)  评论(0编辑  收藏  举报