zoukankan      html  css  js  c++  java
  • Elasticsearch轻量搜索与分析

    前言

    Elasticsearch是一个文档存储系统,但是它更是一个搜索和数据分析引擎。了解Elasticsearch,就不得不认识elasticsearch的搜索与分析。该篇笔记主要记录Elasticsearch的搜索与分析。

    空搜索

    搜索API的最基础的形式是没有指定任何查询的空搜索,它简单地返回集群中所有索引下的所有文档:

    GET /_search
    

    返回结果:

    {
      "took" : 10,
      "timed_out" : false,
      "_shards" : {
        "total" : 23,
        "successful" : 23,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 10000,
          "relation" : "gte"
        },
        "max_score" : 1.0,
        "hits" : [
          {
            "_index" : ".kibana_1",
            "_type" : "_doc",
            "_id" : "space:default",
            "_score" : 1.0,
            "_source" : {
              "space" : {
                "name" : "Default",
                "description" : "This is your default space!",
                "color" : "#00bfb3",
                "disabledFeatures" : [ ],
                "_reserved" : true
              },
              "type" : "space",
              "references" : [ ],
              "migrationVersion" : {
                "space" : "6.6.0"
              },
              "updated_at" : "2020-12-08T06:47:14.690Z"
            }
          },
          ... 9 RESULTS REMOVED ...
        ]
      }
    }
    
    
    返回参数介绍

    hits:返回结果中最重要的部分是 hits ,它包含 total 字段来表示匹配到的文档总数,并且一个 hits 数组包含所查询结果的前十个文档。
    在 hits 数组中每个结果包含文档的 _index 、 _type 、 _id ,加上 _source 字段。这意味着我们可以直接从返回的搜索结果中使用整个文档。这不像其他的搜索引擎,仅仅返回文档的ID,需要你单独去获取文档。
    每个结果还有一个 _score ,它衡量了文档与查询的匹配程度。默认情况下,首先返回最相关的文档结果,就是说,返回的文档是按照 _score 降序排列的。在这个例子中,我们没有指定任何查询,故所有的文档具有相同的相关性,因此对所有的结果而言 1 是中性的 _score 。

    max_score 值是与查询所匹配文档的 _score 的最大值。

    took:执行整个搜索请求耗费了多少毫秒。

    _shards:在查询中参与分片的总数,以及这些分片成功了多少个失败了多少个。正常情况下我们不希望分片失败,但是分片失败是可能发生的。如果我们遭遇到一种灾难级别的故障,在这个故障中丢失了相同分片的原始数据和副本,那么对这个分片将没有可用副本来对搜索请求作出响应。假若这样,Elasticsearch 将报告这个分片是失败的,但是会继续返回剩余分片的结果。

    timeout:查询是否超时。默认情况下,搜索请求不会超时。不过我们可以指定搜索的最大时间:

    GET /_search?timeout=1ms
    

    指定索引与类型

    如果看过Elasticsearch文档的资料的,应该对于在url中添加索引与类型很熟悉,在搜索中,我们也一样可以在url指定索引与类型:

    GET /_search
    #在所有的索引中搜索所有的类型
    GET /gb/_search
    #在 gb 索引中搜索所有的类型
    GET /gb,us/_search
    #在 gb 和 us 索引中搜索所有的文档
    GET /g*,u*/_search
    #在任何以 g 或者 u 开头的索引中搜索所有的类型
    GET /gb/user/_search
    #在 gb 索引中搜索 user 类型
    GET /gb,us/user,tweet/_search
    #在 gb 和 us 索引中搜索 user 和 tweet 类型
    GET /_all/user,tweet/_search
    #在所有的索引中搜索 user 和 tweet 类型
    

    分页

    在默认搜索时,Elasticsearch默认只会返回10条数据,如果我们需要看到其它的文档的话就需要知道如何分页了。如下(size表示返回数量,from表示偏移量):

    GET /_search?size=2&from=1
    

    返回结果:

    {
      "took" : 8,
      "timed_out" : false,
      "_shards" : {
        "total" : 23,
        "successful" : 23,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 10000,
          "relation" : "gte"
        },
        "max_score" : 1.0,
        "hits" : [
          {
            "_index" : ".kibana_1",
            "_type" : "_doc",
            "_id" : "config:7.7.1",
            "_score" : 1.0,
            "_source" : {
              "config" : {
                "buildNum" : 30896
              },
              "type" : "config",
              "references" : [ ],
              "updated_at" : "2020-12-08T07:34:08.496Z"
            }
          },
          {
            "_index" : ".kibana_1",
            "_type" : "_doc",
            "_id" : "upgrade-assistant-telemetry:upgrade-assistant-telemetry",
            "_score" : 1.0,
            "_source" : {
              "upgrade-assistant-telemetry" : {
                "ui_open.overview" : 1
              },
              "type" : "upgrade-assistant-telemetry",
              "updated_at" : "2021-02-19T06:22:35.612Z"
            }
          }
        ]
      }
    }
    
    在分布式系统中深度分页

    假设在一个有 5 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。

    现在假设我们请求第 1000 页—​结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前10010个结果以外。 然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。

    可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。这就是 web 搜索引擎对任何查询都不要返回超过 1000 个结果的原因。

    轻量搜索

    Elasticsearch有两种搜索形式,一个是直接将参数添加在url上的轻量搜索,还有一个就是将参数包含在消息体的JSON中的搜索。在项目中不推荐使用轻量搜索,因为可读性不强,功能也没有那么全。

    下面借用几个例子来进行介绍:

    GET /_all/tweet/_search?q=tweet:elasticsearch
    GET /_all/tweet/_search?q=+name:john +tweet:mary
    GET /_search?q=mary
    GET /_all/tweet/_search?q=+name:(mary john) +date:>2014-09-10 +(aggregations geo)
    
    样例介绍

    第一个查询:查询在 tweet 类型中 tweet 字段包含 elasticsearch 单词的所有文档。

    第二个查询:查询在 name 字段中包含 john 并且在 tweet 字段中包含 mary 的文档。

    第三个查询:搜索返回包含 mary 的所有文档。没有指定参数的,会将文档所有参数值组成一个大字符串进行搜索。

    第四个查询:查询name 字段中包含 mary 或者 john,date 值大于 2014-09-10,_all 字段包含 aggregations 或者 geo的文档。

    注意
    • +前缀表示必须与查询条件匹配。类似地, - 前缀表示一定不与查询条件匹配。没有 + 或者 - 的所有其他条件都是可选的——匹配的越多,文档就越相关。
    • 这种精简让调试更加晦涩和困难。而且很脆弱,一些查询字符串中很小的语法错误,像 - , : , / 或者 " 不匹配等,将会返回错误而不是搜索结果。

    精确值与全文

    Elasticsearch中的数据可以概括的分成:精确值与全文。

    精确值就是表示一个精确数据的值,如日期、用户id等,同时字符串其实也可以表示精确值,如用户名称或邮箱。全文指的是文本数据,比如一篇文章或者一封邮件的内容。

    精确值的查询,要么是匹配,要么是不匹配,如mysql查询:

    SELECT * FROM TABLE_NAME
    WHERE name    = "John Smith"
      AND user_id = 2
      AND date    > "2014-09-15"
    

    相比之下,查询全文数据就复杂很多,我们一般是查询“该文档与我查询关键词的匹配度有多高?”。现在的搜索引擎使用的都是全文搜索,与精确值搜索不同,全文搜索我们希望能有以下效果:

    • 搜索 UK ,会返回包含 United Kindom 的文档。
    • 搜索 jump ,会匹配 jumped , jumps , jumping ,甚至是 leap 。
    • 搜索 johnny walker 会匹配 Johnnie Walker , johnnie depp 应该匹配 Johnny Depp 。
    • fox news hunting 应该返回福克斯新闻( Foxs News )中关于狩猎的故事,同时, fox hunting news 应该返回关于猎狐的故事。

    目前搜索引擎都是使用倒排索引来实现这个功能,接下来我们了解一下倒排索引。

    倒排索引

    倒排索引又叫反向索引,通俗的理解:正向索引是通过key找value,反向就是通过value找key。一个未经处理的数据库中,一般是以文档ID作为索引,以文档内容作为记录。
    而Inverted index 指的是将单词或记录作为索引,将文档ID作为记录,这样便可以方便地通过单词或记录查找到其所在的文档。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

    如我们有两个文档,他们的内容分别是如下内容:

    1.The quick brown fox jumped over the lazy dog
    2.Quick brown foxes leap over lazy dogs in summer
    

    为了创建倒排索引,我们首先将每个文档的 content 域拆分成单独的 词(我们称它为 词条 或 tokens ),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示:

    Term Doc_1 Doc_2
    Quick X
    The X
    brown X X
    dog X
    dogs X
    fox X
    foxes X
    in X
    jumped X
    lazy X X
    leap X
    over X X
    quick X
    summer X
    the X

    如搜索 quick brown ,我们只需要查找包含每个词条的文档:

    Term Doc_1 Doc_2
    brown X X
    quick X
    Total 2 1

    两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单 相似性算法 ,那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。

    但是如果我们这样查询的话,还是会存在一些问题。

    • Quick 和 quick 以独立的词条出现,然而用户可能认为它们是相同的词。
    • fox 和 foxes 非常相似, 就像 dog 和 dogs ;他们有相同的词根。
    • jumped 和 leap, 尽管没有相同的词根,但他们的意思很相近。他们是同义词。

    所以我们还需要搜索有以下效果:

    • Quick 可以小写化为 quick 。
    • foxes 可以 词干提取 --变为词根的格式-- 为 fox 。
    • jumped 和 leap 是同义词,可以索引为相同的单词jump。

    这里就涉及到了Elasticsearch另外几个知识点,分词、标准化和同义词。

    分析与分析器

    Elasticsearch的分析包括了下面过程:

    • 分词,将一块文本分成适合于倒排索引的独立的词语。
    • 标准化,将这些词语标准化,提高它们的“可搜索性”。

    分析器执行上面的工作,分析器实际有以下3个功能:

    • 字符过滤器:在分词之前对字符串进行初步整理。一个字符过滤器可以用来去掉HTML,或者将 & 转化成 and。
    • 分词器:将文本切割成单词。
    • Token过滤器:过滤器可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。
    内置分析器

    Elasticsearch有一些自带的分析器,这里以官方案例简单记录一下:

    #待分析文本
    Set the shape to semi-transparent by calling set_trans(5)
    
    • 标准分析器:标准分析器是Elasticsearch默认使用的分析器。它是分析各种语言文本最常用的选择。它根据 Unicode 联盟 定义的 单词边界 划分文本。删除绝大部分标点。最后,将词条小写。处理结果为:
    set, the, shape, to, semi, transparent, by, calling, set_trans, 5
    
    • 简单分析器:简单分析器在任何不是字母的地方分隔文本,将词条小写。处理结果为:
    set, the, shape, to, semi, transparent, by, calling, set, trans
    
    • 空格分析器:空格分析器在空格的地方划分文本。处理结果为:
    Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
    
    • 语言分析器:特定语言分析器可用于 很多语言。它们可以考虑指定语言的特点。例如, 英语 分析器附带了一组英语无用词(常用单词,例如 and 或者 the ,它们对相关性没有多少影响),它们会被删除。英语分析器的处理结果为:
    set, shape, semi, transpar, call, set_tran, 5
    
    分析器测试

    对于分析器不熟悉的,可以在analyze API进行测试,如下:

    GET /_analyze
    {
      "analyzer": "standard",
      "text": "Failure is never quite so frightening as regret."
    }
    

    返回结果:

    {
      "tokens" : [
        {
          "token" : "failure",
          "start_offset" : 0,
          "end_offset" : 7,
          "type" : "<ALPHANUM>",
          "position" : 0
        },
        {
          "token" : "is",
          "start_offset" : 8,
          "end_offset" : 10,
          "type" : "<ALPHANUM>",
          "position" : 1
        },
        {
          "token" : "never",
          "start_offset" : 11,
          "end_offset" : 16,
          "type" : "<ALPHANUM>",
          "position" : 2
        },
        {
          "token" : "quite",
          "start_offset" : 17,
          "end_offset" : 22,
          "type" : "<ALPHANUM>",
          "position" : 3
        },
        {
          "token" : "so",
          "start_offset" : 23,
          "end_offset" : 25,
          "type" : "<ALPHANUM>",
          "position" : 4
        },
        {
          "token" : "frightening",
          "start_offset" : 26,
          "end_offset" : 37,
          "type" : "<ALPHANUM>",
          "position" : 5
        },
        {
          "token" : "as",
          "start_offset" : 38,
          "end_offset" : 40,
          "type" : "<ALPHANUM>",
          "position" : 6
        },
        {
          "token" : "regret",
          "start_offset" : 41,
          "end_offset" : 47,
          "type" : "<ALPHANUM>",
          "position" : 7
        }
      ]
    }
    
    

    token 是实际存储到索引中的词条。 position 指明词条在原始文本中出现的位置。 start_offset 和 end_offset 指明字符在原始字符串中的位置。

    映射

    在搜索时,不能将所以的数据都作为文本来进行处理。为了能够将时间域视为时间,数字域视为数字,字符串域视为全文或精确值字符串, Elasticsearch 需要知道每个域中数据的类型。这个信息包含在映射中。

    核心简单域类型

    Elasticsearch 支持如下简单域类型:

    • 字符串: string
    • 整数 : byte, short, integer, long
    • 浮点数: float, double
    • 布尔型: boolean
    • 日期: date
      在Elasticsearch中,一个包含新域的文档如果没有指定映射关系的话,Elasticsearch将会使用动态映射,根据JSON中的基本类型来自动映射:
    JSON type 域 type
    布尔型: true 或者 false boolean
    整数: 123 long
    浮点数: 123.45 double
    字符串,有效日期: 2014-09-15 date
    字符串: foo bar string
    查看映射

    当我们需要查看到映射信息时,可以通过下面 方法:

    GET /gp_gamegroupdata_v1/_mapping
    

    返回信息:

    {
      "gp_gamegroupdata_v1" : {
        "mappings" : {
          "properties" : {
            "@timestamp" : {
              "type" : "date"
            },
            "@version" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "id" : {
              "type" : "long"
            },
            "name" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              },
              "analyzer" : "ik_max_word"
            },
            "status" : {
              "type" : "long"
            },
            "update_time" : {
              "type" : "long"
            }
          }
        }
      }
    }
    
    
    注意:较低版本的话,可能需要在url后面加上type。
    GET /{index}/_mapping/{type}
    
    自定义域映射

    尽管在很多情况下基本域数据类型已经够用,但你经常需要为单独域自定义映射,特别是字符串域。自定义映射允许你执行下面的操作:

    • 全文字符串域和精确值字符串域的区别
    • 使用特定语言分析器
    • 优化域以适应部分匹配
    • 指定自定义数据格式

    域最重要的属性是 type 。对于不是 string 的域,你一般只需要设置 type :

    {
        "number_of_clicks": {
            "type": "integer"
        }
    }
    

    string 域映射的两个最重要属性是 index 和 analyzer 。

    index 属性控制怎样索引字符串。它可以是下面三个值:

    • analyzed:
      首先分析字符串,然后索引它。换句话说,以全文索引这个域。
    • not_analyzed:索引这个域,所以它能够被搜索,但索引的是精确值。不会对它进行分析。
    • no:不索引这个域。这个域不会被搜索到。
    {
        "tag": {
            "type":     "string",
            "index":    "not_analyzed"
        }
    }
    

    analyzer 属性指定在搜索和索引时使用的分析器。默认, Elasticsearch 使用 standard 分析器, 但你可以指定一个内置的分析器替代它,例如 whitespace 、 simple 和 english:

    {
        "tweet": {
            "type":     "string",
            "analyzer": "english"
        }
    }
    
    测试映射

    与分析器一样,我们可以对于映射进行测试:

    GET /index_name/_analyze
    {
      "field": "name",
      "text": "Black-cats" 
    }
    

    返回结果:

    {
      "tokens" : [
        {
          "token" : "black-cats",
          "start_offset" : 0,
          "end_offset" : 10,
          "type" : "LETTER",
          "position" : 0
        },
        {
          "token" : "black",
          "start_offset" : 0,
          "end_offset" : 5,
          "type" : "ENGLISH",
          "position" : 1
        },
        {
          "token" : "cats",
          "start_offset" : 6,
          "end_offset" : 10,
          "type" : "ENGLISH",
          "position" : 2
        }
      ]
    }
    
    

    复杂核心域类型

    除了前面介绍的简单类型之外,Elasticsearch还支持一些复杂的类型,如null、数组、对象等。

    数组

    如果我们希望 tag 域包含多个标签。我们可以以数组的形式索引标签:

    { "tag": [ "search", "nosql" ]}
    

    对于数组,没有特殊的映射需求。任何域都可以包含0、1或者多个值,就像全文域分析得到多个词条。数组中所有的值必须是相同数据类型的 。你不能将日期和字符串混在一起。如果你通过索引数组来创建新的域,Elasticsearch 会用数组中第一个值的数据类型作为这个域的 类型 。

    空域

    下面三种域被认为是空的,它们将不会被索引:

    "null_value":               null,
    "empty_array":              [],
    "array_with_null_value":    [ null ]
    
    对象

    对象 -- 在其他语言中称为哈希,哈希 map,字典或者关联数组。

    内部对象 经常用于嵌入一个实体或对象到其它对象中。例如,与其在 tweet 文档中包含 user_name 和 user_id 域,我们也可以这样写:

    {
        "tweet":            "Elasticsearch is very flexible",
        "user": {
            "id":           "@johnsmith",
            "gender":       "male",
            "age":          26,
            "name": {
                "full":     "John Smith",
                "first":    "John",
                "last":     "Smith"
            }
        }
    }
    

    内部对象映射

    Elasticsearch 会动态监测新的对象域并映射它们为 对象 ,在 properties 属性下列出内部域:

    {
      "gb": {
        "tweet": { (1)
          "properties": {
            "tweet":            { "type": "string" },
            "user": { (2)
              "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" }
                  }
                }
              }
            }
          }
        }
      }
    }
    
    • 1为根对象
    • 2为内部对象

    内部对象索引

    Lucene 不理解内部对象。 Lucene 文档是由一组键值对列表组成的。为了能让 Elasticsearch 有效地索引内部类,它会将文档进行转化:

    {
        "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]
    }
    

    内部域 可以通过名称引用(例如, first )。为了区分同名的两个域,我们可以使用全 路径 (例如, user.name.first ) 或 type 名加路径( tweet.user.name.first )。

    内部对象数组

    内部对象的数组是如何被索引的。 假设我们有个 followers 数组:

    {
        "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]
    }
    

    {age: 35} 和 {name: Mary White} 之间的相关性已经丢失了,因为每个多值域只是一包无序的值,而不是有序数组。这足以让我们问,“有一个26岁的追随者?”

    但是我们不能得到一个准确的答案:“是否有一个26岁 名字叫 Alex Jones 的追随者?”

    作者:红雨
    出处:https://www.cnblogs.com/52why
    微信公众号: 红雨python
  • 相关阅读:
    Balance的数学思想构造辅助函数
    1663. Smallest String With A Given Numeric Value (M)
    1680. Concatenation of Consecutive Binary Numbers (M)
    1631. Path With Minimum Effort (M)
    1437. Check If All 1's Are at Least Length K Places Away (E)
    1329. Sort the Matrix Diagonally (M)
    1657. Determine if Two Strings Are Close (M)
    1673. Find the Most Competitive Subsequence (M)
    1641. Count Sorted Vowel Strings (M)
    1679. Max Number of K-Sum Pairs (M)
  • 原文地址:https://www.cnblogs.com/52why/p/14430706.html
Copyright © 2011-2022 走看看