zoukankan      html  css  js  c++  java
  • elasticsearch索引和映射

    1. elasticsearch如何实现搜索

    我们使用sql数据库的时候,要先创建数据库,然后创建表,之后才可以将数据存入到表中,这个表规定了数据的构成结构。虽然elasticsearch是一个无模式的搜索引擎并且可以自动匹配数据的结构(ES 会尽量根据 JSON 源数据的基础类型猜测你想要的字段类型映射),但是我们认为人为定义和控制数据结构是更好的方式。所以我们有必要学习如何创建索引和如何创建映射。

    1.1 搜索实例

    在mysql中我们查询created_at = '2014-09-15'那么该created_at字段只有是2014-09-15和不是两种情况,但是在elsaticsearch中则很有趣,看下下面的例子

    索引中有12条推文,其中只有一条包含日期 2014-09-15 ,但是看一看下面查询命中的 总数 (total):

    GET /_search?q=2014              # 12 results
    GET /_search?q=2014-09-15        # 12 results !
    GET /_search?q=date:2014-09-15   # 1  result
    GET /_search?q=date:2014         # 0  results !
    

    分析:

    • 第一个和第二个搜索是全文搜索,起作用的是 _all字段 ,这里都返回了12条推文,但是我们提到了只有一条包含了日期 2014-09-15,为什么12条数据都能匹配?

    • 第三和第四条数据时在 data字段 搜索,不难理解,第三条数据一条命中,第四条只是个年份,不可能有匹配项。

    为什么会出现上面的结果呢,全文搜索好像有点不一般啊,推测起来,这可能是数据在 _all 字段与 date 字段的索引方式不同。带着这个疑问我们一起分析下。

    可以查看下索引的类型映射

    GET /gb/_mapping/tweet
    

    结果如下

    {
       "gb": {
          "mappings": {
             "tweet": {
                "properties": {
                   "date": {
                      "type": "date",
                      "format": "strict_date_optional_time||epoch_millis"
                   },
                   "name": {
                      "type": "string"
                   },
                   "tweet": {
                      "type": "string"
                   },
                   "user_id": {
                      "type": "long"
                   }
                }
             }
          }
       }
    }
    

    尽管我们没有定义该索引的映射,但是es动态为我们产生了一个映射,这个响应告诉我们 date 字段被认为是 date 类型的。由于 _all 是默认字段,所以没有提及它。但是我们知道 _all 字段是 string 类型的。所以 date 字段和 string 字段 索引方式不同,数据类型不同,因此搜索结果也不一样,但这只是表象,深层次原因呢?

    1.2 es中数据的类型

    Elasticsearch 中的数据可以概括的分为两类:『精确值』和 『全文』。

    ▶『精确值』 如它们听起来那样精确。例如日期或者用户 ID,但字符串也可以表示精确值,例如用户名或邮箱地址。对于精确值来讲,Foo 和 foo 是不同的,2014 和 2014-09-15 也是不同的。

    精确值很容易查询。结果是二进制的:要么匹配查询,要么不匹配。这种查询很容易用 SQL 表示:

    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 应该返回关于猎狐的故事。

    es是如何做到以上的想法的呢?

    Elasticsearch 首先会 分析文档,之后根据结果创建 倒排索引 ,接下来一起讨论分析过程和倒排索引

    1.3 倒排索引

    一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

    例如,假设我们有两个文档,每个文档的 content 域包含如下内容:

    The quick brown fox jumped over the lazy dog
    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 +fox 不会得到任何匹配文档。(记住,+ 前缀表明这个词必须存在。)只有同时出现 Quick 和 fox 的文档才满足这个查询条件,但是第一个文档包含 quick fox ,第二个文档包含 Quick foxes 。

    我们的用户可以合理的期望两个文档与查询匹配。我们可以做的更好。

    如果我们将词条规范为标准模式,那么我们可以找到与用户搜索的词条不完全一致,但具有足够相关性的文档。例如:

    • Quick 可以小写化为 quick 。
    • foxes 可以 词干提取 --变为词根的格式-- 为 fox 。类似的, dogs 可以为提取为 dog 。
    • jumped 和 leap 是同义词,可以索引为相同的单词 jump 。
      现在索引看上去像这样:
    Term      Doc_1  Doc_2
    -------------------------
    brown   |   X   |  X
    dog     |   X   |  X
    fox     |   X   |  X
    in      |       |  X
    jump    |   X   |  X
    lazy    |   X   |  X
    over    |   X   |  X
    quick   |   X   |  X
    summer  |       |  X
    the     |   X   |  X
    ------------------------
    

    这还远远不够。我们搜索 +Quick +fox 仍然 会失败,因为在我们的索引中,已经没有 Quick 了。但是,如果我们对搜索的字符串使用与 content 域相同的标准化规则,查询字符串就会变成查询 +quick +fox ,这样两个文档都会匹配!

    注意:这非常重要。我们要用相同标准处理两部分的数据 查询字符串索引文本 ,你只能搜索在索引中出现的词条,所以索引文本和查询字符串必须标准化为相同的格式。

    1.4 分析与分析器

    1.4.1 什么是分析器

    首先,将一块文本分成适合于倒排索引的独立的 词条 ,
    之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall
    分析器执行上面的工作。 分析器 实际上是将三个功能封装到了一个包里

    • 字符过滤器

    首先,字符串按顺序通过每个 字符过滤器 。字符过滤器 用来 整理 一个尚未被分词的字符串。例如,如果我们的文本是HTML格式的,它会包含像 <p> 或者 <div> 这样的HTML标签,这些标签是我们不想索引的。我们可以使用 html清除 字符过滤器 来移除掉所有的HTML标签,并且像把 &Aacute; 转换为相对应的Unicode字符 Á 这样,转换HTML实体。

    一个分析器可能有0个或者多个字符过滤器。

    • 分词器

    其次,字符串被 分词器 分为单个的词条。 分词器把字符串分解成单个词条或者词汇单元。 标准 分析器 把一个字符串根据单词边界分解成单个词条,并且移除掉大部分的标点符号,然而还有其他不同行为的分词器存在。

    例如, 关键词 分词器 完整地输出 接收到的同样的字符串,并不做任何分词。 空格 分词器 只根据空格分割文本 。 正则 分词器 根据匹配正则表达式来分割文本 。

    一个分析器 必须 有一个唯一的分词器。

    • 词单元过滤器

    最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 aandthe 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。

    Elasticsearch提供了开箱即用的字符过滤器、分词器和词单元过滤器。 这些可以组合起来形成自定义的分析器以用于不同的目的。

    1.4.2 内置分析器种类

    但是, 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
    

    注意看 transparentcallingset_trans 已经变为词根格式。

    1.4.3 分析行为(开篇实例解答)

    当我们 索引 一个文档,它的全文域被分析成词条以用来创建倒排索引。 但是,当我们在全文域 搜索 的时候,我们需要将查询字符串通过 相同的分析过程 ,以保证我们搜索的词条格式与索引中的词条格式一致。

    全文查询,理解每个域是如何定义的,因此它们可以做 正确的事:

    • 当你查询一个 全文 域时, 会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。
    • 当你查询一个 精确值 域时,不会分析查询字符串, 而是搜索你指定的精确值。

    现在你可以理解在 开始章节 的查询为什么返回那样的结果:

    • date 域包含一个精确值:单独的词条 2014-09-15
    • _all 域是一个全文域,所以分词进程将日期(2014-09-15)转化为三个词条: 201409, 和 15
      当我们在 _all 域查询 2014,它匹配所有的12条推文,因为它们都含有 2014
    GET /_search?q=2014              # 12 results
    

    当我们在 _all 域查询 2014-09-15,它首先分析查询字符串,产生匹配 201409, 或 15 中 任意 词条的查询。这也会匹配所有12条推文,因为它们都含有 2014

    GET /_search?q=2014-09-15        # 12 results !
    

    当我们在 date 域查询 2014-09-15,它寻找 精确 日期,只找到一个推文:

    GET /_search?q=date:2014-09-15   # 1  result
    

    当我们在 date 域查询 2014,它找不到任何文档,因为没有文档含有这个精确日志:

    GET /_search?q=date:2014         # 0  results !
    

    1.4.4 测试分析器

    可以使用 analyze API 来看文本是如何被分析的。在消息体里,指定分析器和要分析的文本:

    GET /_analyze
    {
      "analyzer": "standard",
      "text": "Text to analyze"
    }
    

    结果

    {
       "tokens": [
          {
             "token":        "text",
             "start_offset": 0,
             "end_offset":   4,
             "type":         "<ALPHANUM>",
             "position":     1
          },
          {
             "token":        "to",
             "start_offset": 5,
             "end_offset":   7,
             "type":         "<ALPHANUM>",
             "position":     2
          },
          {
             "token":        "analyze",
             "start_offset": 8,
             "end_offset":   15,
             "type":         "<ALPHANUM>",
             "position":     3
          }
       ]
    }
    

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

    1.4.5 指定分析器

    当Elasticsearch在你的文档中检测到一个新的字符串域 ,它会自动设置其为一个全文 字符串 域,使用 标准 分析器对它进行分析。

    你不希望总是这样。可能你想使用一个不同的分析器,适用于你的数据使用的语言。有时候你想要一个字符串域就是一个字符串域--不使用分析,直接索引你传入的精确值,例如用户ID或者一个内部的状态域或标签。

    要做到指定数据类型和指定锁使用的分析器,我们必须手动指定这些域的映射。关于如何配置和指定分析器,将在索引的构建中讲解

    1.5 映射

    上面已经知道了倒排索引,分析和分析器内容,那么如何才能够是es实现以上特性呢,就是编写映射规则,我们配置映射的时候会规定数据的类型、是否使用分析器、使用何种分析器等内容。

    为了能够将时间域视为时间,数字域视为数字,字符串域视为全文或精确值字符串, Elasticsearch 需要知道每个域中数据的类型。映射定义了类型中的域,每个域的数据类型,以及Elasticsearch如何处理这些域。映射也用于配置与类型有关的元数据。我们这里先了解下映射的概念,下一章节会讲解如何编写映射。

    1.5.1 简单核心域类型

    简单域类型:

    • 字符串: string
    • 整数 : byte, short, integer, long
    • 浮点数: float, double
    • 布尔型: boolean
    • 日期: date

    如果没有定义数据的类型,也没有使用定义动态映射规则,那么会通过JSON中基本数据类型,尝试猜测域类型,使用如下规则:

    JSON type 域 type
    布尔型: true 或者 false boolean
    整数: 123 long
    浮点数: 123.45 double
    字符串,有效日期: 2014-09-15 date
    字符串: foo bar string

    1.5.2 复杂核心域类型

    除了简单标量数据类型, JSON 还有 null 值,数组,和对象,这些 Elasticsearch 都是支持的。

    • 多值域

    对于数组,没有特殊的映射需求,在es中没有专门的数组类型。任何域都可以包含0、1或者多个值,就像全文域分析得到多个词条。

    数组中所有的值必须是相同数据类型的 。你不能将日期和字符串混在一起。如果你通过索引数组来创建新的域,Elasticsearch 会用数组中第一个值的数据类型作为这个域的 类型 。

    数据:

    { "tag": [ "search", "nosql" ]}
    
    • 空域

    当然,数组可以为空。 这相当于存在零值。 事实上,在 Lucene 中是不能存储 null 值的,所以我们认为存在 null 值的域为空域。

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

    "null_value":               null,
    "empty_array":              [],
    "array_with_null_value":    [ null ]
    
    • 多层级对象

    数据:

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

    映射:

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

    type 映射(tweet)只是一种特殊的 对象 映射,我们称之为 根对象。

    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 的追随者?”

    相关内部对象被称为 nested 嵌套对象,可以回答上面的查询,这里暂不做研究。

    2. 索引

    2.1 什么是索引

    索引是es中存储数据的一种逻辑,大部分人对mysql会更熟悉一些,可以对比下

    (1)关系型数据库中的数据库(DataBase),等价于ES中的索引(Index)

    (2)一个数据库下面有N张表(Table),等价于1个索引Index下面有N多类型(Type)

    (3)一个数据库表(Table)下的数据由多行(ROW)多列(column,属性)组成,等价于1个Type由多个文档(Document)和多Field组成。

    (4)在一个关系型数据库里面,schema定义了表、每个表的字段,还有表和字段之间的关系。 与之对应的,在ES中:Mapping定义索引下的Type的字段处理规则,即索引如何建立、索引类型、是否保存原始索引JSON文档、是否压缩原始JSON文档、是否需要分词处理、如何进行分词处理等。

    2.2 创建一个默认配置的索引

    curl -XPOST 'http://localhost:9200/posts'
    

    以上索引采用的是默认配置,新的字段通过『动态映射』的方式被添加到类型映射。当然我们可以做更多的控制,手动创建索引,在请求体里传入设置或类型映射:

    PUT /my_index
    {
        "settings": { ... any settings ... },
        "mappings": {
            "type_one": { ... any mappings ... },
            "type_two": { ... any mappings ... },
            ...
        }
    }
    

    上面分为两部分,第一部分是settings,第二部分是mappings

    2.3 索引的设置(settings部分)

    可以通过修改配置来自定义索引行为,但是除非你理解这些配置的作用并且知道为什么要去修改,否则不要随意修改,Elasticsearch 已经提供了优化好的默认配置。

    配置:

    ▶ 1、number_of_shards

    每个索引的主分片数,默认值是 5 。这个配置在索引创建后不能修改。

    ▶ 2、number_of_replicas

    每个主分片的副本数,默认值是 1 。对于活动的索引库,这个配置可以随时修改。

    例如,我们可以创建只有 一个主分片,没有副本的小索引:

    PUT /my_temp_index
    {
        "settings": {
            "number_of_shards" :   1,
            "number_of_replicas" : 0
        }
    }
    

    ▶ 3、配置分析器

    第三个重要的索引设置是 analysis 部分, 用来配置已存在的分析器或针对你的索引创建新的自定义分析器,后面编写映射的

    前面我们已经介绍了一些内置的分析器,用于将全文字符串转换为适合搜索的倒排索引。

    standard 分析器是用于全文字段的默认分析器, 对于大部分西方语系来说是一个不错的选择。 它包括了以下几点:

    • standard 分词器,通过单词边界分割输入的文本。
    • standard 语汇单元过滤器,目的是整理分词器触发的语汇单元(但是目前什么都没做)。
    • lowercase 语汇单元过滤器,转换所有的语汇单元为小写。
    • stop 语汇单元过滤器,删除停用词--对搜索相关性影响不大的常用词,如 a , the , and , is

    默认情况下,停用词过滤器是被禁用的。如需启用它,你可以通过创建一个基于 standard 分析器的自定义分析器并设置 stopwords 参数。 可以给分析器提供一个停用词列表,或者告知使用一个基于特定语言的预定义停用词列表。

    在下面的例子中,我们创建了一个新的分析器,叫做 es_std , 并使用预定义的 西班牙语停用词列表:

    PUT /spanish_docs
    {
        "settings": {
            "analysis": {
                "analyzer": {
                    "es_std": {
                        "type":      "standard",
                        "stopwords": "_spanish_"
                    }
                }
            }
        }
    }
    

    es_std 分析器不是全局的--它仅仅存在于我们定义的 spanish_docs 索引中。 为了使用 analyze API来对它进行测试,我们必须使用特定的索引名:

    GET /spanish_docs/_analyze?analyzer=es_std
    El veloz zorro marrón
    

    简化的结果显示西班牙语停用词 El 已被正确的移除:

    {
      "tokens" : [
        { "token" :    "veloz",   "position" : 2 },
        { "token" :    "zorro",   "position" : 3 },
        { "token" :    "marrón",  "position" : 4 }
      ]
    }
    

    ▶ ▶自定义分析器

    和我们之前配置 es_std 分析器一样,我们可以在 analysis 下的相应位置设置 字符过滤器分词器词单元过滤器:

    PUT /my_index
    {
        "settings": {
            "analysis": {
                "char_filter": { ... custom character filters ... },
                "tokenizer":   { ...    custom tokenizers     ... },
                "filter":      { ...   custom token filters   ... },
                "analyzer":    { ...    custom analyzers      ... }
            }
        }
    }
    

    作为示范,让我们一起来创建一个自定义分析器吧,字符过滤器 实现1、2、 分词器 实现3, 词单元过滤器实现4、5:

    1、使用 html清除 字符过滤器移除HTML部分。(html_strip)

    2、使用一个自定义的 映射 字符过滤器把 & 替换为 " and " :(_to_and)

    "char_filter": {
        "&_to_and": {
            "type":       "mapping",
            "mappings": [ "&=> and "]
        }
    }
    

    3、使用 标准 分词器分词。(standard)

    4、小写词条,使用 小写 词过滤器处理。(lowercase)

    5、使用自定义 停止 词过滤器移除自定义的停止词列表中包含的词:(my_stopwords)

    "filter": {
        "my_stopwords": {
            "type":        "stop",
            "stopwords": [ "the", "a" ]
        }
    }
    

    一个完整的示例

    PUT /my_index
    {
        "settings": {
            "analysis": {
                "char_filter": {
                    "&_to_and": {
                        "type":       "mapping",
                        "mappings": [ "&=> and "]
                }},
                "filter": {
                    "my_stopwords": {
                        "type":       "stop",
                        "stopwords": [ "the", "a" ]
                }},
                "analyzer": {
                    "my_analyzer": {
                        "type":         "custom",
                        "char_filter":  [ "html_strip", "&_to_and" ],
                        "tokenizer":    "standard",
                        "filter":       [ "lowercase", "my_stopwords" ]
                }}
    }}}
    

    索引被创建以后,使用 analyze API 来 测试这个新的分析器:

    GET /my_index/_analyze?analyzer=my_analyzer
    The quick & brown fox
    

    下面的缩略结果展示出我们的分析器正在正确地运行:

    {
      "tokens" : [
          { "token" :   "quick",    "position" : 2 },
          { "token" :   "and",      "position" : 3 },
          { "token" :   "brown",    "position" : 4 },
          { "token" :   "fox",      "position" : 5 }
        ]
    }
    

    这个分析器现在是没有多大用处的,除非我们告诉 Elasticsearch在哪里用上它。我们可以像下面这样把这个分析器应用在一个 string 字段上:

    PUT /my_index/_mapping/my_type
    {
        "properties": {
            "title": {
                "type":      "string",
                "analyzer":  "my_analyzer"
            }
        }
    }
    

    2.4 索引的映射配置(mappings部分)

    2.4.1 映射写法

    比如索引gb下有tweet和user类型,那么映射应该如何定义呢,类似于下面

    {
       "gb": {
          "mappings": {
          
             "tweet": {
                "properties": {
                   "date": {
                      "type": "date",
                      "format": "strict_date_optional_time||epoch_millis"
                   },
                   "name": {
                      "type": "string"
                   },
                   "tweet": {
                      "type": "string"
                   },
                   "user_id": {
                      "type": "long"
                   }
                }
             },
             
             "user":{
                 ...
             }
          }
       }
    }
    
    

    上面json数据层次如下

    索引gb->   mappings->   类型tweet->   properites->   字段date
                                                         字段name
                                                         ......
                                                         
                            类型user->    properites->   字段aa
                                                         字段bb
                                                         ......
    

    接下来梳理下映射的配置项

    2.4.2 类型

    关于配置中类型怎么处理很简单,如果有多个类型,那么就并列的嵌套在mappings对象下。

    但是,这里我们有必要进一步了解下类型

    类型实际上是个逻辑区分,每个文档的类型名被存储在一个叫 _type 的元数据字段上。 当我们要检索某个类型的文档时, Elasticsearch 通过在 _type 字段上使用过滤器限制只返回这个类型的文档。

    如果有两个不同的类型,每个类型都有同名的字段,但映射不同(例如:一个是字符串一个是数字),那么当配置这样的映射时候会出现异常。因为在 Elasticsearch 中的所有类型最终都共享相同的映射,

    以 data 索引中两种类型的映射为例(只是演示,不真实有效):

    {
       "data": {
          "mappings": {
             "people": {
                "properties": {
                   "name": {
                      "type": "string",
                   },
                   "address": {
                      "type": "string"
                   }
                }
             },
             "transactions": {
                "properties": {
                   "timestamp": {
                      "type": "date",
                      "format": "strict_date_optional_time"
                   },
                   "message": {
                      "type": "string"
                   }
                }
             }
          }
       }
    }
    

    每个类型定义两个字段 (分别是 "name"/"address" 和 "timestamp"/"message" )。它们看起来是相互独立的,但在后台 Lucene 将创建一个映射,如:

    {
       "data": {
          "mappings": {
            "_type": {
              "type": "string",
              "index": "not_analyzed"
            },
            "name": {
              "type": "string"
            }
            "address": {
              "type": "string"
            }
            "timestamp": {
              "type": "long"
            }
            "message": {
              "type": "string"
            }
          }
       }
    }
    

    结论:

    • 多个类型可以在相同的索引中存在,只要它们的字段不冲突,类型可以很好的区分同一个集合中的不同细分,

    • 类型不适合 完全不同类型的数据 。如果两个类型的字段集是互不相同的,这就意味着索引中将有一半的数据是空的(字段将是 稀疏的 ),最终将导致性能问题。在这种情况下,最好是使用两个单独的索引。

    2.4.3 根对象

    映射的最高一层被称为 根对象 ,它可能包含下面几项:

    • 一个 properties 节点,列出了文档中可能包含的每个字段的映射
    • 各种元数据字段,它们都以一个下划线开头,例如 _type 、 _id 和 _source
    • 设置项,控制如何动态处理新的字段,例如 analyzer 、 dynamic_date_formats 和 dynamic_templates
    • 其他设置,可以同时应用在根对象和其他 object 类型的字段上,例如 enabled 、 dynamic 和 include_in_all

    在一个映射配置中的部分选项的位置示例:

    {
       "gb": {
          "mappings": {
              "people": {
                 "_source": {
                    "enabled":  false
                 },
                 "_all": {
                    "enabled":  false
                 },
                 "properties": {
                   "name": {
                      "type": "string",
                      "index": "analyzed",
                      "analyzer": "english"
                   },
                   "address": {
                      "type": "string"
                   }
                 }
             },
    
          }
       }
    }
    

    ▶ 1、属性

    • type属性

    字段的数据类型,例如 string 或 date

    对于不是 string 的域,你一般只需要设置 type :

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

    默认, string 类型域会被认为包含全文。就是说,它们的值在索引前,会通过 一个分析器,针对于这个域的查询在搜索前也会经过一个分析器。

    • index属性

    字段是否应当被当成全文来搜索( analyzed ),或被当成一个准确的值( not_analyzed ),还是完全不可被搜索( no )

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

    analyzed 首先分析字符串,然后索引它。换句话说,以全文索引这个域。

    not_analyzed 索引这个域,所以它能够被搜索,但索引的是精确值。不会对它进行分析。

    no 不索引这个域。这个域不会被搜索到。

    string 域 index 属性默认是 analyzed 。如果我们想映射这个字段为一个精确值,我们需要设置它为 not_analyzed :

    {
        "tag": {
            "type":     "string",
            "index":    "not_analyzed"
        }
    }
    

    其他简单类型(例如 long , double , date 等)也接受 index 参数,但有意义的值只有 no 和 not_analyzed , 因为它们永远不会被分析。

    • analyzer属性

    对于 analyzed 字符串域,用 analyzer 属性指定在搜索和索引时使用的分析器。上面我们用了很多篇幅介绍了分析器内容,在这里就可以使用这些分析器了。默认, Elasticsearch 使用 standard 分析器, 但你可以指定一个内置的分析器替代它,例如 whitespace 、 simple 和 english

    {
        "tweet": {
            "type":     "string",
            "analyzer": "english"
        }
    }
    
    • multi_field类型

    有时候需要将同一个值,存入到不同的字段,例如一个字段用于搜索,另一个字段用于统计。这时我们就可以使用multi_field来实现

     {
       "gb": {
          "mappings": {
             "user": {
                "properties": {
                   "name": {
                      "type": "multi_field",
                      "fields": {
                          "name": {
                              "type":     "string",
                              "index": "analyzed"
                          },
                          "facet": {
                              "type":     "string",
                              "index": "not_analyzed"
                          }
                      }
                   },
                  
                }
             },
    
          }
       }
    }
    

    上述定义会创建两个字段,一个通过name来引用,另一个通过name.facet引用。当然创建索引的时候没有必要专门指定两个不同字段,一个name字段就可以了,es会自动完成剩余的工作。

    ▶ 2、元数据:_source字段

    默认地,Elasticsearch 在 _source 字段存储代表文档体的JSON字符串, _source 字段在被写入磁盘之前先会被压缩。

    拥有该字段意味着下面这些:

    • 搜索结果包括了整个可用的文档——不需要额外的从另一个的数据仓库来取文档。
    • 如果没有 _source 字段,部分 update 请求不会生效。
    • 当你的映射改变时,你需要重新索引你的数据,有了_source字段你可以直接从Elasticsearch这样做,而不必从另一个(通常是速度更慢的)数据仓库取回你的所有文档。
    • 当你不需要看到整个文档时,单个字段可以从 _source 字段提取和通过 get 或者 search 请求返回。
    • 调试查询语句更加简单,因为你可以直接看到每个文档包括什么,而不是从一列id猜测它们的内容。

    然而,存储 _source 字段的确要使用磁盘空间。如果上面的原因对你来说没有一个是重要的,你可以用下面的映射禁用 _source 字段:

    PUT /my_index
    {
        "mappings": {
            "my_type": {
                "_source": {
                    "enabled":  false
                },
                "properties": {......}
            }
        }
    }
    

    在一个搜索请求里,你可以通过在请求体中指定 _source 参数,来达到只获取特定的字段的效果:

    GET /_search
    {
        "query":   { "match_all": {}},
        "_source": [ "title", "created" ]
    }
    

    这些字段的值会从 _source 字段被提取和返回,而不是返回整个 _source 。

    ▶ 3、 元数据:_all字段

    _all 字段:一个把其它字段值 当作一个大字符串来索引的特殊字段。 query_string 查询子句(搜索 ?q=john )在没有指定字段时默认使用 _all 字段。

    _all 字段在新应用的探索阶段,当你还不清楚文档的最终结构时是比较有用的。你可以使用这个字段来做任何查询,并且有很大可能找到需要的文档:

    GET /_search
    {
        "match": {
            "_all": "john smith marketing"
        }
    }
    

    随着应用的发展,搜索需求变得更加明确,你会发现自己越来越少使用 _all 字段。 _all 字段是搜索的应急之策。通过查询指定字段,你的查询更加灵活、强大,你也可以对相关性最高的搜索结果进行更细粒度的控制。

    如果你不再需要 _all 字段,你可以通过下面的映射来禁用:

    PUT /my_index/_mapping/my_type
    {
        "my_type": {
            "_all": { "enabled": false },
            "properties": {......}
        }
    }
    

    _all字段会增加索引的大小,因为在不需要使用的时候最好禁用。

    通过 include_in_all 设置来逐个控制字段是否要包含在 _all 字段中,默认值是 true。在一个对象(或根对象)上设置 include_in_all 可以修改这个对象中的所有字段的默认行为。

    你可能想要保留 _all 字段作为一个只包含某些特定字段的全文字段,例如只包含 titleoverviewsummarytags。 相对于完全禁用 _all 字段,你可以为所有字段默认禁用 include_in_all 选项,仅在你选择的字段上启用:

    PUT /my_index/my_type/_mapping
    {
        "my_type": {
            "include_in_all": false,
            "properties": {
                "title": {
                    "type":           "string",
                    "include_in_all": true
                },
                ...
            }
        }
    }
    

    记住,_all 字段仅仅是一个 经过分词的 string 字段。它使用默认分词器来分析它的值,不管这个值原本所在字段指定的分词器。就像所有 string 字段,你可以配置 _all 字段使用的分词器:

    PUT /my_index/my_type/_mapping
    {
        "my_type": {
            "_all": { "analyzer": "whitespace" }
        }
    }
    

    2.4.4 动态映射

    我们开头就说过es会自动匹配数据结构,这种机制其实就是动态映射,当 Elasticsearch 遇到文档中以前 未遇到的字段,它用 dynamic mapping 来确定字段的数据类型并自动把新的字段添加到类型映射。

    上面我们又考虑到,想要自己定义数据结构,所有要自己实现配置分析器,定义映射关系,但是我们想要新加一个字段,这时候还是会用到动态映射。

    有时这是想要的行为有时又不希望这样。通常没有人知道以后会有什么新字段加到文档,但是又希望这些字段被自动的索引。也许你只想忽略它们。如果Elasticsearch是作为重要的数据存储,可能就会期望遇到新字段就会抛出异常,这样能及时发现问题。

    幸运的是可以用 dynamic 配置来控制这种行为 ,可接受的选项如下:

    • true 动态添加新的字段--缺省
    • false 忽略新的字段
    • strict 如果遇到新字段抛出异常

    配置参数 dynamic 可以用在根 object 或任何 object 类型的字段上。你可以将 dynamic 的默认值设置为 strict , 而只在指定的内部对象中开启它, 例如:

    PUT /my_index
    {
        "mappings": {
            "my_type": {
                "dynamic":      "strict", 
                "properties": {
                    "title":  { "type": "string"},
                    "stash":  {
                        "type":     "object",
                        "dynamic":  true 
                    }
                }
            }
        }
    }
    

    把 dynamic 设置为 false 一点儿也不会改变 _source 的字段内容。 _source 仍然包含被索引的整个JSON文档。只是新的字段不会被加到映射中也不可搜索。

    2.4.5 自定义动态映射

    有时候你想增加新的字段,也想用动态映射,但是动态映射又不是很只能,幸运的是,es允许我们自己定义动态映射的规则。

    通过动态模板,你可以完全控制 新检测生成字段的映射。你甚至可以通过字段名称或数据类型来应用不同的映射。

    每个模板都有一个名称, 你可以用来描述这个模板的用途, 一个 mapping 来指定映射应该怎样使用,以及至少一个参数 (如 match) 来定义这个模板适用于哪个字段。

    模板按照顺序来检测;第一个匹配的模板会被启用。例如,我们给 string 类型字段定义两个模板:

    • es :以 _es 结尾的字段名需要使用 spanish 分词器。
    • en :所有其他字段使用 english 分词器。

    我们将 es 模板放在第一位,因为它比匹配所有字符串字段的 en 模板更特殊:

    PUT /my_index
    {
        "mappings": {
            "my_type": {
                "dynamic_templates": [
                    { "es": {
                          "match":              "*_es", 
                          "match_mapping_type": "string",
                          "mapping": {
                              "type":           "string",
                              "analyzer":       "spanish"
                          }
                    }},
                    { "en": {
                          "match":              "*", 
                          "match_mapping_type": "string",
                          "mapping": {
                              "type":           "string",
                              "analyzer":       "english"
                          }
                    }}
                ]
    }}}
    
    • match_mapping_type 允许你应用模板到特定类型的字段上,就像有标准动态映射规则检测的一样, (例如 string 或 long)。
    • match 参数只匹配字段名称
    • path_match 参数匹配字段在对象上的完整路径,所以 address.*.name 将匹配这样的字段:
    {
        "address": {
            "city": {
                "name": "New York"
            }
        }
    }
    
    • unmatch 和 path_unmatch将被用于未被匹配的字段。

    2.4.6 缺省映射

    通常,一个索引中的所有类型共享相同的字段和设置。 default 映射更加方便地指定通用设置,而不是每次创建新类型时都要重复设置。 default 映射是新类型的模板。在设置 default 映射之后创建的所有类型都将应用这些缺省的设置,除非类型在自己的映射中明确覆盖这些设置。

    例如,我们可以使用 default 映射为所有的类型禁用 _all 字段, 而只在 blog 类型启用:

    PUT /my_index
    {
        "mappings": {
            "_default_": {
                "_all": { "enabled":  false }
            },
            "blog": {
                "_all": { "enabled":  true  }
            }
        }
    }
    

    不难发现,default 映射也可以指定映射规则,所以_default_ 映射也是一个指定索引 dynamic templates 的好方法。

    2.5 索引的管理

    2.5.1 创建索引

    创建一个使用默认配置的索引

    curl -XPOST 'http://localhost:9200/posts'
    

    创建一个手动定义规则的索引

    PUT /my_index
    {
        "settings": { ... any settings ... },
        "mappings": {
            "type_one": { ... any mappings ... },
            "type_two": { ... any mappings ... },
            ...
        }
    }
    

    2.5.2 查看索引

    GET /gb/_mapping/tweet
    

    通过 /_mapping ,我们可以查看 Elasticsearch 在一个或多个索引中的一个或多个类型的映射

    {
       "gb": {
          "mappings": {
             "tweet": {
                "properties": {
                   "date": {
                      "type": "date",
                      "format": "strict_date_optional_time||epoch_millis"
                   },
                   "name": {
                      "type": "string"
                   },
                   "tweet": {
                      "type": "string"
                   },
                   "user_id": {
                      "type": "long"
                   }
                }
             }
          }
       }
    }
    

    2.5.3 删除索引

    # 删除一个索引
    DELETE /my_index
    
    # 删除 多个 索引:
    DELETE /index_one,index_two
    DELETE /index_*
    
    # 删除 全部 索引:
    DELETE /_all
    DELETE /*
    

    2.5.4 重构索引

    • 如果索引中还没有任何数据

    最简单的方法就是删除索引,然后重新按照自己的需求建立。

    • 如果已经有数据

    对现有数据的这类改变最简单的办法就是重新索引:用新的设置创建新的索引并把文档从旧的索引复制到新的索引。

    字段 _source 的一个优点是在Elasticsearch中已经有整个文档。你不必从源数据中重建索引,而且那样通常比较慢。

    为了有效的重新索引所有在旧的索引中的文档,用 scroll 从旧的索引检索批量文档 , 然后用 bulk API (批量写入)把文档推送到新的索引中。

  • 相关阅读:
    app移动测试 (自动化遍历方法和技巧)(转载大佬)
    使用Jmeter录制脚本并调试
    整理token,session ,cookies 和正则表达式整理
    ·接口测试核心:URL&HTTP协议详解
    性能测试基本概念 聚合报告指标分析
    数据分析技能点梳理
    数据分析整体知识点架构(转载)
    数据分析职业前景规划
    数据分析三年建议指导(书籍等)
    系统吞吐量(TPS)、用户并发量、性能测试概念和公式 (转载)
  • 原文地址:https://www.cnblogs.com/redirect/p/8436072.html
Copyright © 2011-2022 走看看