总进度

今天我们来学习Elasticsearch中的动态模版,其实我们在第一课2.2.3章节中就已经学过了类似的了,链接如下

根据给定的需求创建索引

但是今天咱们学点不一样的,上次只是简单的使用,这次咱要深入理解,完美掌控才是第一目标,废话少说,下面开始

什么是动态模版

动态模版允许我们控制动态字段映射规则之外的数据

动态字段的映射我们可以设置dynamic参数为true或者runtime来启用动态映射,也可以自定义动态映射模版自定义映射,自定义之后的模版就可以根据匹配规则应用于动态添加的字段。在上一节中,动态映射规则已经说过,本节直接搬过来,如下

JSON data type

"dynamic":"true"

"dynamic":"runtime"

null

不添加

不添加

true or false

boolean

boolean

double

float

double

long

long

long

object

object

不添加

array

根据数组中第一个非空的值判断

根据数组中第一个非空的值判断

日期类型的字符串

date

date

数字类型的字符串

float or long

double or long

不是日期也不是数字的字符串

text类型以及.keyword的字类型

keyword

其中自定义动态模版匹配规则方式有如下

  • match_mapping_typeElasticsearch中检测到的数据类型进行操作,参考上方图表
  • matchunmatch 可以使用模式匹配字段名
  • path_matchpath_unmatch 对字段的完整虚线路径进行操作
  • 如果动态模版没有定义march_mapping_typematchpath_match,那么模版不会匹配任何一个字段。但是_bulk 请求时可以通过模版名引用模版

也可以使用{name}{dynamic_type}模版变量作为占位符,比如后文中使用占位符实现分词器的指定等

动态模版的定义是对象数组

"dynamic_templates":[
  {
    "my_template_name":{#1
      ... 匹配条件 ...#2
      "mapping":{...}#3
    }
  },
  ...
]
  1. 自定义模版名称,可以是任意的字符串
  2. 模版的使用匹配条件可以是:match_mapping_type,match,match_pattern,unmatch,path_match,path_unmatch
  3. 匹配字段应该使用的索引映射

通过上面的 学习,我们知道了动态模版的定义,既然定义好了就该有验证,毕竟定义好的模版能不能用,是否定义的正确性还是需要验证的

验证动态模版

如果定义的模版包含无效的映射片段则会返回错误。在index操作时应用动态模版进行验证,但是大多数情况下在动态模版更新的时候会进行验证。提供无效的映射片段可能造成在更新或者某些条件下动态模版的验证失败,比如:

  • 如果没有指定match_mapping_type,但是这个模版提供了最少一个的有效映射,那么这个映射片段是有效的。但是如果将与模版匹配的字段映射为其他类型,则在index时返回验证错误。例如:配置一个动态模版,不包含match_mapping_type,这样就是一个有效的字符串类型,但是如果有一个字段匹配动态模版时被匹配为long,那么在index时将返回验证错误。建议就是将match_mapping_type配置为预期的JSON类型(参考开头的映射关系表)或者在mapping中配置好所需的类型
  • 如果我们在mapping的片段中使用了{name}占位符,那么在动态模版的更新时是跳过验证的。这是因为当时的字段名还是不知道的,所以在index时进行验证

如果有多个模版同时匹配,按照顺序匹配规则处理,第一个匹配的模版具有最高的优先级;当通过update mapping API更新动态模版时,所有的现有模版将会被覆盖,这就允许在最初的创建动态模版之后可以重新排序或者删除它们

动态模版中映射运行时字段

在上一节中我们的本小节内容例子就是使用的这个,有兴趣的可以回过头再看一眼,链接放到文章开头了

如果我们想Elasticsearch将某一种类型的新字段动态映射为运行时字段,那么我们可以通过设置"dynamic":"runtime",这些字段不会被编入索引,并且在查询时是从_source加载

或者我们使用默认的动态映射规则,然后创建动态模版,并将特定的字段映射为运行时字段。我们需要在index mapping中设置"dynamic:"true",然后创建一个动态模版,并将某种类型的新字段映射为运行时字段

举个例子,假设我们有一组数据,其中每个字段都是_ip开头的,根据动态映射规则,Elasticsearch会根据数值检测到的任何的字符串映射为float或者long,此时我们就可以创建一个动态模版,将这个新字符串映射为ip类型的运行时字段

下面是我们的一个例子,大概意思就是当Elasticsearch使用匹配模式是ip*的新字段时,它会将这些字段映射为ip类型的运行时字段。因为这些字段不是动态映射的,所以我们可以使用"dynamic":"true"或者"dynamic":"runtime"结合使用

PUT my-dynamic-template-001/
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_ip": {
          "match_mapping_type": "string",
          "match": "ip*",
          "runtime": {
            "type": "ip"
          }
        }
      }
    ]
  }
}

结合"dynamic":"true"或者"dynamic":"runtime"使用

PUT my-dynamic-template-001/
{
  "mappings": {
    "dynamic":"runtime",
    "dynamic_templates": [
      {
        "strings_as_ip": {
          "match_mapping_type": "string",
          "match": "ip*",
          "runtime": {
            "type": "ip"
          }
        }
      }
    ]
  }
}

上面的语句,我们会把符合匹配模式ip*的新字段映射为运行时字段,但是因为我们设置"dynamic":"runtime",所以后面的新字段我们都会设置为运行时字段,也就是下面这个语句,其中ip_req,ip_res,符合动态模版dynamic_templates的匹配规则ip*,而my_ip使用索引开头设置的"dynamic":"runtime"也会加入到运行时字段

PUT my-dynamic-template-001/_doc/1
{
  "ip_req":"127.0.0.1",
  "ip_res":"127.0.0.1",
  "my_ip":"locahost"
}

此时我们查看索引情况如下

{
  "my-dynamic-template-001" : {
    "mappings" : {
      "dynamic" : "runtime",
      "dynamic_templates" : [
        {
          "strings_as_ip" : {
            "match" : "ip*",
            "match_mapping_type" : "string",
            "runtime" : {
              "type" : "ip"
            }
          }
        }
      ],
      "runtime" : {
        "ip_req" : {
          "type" : "ip"
        },
        "ip_res" : {
          "type" : "ip"
        },
        "my_ip" : {
          "type" : "keyword"
        }
      }
    }
  }
}

上面就是一个简单的使用,其中

  • match_mapping_typestring,也就是字段的值是字符串
  • matchip* 即该字段名为ip开头的
  • runtime 定义被映射的字段类型,在上面例子中,被映射为runtime,类型为ip

match_mapping_type

match_mapping_typeJSON解析器检测到的数据类型。因为JSON不区分longinteger,也不区分doublefloat,所以解析时doublefloat都会被认为是doubleintegerlong都会被认为是long

注意:当使用动态映射的时候,ELasticsearch将始终选择更广泛的数据类型,但是有个例外是float类型,它的需要的存储空间少于double,并且对于大多数的应用程序来说足够准确。但是运行时字段不支持float类型,所以这就是"dynamic":"runtime"使用double的原因

Elasticsearch 会自动检测数据类型,检测规则就是文章开头的那个表格内容,并且我们还可以使用match_mapping_type中使用通配符*来匹配所有的数据类型

举个例子,如果我们想把整数字段映射为integer而不是long类型,字符串字段匹配为textkeyword类型,我们可以使用如下模版

  • 创建一个模版
PUT my-dynamic-template-002
{
  "mappings": {
    "dynamic_templates": [
      {
        "integers": {
          "match_mapping_type": "long",
          "mapping": {
            "type": "integer"
          }
        }
      },
      {
        "strings": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text",
            "fields": {
              "raw": {
                "type":  "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    ]
  }
}
  • 插入一条测试数据
PUT my-dynamic-template-002/_doc/1
{
  "my_integer": 5, 
  "my_string": "Some string" 
}
  • 查看生成的mapping
GET my-dynamic-template-002/_mapping

返回结果如下,my_integer会被映射为integermy_string会被映射为textkeyword

{
  "my-dynamic-template-002" : {
    "mappings" : {
      "dynamic_templates" : [
        {
          "integers" : {
            "match_mapping_type" : "long",
            "mapping" : {
              "type" : "integer"
            }
          }
        },
        {
          "strings" : {
            "match_mapping_type" : "string",
            "mapping" : {
              "fields" : {
                "raw" : {
                  "ignore_above" : 256,
                  "type" : "keyword"
                }
              },
              "type" : "text"
            }
          }
        }
      ],
      "properties" : {
        "my_integer" : {
          "type" : "integer"
        },
        "my_string" : {
          "type" : "text",
          "fields" : {
            "raw" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}

match与unmatch

match 使用模式匹配字段名称,unmatch 使用模式排除匹配字段

match_pattern可以通过设置此参数值调整match参数的行为,使match参数支持与字段名匹配的完整Java正则表达式来替代简单的通配符,如下

"match_pattern": "regex",
  "match": "^profit_\d+$"

如下示例,我们匹配名称以long_开头的所有字符串字段,排出以_text结尾的字符串字段,并将它们映射为long类型的字段

PUT my-dynamic-template-003
{
  "mappings": {
    "dynamic_templates": [
      {
        "longs_as_strings": {
          "match_mapping_type": "string",
          "match":   "long_*",
          "unmatch": "*_text",
          "mapping": {
            "type": "long"
          }
        }
      }
    ]
  }
}

PUT my-dynamic-template-003/_doc/1
{
  "long_num": "5", 
  "long_text": "foo" 
}

GET my-dynamic-template-003/_mapping

在上面例子中,long_num被映射为long类型的字段,这是因为match_mapping_typestring类型,并且是long_开头的。long_text虽然是long_开头的,但是也是_text结尾的,符合unmatch条件,这样的话long_text就按照默认规则string进行映射,生成textkeyword字段

path_match与path_unmatch

path_matchpath_unmatchmatchunmatch原理类似,但是是对字段的完整虚线路径进行匹配,而不仅仅是最终名称,例如some_object.*.some_field

PUT my-dynamic-template-004
{
  "mappings": {
    "dynamic_templates": [
      {
        "full_name": {
          "path_match":   "name.*",
          "path_unmatch": "*.middle",
          "mapping": {
            "type":       "text",
            "copy_to":    "full_name"
          }
        }
      }
    ]
  }
}

PUT my-dynamic-template-004/_doc/1
{
  "name": {
    "first":  "John",
    "middle": "Winston",
    "last":   "Lennon"
  }
}

如上所示,凡是name下的任何字段,除了以middle结尾的字段除外,都会被映射为text类型,并且copy_to带有full_name

执行如下命令查看

GET my-dynamic-template-004/_mapping

显示结果如下

{
  "my-dynamic-template-004" : {
    "mappings" : {
      "dynamic_templates" : [
        {
          "full_name" : {
            "path_match" : "name.*",
            "path_unmatch" : "*.middle",
            "mapping" : {
              "copy_to" : "full_name",
              "type" : "text"
            }
          }
        }
      ],
      "properties" : {
        "full_name" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "name" : {
          "properties" : {
            "first" : {
              "type" : "text",
              "copy_to" : [
                "full_name"
              ]
            },
            "last" : {
              "type" : "text",
              "copy_to" : [
                "full_name"
              ]
            },
            "middle" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        }
      }
    }
  }
}

从上面的结果可知,我们能够看出来,name之下的middle没有copy_to,它的映射按照默认的string映射规则,生成text以及keyword

需要注意的是,除了leaf字段外,path_matchpath_unmatch参数匹配对象路径,如下对文档索引将产生错误,因为name.title不能映射为文本类型

PUT my-dynamic-template-004/_doc/2
{
  "name": {
    "first":  "Paul",
    "last":   "McCartney",
    "title": {
      "value": "Sir",
      "category": "order of chivalry"
    }
  }
}

报错如下

{
  "error" : {
    "root_cause" : [
      {
        "type" : "mapper_parsing_exception",
        "reason" : "failed to parse field [name.title] of type [text] in document with id '2'. Preview of field's value: '{category=order of chivalry, value=Sir}'"
      }
    ],
    "type" : "mapper_parsing_exception",
    "reason" : "failed to parse field [name.title] of type [text] in document with id '2'. Preview of field's value: '{category=order of chivalry, value=Sir}'",
    "caused_by" : {
      "type" : "illegal_state_exception",
      "reason" : "Can't get text on a START_OBJECT at 5:14"
    }
  },
  "status" : 400
}

template variables (模版变量)

{name}{dynamic_type}占位符在映射中被替换为字段名称和检测到的动态类型

如下示例,将所有的字符串字段使用与字段同名的分析器,并禁用所有不是字符串字段的doc_values

PUT my-dynamic-template-005
{
  "mappings": {
    "dynamic_templates": [
      {
        "named_analyzers": {
          "match_mapping_type": "string",
          "match": "*",
          "mapping": {
            "type": "text",
            "analyzer": "{name}"
          }
        }
      },
      {
        "no_doc_values": {
          "match_mapping_type":"*",
          "mapping": {
            "type": "{dynamic_type}",
            "doc_values": false
          }
        }
      }
    ]
  }
}

PUT my-dynamic-template-005/_doc/1
{
  "english": "Some English text", 
  "count":   5 
}
GET my-dynamic-template-005/_mapping

在上面例子中,{name} 被替换为field name,而{dynamic_type}被替换为有JSON解析器检测到的数据类型

english字段分析器设置为english,而count被检测为long类型,并且doc_values设置为false

动态模版的例子

结构化搜索

当我们设置"dynamic":"true"时,Elasticsearch会将字符串字段映射为text类型,并带有一个keyword类型的子字段,如果我们只是结构化的搜索,对全文检索不需要,那么我们就可以让Elasticsearch映射为keyword字段,但是这样做的话,必须搜索与索引完全相同的值才能搜索这些字段,也就是精确匹配

PUT my-dynamic-template-006
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}

字符串的纯文本映射

与上面结构化搜索相反,如果我们只关心全文检索,并且不会对字段进行聚合、排序和精确查找,那么我们让Elasticsearch将字符串映射为text类型

PUT my-dynamic-template-007
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_text": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text"
          }
        }
      }
    ]
  }
}

对于最近版本增加的运行时字段,我们呢还可以创建一个动态模版,将字符串映射为运行时的keyword类型,虽说该字段不会被索引,但是它们的值是存在_source中,并且可以用于搜索、聚合、过滤和排序

例如如下示例,将创建一个动态模版,将string字段映射为keyword运行时字段,虽然runtime定义是空的,但是Elasticsearch会使用文章开头的匹配规则添加,任何一个未通过时间或者数字检测的字符串都会被映射为keyword类型

PUT my-dynamic-template-008
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "runtime": {}
        }
      }
    ]
  }
}

此时我们索引一个文档

PUT my-dynamic-template-008/_doc/1
{
  "english": "Some English text",
  "count":   5
}

查看映射关系时,可以看到english被映射为keyword类型的运行时字段

GET my-dynamic-template-008/_mapping
{
  "my-dynamic-template-008" : {
    "mappings" : {
      "dynamic_templates" : [
        {
          "strings_as_keywords" : {
            "match_mapping_type" : "string",
            "runtime" : { }
          }
        }
      ],
      "runtime" : {
        "english" : {
          "type" : "keyword"
        }
      },
      "properties" : {
        "count" : {
          "type" : "long"
        }
      }
    }
  }
}

Disable norms

如果我们不按照评分进行排序,那么可以禁用索引中的评分因子以节省空间

PUT my-dynamic-template-009
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text",
            "norms": false,
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    ]
  }
}

上面模版中出现的keyword的关键字字段是与动态映射的默认规则一致的,如果我们不需要可以按照上面的例子将其删除

时间序列

在使用Elasticsearch进行时间序列的分析时,通常会有很多的数字字段,但是这些数字字段一般不会进行过滤通常都是进行聚合。在这种情况下我们可以禁用这些字段的索引以节省磁盘空间,或许也可能获得一些索引的速度

PUT my-dynamic-template-010
{
  "mappings": {
    "dynamic_templates": [
      {
        "unindexed_longs": {
          "match_mapping_type": "long",
          "mapping": {
            "type": "long",
            "index": false
          }
        }
      },
      {
        "unindexed_doubles": {
          "match_mapping_type": "double",
          "mapping": {
            "type": "float", 
            "index": false
          }
        }
      }
    ]
  }
}

与默认的动态规则一样,double被映射为float,因为他可以满足绝大多数的请求。并且只需要一半的磁盘空间

总结

好了关于动态模版的知识到这就结束了,从开始介绍什么是动态模版,其次是动态模版如何使用以及最后的动态模版的常用示例,那么你掌握了多少呢,快去尝试一下吧,下一篇预告《为时间序列索引定义索引生命周期策略ILM》敬请期待