• 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
  • 相关阅读:
    Appium+python自动化17-启动iOS模拟器APP源码案例
    Pycharm上python和unittest两种姿势傻傻分不清楚
    jenkins显示html样式问题的几种解决方案总结
    Appium+python自动化16-appium1.6在mac上环境搭建启动ios模拟器上Safari浏览器
    selenium+python在mac环境上的搭建
    python+requests接口自动化完整项目设计源码
    Appium+python自动化15-在Mac上环境搭建
    git使用教程2-更新github上代码
    git使用教程1-本地代码上传到github
    针对初学者的A*算法入门详解(附带Java源码)
  • 原文地址:https://www.cnblogs.com/52why/p/14430706.html
Copyright © 2020-2023  润新知