第3章 映射
映射是定义存储和索引的文档类型以及字段的过程。索引中的每一个文档都有一个类型,每种类型都有它自己的映射。一个映射定义了文档结构内每个字段的数据类型。映射通过配置来定义字段类型与该类型相关联的元数据的关系。例如,可以通过映射来定义日期类型的格式、数字类型的格式或者文档中所有字段的值是否应该被_all字段索引等。本章将介绍映射的概念、参数,以及动态映射的使用等。
3.1 概念
1.映射类型
每个索引拥有一个或多个映射类型,用来在索引中将文档划分为不同的逻辑组。
每个映射类型拥有:
- 元字段:用来定义如何处理文档的元数据。元字段包括文档的_index字段、_type字段、_id字段和_source字段。
- 字段或属性:每个映射类型包含与类型相关的字段或属性列表。同一索引中不同的映射类型的相同名称字段必须拥有相同的映射。
2. 字段数据类型
每个字段拥有一个数据类型,可以是简单数据类型,比如字符串型(String)、日期型(date)、长整型(long)、双精度浮点型(double)、布尔型(boolean)或者IP。
支持JSON的层次性类型,比如对象(object)、嵌套(nested),或者特定的类型,比如地理点(geo_point)、地理形状(geo_shape)。
基于不同目的对同一个字段进行不同方式的索引是很有用的。例如,一个字符串类型字段可以在全文搜索中作为分析字段,在排序或聚合时作为不分析的字段。或者,可以通过标准分析器、英文分析器或者法语分析器对字符串字段进行索引。
3. 动态映射
字段和映射类型在使用前不需要事先定义。依靠动态映射,通过索引文档,新的映射类型和字段名会自动添加。新的字段可以添加到顶级映射类型或者映射内部的对象和嵌入字段。
动态映射可以配置自定义映射用于新类型或者新字段。
4. 显式映射
相对于Elasticsearch来说,我们对于数据类型的掌控更加全面,所以我们可以制定显式映射而不是使用动态映射。
当创建索引的时候,可以创建映射类型和字段。也可以在当前的索引中通过映射创建接口添加映射类型和字段。
5. 更新当前映射
除了记录之外,现有的映射类型和字段不能更新。修改映射意味着废弃已经索引的文档,我们反而应该根据映射创建新的索引并且重新索引数据。
6. 映射类型之间共享字段
映射类型在每个索引中是唯一的,就是在一个索引的多个类型中,如果多个类型中的映射名称一样,则它必须是相同的类型。
例如:如果一个title字段同时存在于user和blogpost映射类型中,title字段在每个类型中必须拥有相同的映射。这个规则的唯一例外是:对于copy_to参数、dynamic参数、enabled参数、ignore_above参数,include_in_all参数,每个不同映射类型中的字段拥有不同的参数设置。
通常,相同名称的字段由相同类型的数据构成,所以拥有相同的索引是没有问题的。当产生类型冲突的时候,可以选择更详细的命名,比如user_title和blog_title。
7. 映射示例
当创建索引的时候,可以指定映射:
请求:
PUT http://127.0.0.1:9200/secisland
{
"mappings":{
"user":{
"_all":{"enabled": false},
"properties": {
"title": {"type": "string"},
"name": {"type": "string"},
"age": {"type":"integer"}
}
},
"blogpost": {
"properties":{
"title": {"type": "string"},
"body": {"type": "string"},
"user_id": {"type":"string", "index":"not_analyzed"},
"created": {
"type":"date",
"format" : "strict_date_optional_time||epoch_millis"
}
}
}
}
}
上面的接口表示创建一个名为secisland的索引,在索引中添加名为user和blogpost的映射类型。user映射类型取消元字段_all,指定了每个映射类型的字段或属性,指定了每个字段的数据类型和映射。
3.2 字段数据类型
Elasticsearch支持一系列不同的数据类型来定义文档字段,分为核心数据、复杂数据、地理数据、专门数据类型。
核心数据类型包括:
- 字符串数据类型:string
- 数字型数据类型:long、integer、short、byte、double、float
- 日期型数据类型:date
- 布尔型数据类型:boolean
- 二进制数据类型:binary
复杂数据类型包括:
- 数组数据类型:不需要专门的类型来定义数组。
- 对象数据类型:object,单独的JSON对象。
- 嵌套数据类型:nested,关于JSON对象的数组。
地理数据类型包括:
- 地理点数据类型:geo_point,经纬点。
- 地理形状数据类型:geo_shape,多边形的复杂地理形状。
专门数据类型包括:
- IPv4数据类型:IP协议为IPv4的地址。
- 完成数据类型:completion,提供自动补全的建议。
- 单词计数数据类型:token_count,统计字符串中的单词数量。
3.2.1 核心数据类型
1. 字符串数据类型
字符串数据类型的字段接受文本值,可以分为如下两种:
- 全文本。全文本值通常用于基于文本的相关性搜索,全文本字段可以分词,即在索引执行之前通过一个分词器将字符串转换为单词列表。分词操作使得Elasticsearch可以在全文本字段上搜索单词。全文本字段不用于排序而且很少用于聚合。
- 关键字。关键字是个精准值。通常用于过滤(例如,为published的博客文章获取所有status字段值)、排序、参与聚合。关键字字段不参与分词。
全文本(可以分词)字段和关键字(不可以分词)字段映射示例如下:
请求:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"full_name": {
"type": "string"
},
"status": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
其中,full_name字段是一个可分词的全文本类型字段——index:默认是analyzed。status字段是一个不可分词的关键字字段。
同一个字段同时拥有全文本和关键字两个版本,通常是很有用的:一个用于全文搜索,另一个用于聚合和排序。这可以通过多字段来实现。
字符串数据类型的字段可以接受的参数如表3-1所示。
表3-1 字符串数据类型的字段可以接受的参数
参数 | 说明 |
---|---|
analyzer | 分词器可以用于可分词的字符串型字段。默认为默认的索引分词器或者标准分词器 |
boost | 字段级索引加权。接受浮点型数字,默认值是1.0 |
doc_values | 定义字段是否应该以列跨度的方式存储在磁盘上,以便用于排序、聚合或者脚本。接受true或false参数。对于不可分词字段,默认值是true。可分词字段不支持这个参数 |
fielddate | 决定字段是否可以使用内存字段值进行排序,聚合或者在脚本中使用。接受disabled或者paged_bytes(默认)参数。没有分析过的字段会优先使用文档值 |
ignore_above | 不要索引或执行任何长于这个值的字符串。默认为0(禁用) |
include_in_all | 决定字段是否应该包含在_all字段中。接受true或false参数 |
index | 决定字段是否可以被用户搜索。接受参数analyzed(默认,视为全文本字段),not_analyzed(作为关键字字段)以及no |
index_options | 定义存储在索引中,用于搜索和突出用途的信息 |
norms | 计算查询得分的时候是否应该考虑字段长度。默认依赖于索引设置:analyzed字段默认为{"enabled":true, "loading":"lazy"}。not_analyzed字段默认为{"enabled":false} |
null_value | 接受一个字符串值替换所有null值。默认为null,意味着字段作为缺失字段。如果字段是可分词(analyzed)的,null_value也会被分词 |
position_increment_gap | 定义字符串数组中应该插入的虚拟索引词的数量。默认值为100,以一个较合理的值来阻止短语查询在跨字段匹配索引词的时候溢出 |
store | 决定字段值是否应该被存储以及从_source字段分别获取。接受参数true或false(默认) |
search_analyzer | 指定搜索时用在可分词字段上的分词器 |
search_quote_analyzer | 指定搜索短语时使用的分词器 |
similarity | 指定使用的相似度评分算法,默认为TF/IDF |
term_vector | 定义一个可分词字段是否应该存储索引词向量。默认为no |
2. 数字型数据类型
数字型数据类型支持的数字类型如表3-2所示。
表3-2 数字型数据类型
参数 | 说明 |
---|---|
long | 一个有符号的64位整数,最小值为-263,最大值为263-1 |
integer | 一个有符号的32位整数,最小值为-231,最大值为231-1 |
short | 一个有符号的16位整数,最小值为-32768,最大值为32767 |
byte | 一个有符号的8位整数,最小值为-128,最大值为127 |
double | 64位双精度浮点数 |
float | 32位单精度浮点数 |
数字型字段映射配置示例如下:
请求:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog:" {
"properties": {
"number_of_bytes": {
"type": "integer"
},
"time_in_seconds": {
"type": "float"
}
}
}
}
}
数字型字段参数见表3-3。
表3-3数字型字段参数
参数 | 说明 |
---|---|
coerce | 试着将字符串型数据转换为整数型数字数据 |
boost | 字段级索引加权,接受浮点型数字参数,默认为1.0 |
doc_values | 定义字段是否应该以列跨度的方式存储在磁盘上,以便用于排序、聚合或者脚本。接受true(默认)或false参数 |
ignore_malformed | 如果是true,畸形的数字会被忽略。如果是false(默认),畸形数字会抛出异常并丢弃整个文档 |
include_in_all | 决定 字段是否应该包含在_all字段中。接受true或false参数。如果所有被设置在no或者父对象字段设置include_in_all为false,参数默认值为false;其他情况下,默认值为true |
index | 决定字段是否可以被用户搜索。接受参数not_analyzed(默认)以及no |
null_value | 接受与字段同类型的数字型值来代替null值。默认是null,意味着字段作为缺失字段 |
precision_step | 控制索引的额外索引词的数量来使范围查询更快速。默认值取决于数字类型 |
store | 决定字段值是否应该存储以及从_source字段分别获取。接受参数true或false(默认) |
3. 日期型数据类型
JSON没有日期型数据类型,所以在Elasticsearch中,日期可以是:
- 包含格式化日期的字符串,例如“2015-01-01”或者“2015/01/01 12:10:30”。
- 代表世界毫秒数的长整型数字。
- 代表时间秒数的整数。
通常,日期被转换为UTC(如果时区被指定)但是存储为代表时间毫秒数的长整数。
可以自定义时间格式,如果没有指定格式,则使用默认值:
strict_date_optional_time||epoch_millis
这意味着接受任意时间戳的日期值,例如:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"date": {
"type":"date"
}
}
}
}
}
创建映射之后,可以防止日期型数据:
PUT http://127.0.0.1:9200/secisland/secilog/1
{"date":"2015-01-01"}
PUT http://127.0.0.1:9200/secisland/secilog/2
{"date":"2015-01-01T12:10:30Z"}
PUT http://127.0.0.1:9200/secisland/secilog/3
{"date":"1420070400001"}
(1)多日期格式
使用双竖线(||)分割,可以指定多个日期格式。每个格式会被依次尝试,直到找到匹配的格式。第一个格式会用于将时间毫秒数值转换为字符串:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"date": {
"type": "date",
"format": "yyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
}
(2)日期型数字的字段参数
日期型数据的字段参数参见表3-4。
表3-4 日期型字段参数
参数 | 说明 |
---|---|
boost | 字段级索引加权,接受浮点型数字参数,默认为1.0 |
doc_values | 定义字段是否应该以列跨度的方式存储在磁盘上,以便用于排序,聚合或者脚本。接受true(默认)或false参数 |
format | 可解析的日期格式。默认为strict_date_optional_time||epoch_millis |
ignore_malformed | 如果是true,畸形的日期会被忽略。如果是false(默认),畸形日期会抛出异常并丢弃整个文档 |
include_in_all | 决定字段是否应该包含在_all字段中。接受true或false参数。如果索引被设置为no或者父对象字段设置include_in_all为false,参数默认值为false;其他情况下,默认值为true |
index | 决定字段是否可以被用户搜索。接受参数not_analyzed(默认)以及no |
null_value | 接受日期型值来代替null值。默认是null,意味着字段作为缺失字段。 |
precision_step | 控制索引的额外索引词的数量来使范围查询更快速。默认值为16 |
store | 决定字段值是否应该存储以及从_source字段分别获取。接受参数true或false(默认) |
4. 布尔数据类型
布尔型字段接受true或false值,也可以接受代表真或假的字符串和数字:
- 假值——false,“false”,“off”,“no”,“0”,“”(空字符串),0,0.0。
- 真值——其他任何非假的值。
示例如下:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"is_published": {
"type": "boolean"
}
}
}
}
}
请求:
POST http://127.0.0.1:9200/secisland/secilog/1
{"is_published": true}
索引词聚合之类的聚合使用1和0作为key,使用字符串"true"和"false"作为key_as_string。使用脚本时,布尔字段返回1和0。布尔型字段参数见表3-5。
表3-5 布尔型字段参数
参数 | 说明 |
---|---|
boost | 字段级索引加权,接受浮点型数字参数,默认为1.0 |
doc_values | 定义字段是否应该以列跨度的方式存储在磁盘上,以便用于排序、聚合或者脚本。接受true(默认)或false参数 |
index | 决定字段是否可以被用户搜索。接受参数not_analyzed(默认)以及no |
null_value | 接受布尔型值来代替null值。默认是null,意味着字段被作为缺失字段 |
store | 决定字段值是否应该被存储以及从_source字段分别获取。接受参数true或false(默认) |
5. 二进制数据类型
二进制数据类型接受Base64编码字符串的二进制值。字段不以默认方式存储而且不能搜索:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"name": {"type":"string"},
"blob": {"type":"binary"}
}
}
}
}
PUT http://127.0.0.1:9200/secisland/secilog/1
{
"name": "Some binary blob",
"blob": "U29tZSBiaW5hcnkgYmxvYg=="
}
Base64编码二进制值不能嵌入换行符\n。二进制数据类型的字段参数如下所示:
- doc_values——定义字段是否应该以列跨度的方式存储在磁盘上,以便用于排序、聚合或者脚本。接受true(默认)或false参数。
- store——决定字段值是否应该存储以及从_source字段分别获取。接受参数true或false(默认)。
3.2.2 复杂数据类型
1. 数组数据类型
在Elasticsearch中,没有专门的数组类型。每个字段默认可以包含零个或更多的值,然而,数组中所有的值都必须是相同的数据类型。例如:
- 字符串数组:["one", "two"]
- 整数数组:[1,2]
- 由数组组成的数组:[1,[2,3]],等同于[1,2,3]
- 对象数组:[{"name":"Mary","age":12}, {"name":"John", "age":10}]
注意:无法对数组中的每一个对象进行单独的查询。
当动态添加字段的时候,数组中第一个元素的值决定了字段类型,随后的所有值必须是相同的数据类型或者可以强制转换为相同的数据类型。
Elasticsearch不支持混合数据类型的数组,比如:[10, "some string"]。
数组可能包含null值,会被null_value配置替换掉或者忽略掉。一个空数组[]被当做缺失字段——没有值的字段。
2. 对象数据类型
JSON文档是天然分层的:文档可以包含内部对象。同样,内部对象也可以包含内部对象。
PUT http://127.0.0.1:9200/secisland/secilog/1
{
"region": "US",
"manager": {
"age": 30,
"name": {"first": "John", "last": "Smith"}
}
}
本质上,文档被简单地索引为键值对的列表,形如:
{
"region": "US",
"manager.age": 30,
"manager.name.first": "John",
"manager.name.last": "Smith"
}
上面文档的映射结构为:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"region": {
"type": "string",
"index": "not_analyzed"
},
"manager": {
"properties": {
"age": {"type": "integer"},
"name": {
"properties": {
"first":{"type":"string"},
"last":{"type":"string"}
}
}
}
}
}
}
}
}
映射类型是一种对象类型,拥有参数字段,manager字段是一个内部对象字段,manager.name字段是manager字段中的内部对象字段。可以明确地设置type字段为object(默认值)。对象数据类型的参数如下所示:
- dynamic——定义新的参数是否应该动态加入到已经存在的对象中。接受true(默认),false和strict。
- enabled——赋值给对象字段的JSON值应该被解析和索引(true,默认)还是完全忽略(false)。
- include_in_all——为对象内的所有属性设置include_in_all值。对象本身不添加到_all字段。
- properties——对象内的字段可以是任意数据类型,包括对象数据类型。新的属性可以添加到已存在的对象中。
3. 嵌套数据类型
嵌套数据类型是对象数据类型一个专门的版本,用来使一组对象被单独地索引和查询。
(1)对象数组是如何摊平的
Lucene没有内部对象的概念,所以Elasticsearch利用简单的列表存储字段名和值,将对象层次摊平。例如,下面的文档:
PUT http://127.0.0.1:9200/secisland/secilog/1
{
"group": "fans",
"user": {
{"first": "John", "last": "Smith"},
{"first": "Alice", "last": "White"}
}
}
把它内部转换为文档,结构如下:
{
"group": "fans",
"user.first": ["alice","john"],
"user.last": ["smith","white"]
}
user.first和user.last字段存在多值字段中,alice和white的关联性丢失了。这个文档可能错误地匹配到关于alice和smith的查询。
(2)对一组对象使用嵌套字段
如果需要对一组对象进行索引而且保留数组中每个对象的独立性,可以使用嵌套数据类型而不是对象数据类型。本质上,嵌套对象将数组中的每个对象作为分离出来的隐藏文档进行索引。这也意味着每个嵌套对象可以独立于其他对象被查询,示例如下。
创建的映射如下所示:
PUT PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"user": {"type": "nested"}
}
}
}
}
插入的数据如下所示:
PUT http://127.0.0.1:9200/secisland/secilog/1
{
"group": "fans",
"user": [
{"first": "John", "last": "Smith"},
{"first": "Alice", "last": "White"}
]
}
可以根据对象进行搜索,但对象的条件要全匹配才能搜到,例如搜索1如下所示:
POST http://127.0.0.1:9200/secisland/secilog/_search
{
"query": {
"nested": {
"path": "user",
"query": {
"bool": {
"must": [
{"match": { "user.first": "Alice}},
{"match": { "user.last": "Smith"}}
]
}
}
}
}
}
搜索2如下所示:
POST http://127.0.0.1:9200/secisland/secilog/_search
{
"query": {
"nested": {
"path": "user",
"query": {
"bool": {
"must": [
{"match": { "user.first": "Alice}},
{"match": { "user.last": "White"}}
]
}
}
}
}
}
user字段作为嵌套类型添加到索引中。搜索1匹配不到结果,因为Alice和Smith不在同一个嵌套对象中;搜索2匹配到搜索结果,因为Alice和White在同一个嵌套对象中。
嵌套数据类型的字段参数如下所示:
- dynamic——定义新的参数是否应该动态加入到已经存在的对象中。接受true(默认),false和strict。
- include_in_all——设置所有嵌套对象属性的include_in_all值。嵌套文档没有它们自身的_all字段,取而代之的是,值被添加到“根”文档的_all 字段中。
- properties——嵌套对象的字段可以使任何数据类型,包括嵌套对象类型。新的属性可以被添加到已经存在的嵌套对象中。
3.2.3 地理数据类型
1. 地理点数据类型
地理点数据类型字段接受经纬度对,可用于:
- 查找一定范围内的地理点,这个范围可以是相对于一个中心点的固定距离,也可以是多边形或者地理散列单元。
- 通过地理位置或者相对于中心点的距离聚合文档。
- 整合距离到文档的相关性评分中。
- 通过距离对文档进行排序。
指定字段类型为地理位置数据类型:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"location": {"type": "geo_point}
}
}
}
}
存储地理位置数据有4种不同方式。下面分别介绍。
(1)请求:
PUT http://127.0.0.1:9200/secisland/secilog/1
{
"text": "Geo-point as an object",
"location": {"lat": 41.12, "lon": -71.34}
}
地理点参数形如对象参数,拥有纬度和经度键值对。
(2)请求
PUT http://127.0.0.1:9200/secisland/secilog/2
{
"text": "Geo-point as an string",
"location": "41.12,-71.34"
}
字符串地理点参数的格式为“纬度,经度”。
(3)请求
PUT http://127.0.0.1:9200/secisland/secilog/3
{
"text": "Geo-point as an geohash",
"location": "drm3btev3e86"
}
散列地理点参数。
(4)请求
PUT http://127.0.0.1:9200/secisland/secilog/4
{
"text": "Geo-point as an array",
"location": [-71.34, 41.12]
}
地理点数组参数,格式为[经度,纬度]。
注意:字符串地理点参数顺序为(纬度,经度),地理点数组参数的顺序为(经度,纬度)。地理点数据字段参数见表3-6。
表3-6 地理点字段参数
参数 | 说明 |
---|---|
coere | 基于标准的-180:180/-90:90坐标系统的经度和纬度值。接受true和false(默认) |
doc_values | 定义字段是否应该以列跨度的方式存储在磁盘上,以便用于排序、聚合或者脚本。接受true(默认)或false参数 |
geohash | 定义地理点是否应该作为地理散列值在子字段.geohash中被索引。默认为false,除非geohash_prefix参数值为true |
geohash_precision | 用于geohash和geohash_prefix选项的地理散列最大长度 |
geohash_prefix | 定义地理点是否应该作为添加前缀的地理散列来进行索引。默认值为false |
ingore_malformed | 如果是true,畸形的地理点会被忽略。如果是false(默认),畸形地理点会抛出异常并丢弃整个文档 |
lat_lon | 定义地理点是否应该在子字段.lat和.lon中被索引到。接受true和false(默认) |
precision_step | 控制每个经纬点被索引的额外索引词的数量。默认值为16。与lat_lon参数的值无关 |
2. 地理形状数据类型
地理形状数据类型有利于索引和搜索任意地理形状,例如矩形和多边形。无论是数据被索引还是在查询执行的过程中,都可以使用地理形状数据类型在地理点的基础上包含地理形状。
利用地理形状查询(geo_shape)来查询文档,可以使用地理形状数据类型。
(1)映射选项
地理形状类型映射将geo_json几何对象映射成地理形状类型。为了启用映射选项,需要明确映射字段为地理形状类型,参见表3-7。
表3-7 地理形状数据类型映射选项
选项 | 描述 |
---|---|
tree | 引用的前缀树名:geohash或quadtree |
precision | 这个参数可以用来代替tree_levels来设置一个适当的tree_levels参数值。指定一个适当的精确度,Elasticsearch会计算匹配精确度的最佳tree_levels值。这个值应该是一个数字,后面跟着一个可选的距离单元。可用的距离单元包括:in、inch、yd、yard、mi、miled、km、kilometers、m、meters、cm、centimeters、mm、millimeters |
tree_levels | 前缀树使用的层的最大数量。可以用来控制形状表示的精度以及因此索引的索引词数量。默认是选取前缀树引用的默认值。因为这个参数需要一定程度的底层实现的理解,可以使用precision参数进行替代。然而,即使使用precision参数,Elasticsearch本质上只会使用并通过映射接口返回tree_levels |
strategy | 定义了如何在索引和搜索时表示形状。会影响可用的功能,所以建议让Elasticsearch自动设置这个参数。有两种可用的值:recursive和term。term仅支持点类型(points_only参数会自动设置true),recursive支持所有的形状类型 |
distance_error_pct | 用于示意前缀树应该使用的精确值。默认是0.025(2.5%),最大支持的值为0.5 |
orientation | 定义了如何解读多边形/多边形集合定点的顺序。这个参数定义两种坐标系统规则(右手或左手)中的一个,每一种坐标系统可以用三种方式进行指定。1.右手规则:right、ccw、counterclockwise,2.左手规则:left、cw、clockwise。默认方向(counterclockwise)遵循OGC标准,定义了外环顶点按照逆时针的方向内环顶点按顺时针方向。在映射中对地理形状字段设置这个参数,可以明确设置坐标列表中的顶点顺序。 |
points_only | 设置这个选项为true(默认为false),只对点形状配置地理形状字段类型(不支持多个点) |
(2)前缀树
在索引中高效地表示形状,形状被转换到一系列表示为方格(通常被称为“栅格”)的散列用于实现一个前缀树。树的概念来自该前缀树使用多层网络,每层增加精度级别来表示陆地。这可以提高地图的缩放级别或图像的细节水平。
(3)空间策略
前缀树的实现基于空间策略用来分解提供的形状为近似方格,每个策略解决这些问题:
- 哪种类型的形状可以被索引。
- 形状可以用于哪种类型的查询操作。
- 每个字段是否可以保存多个形状。
提供的这些策略实现(具有相应的功能)见表3-8。
表3-8 空间策略
策略 | 支持的形状 | 支持的查询 | 多形状 |
---|---|---|---|
recursive | 所有 | INTERSECTS、DISJOINT、WITHIN、CONTAINS | 是 |
term | 点 | INTERSECTS | 是 |
(4)准确性
地理形状不提供100%的准确性,并且取决于匹配值,可能对确定的查询返回一些误判或漏判的结果。为了缓和这个问题,需要为tree_levels参数选择一个合适的值来使用相应的预期。例如:
{
"properties": {
"location": {
"type": "geo_shape",
"tree": "quadtree",
"precision": "1m"
}
}
}
这个映射将location字段映射到地理形状类型,使用quad前缀树并且精度1m。Elasticsearch转换这个精度到tree_levels设置为26。
(5)性能方面的考虑
Elasticsearch使用前缀树中的路径作为索引和查询中的索引词。更高级别的精确值,会生成更多的索引词。计算索引词、加载到内存、保存的磁盘也需要额外的性能花销。
索引大小和合理水平的精确值的折中是50m。
(6)输入结构
用于表示形状的GeoJSON格式见表3-9:
表3-9 GeoJSON格式
GeoJSON类型 | Elasticsearch类型 | 描述 |
---|---|---|
Point | point | 单独地理坐标点 |
LineString | linestring | 一个任意的线,给出两个或更多的点 |
Polygon | polygon | 一个封闭的多边形,第一个点和最后一个点必须匹配,需要N+1个顶点创建一个N多边形,最少需要4个顶点 |
MultiPoint | multipoint | 一组不连续的但是可能相关的点 |
MultiLineString | multilinestring | 一组分离的线 |
MultiPolygon | multipolygon | 一组分离的多边形 |
GeometryCollection | geometrycollection | 一种类似于multi*形状的GeoJSON形状,多种类型可以共存(例如,一个点和一条线) |
N/A | envelope | 一个封闭的矩形,通过制定左上角和右下角确定 |
N/A | circle | 一个圆,通过制定圆心和带单位的半径来确定,默认单位为METERS |
下面举例说明这些格式。
Point是一个单独的地理坐标点,比如当前建筑的位置或者智能手机地理定位接口提供的确切位置。
{
"location": {
"type": "point",
"coordinates": [-77.03653, 38.897676]
}
}
Linestring通过两个或更多的一组位置定义。只指定两个点,Linestring会表示一条直线。指定更多的点,可以创建任意的线。
{
"location": {
"type": "linestring",
"coordinates": [[-77.03653, 38.897676], [-77.009051, 38.889939]]
}
}
Polygon通过一列地理点列表进行定义。每个列表(外环)中的第一个点和最后一个点必须相同(多边形必须是封闭的)。
{
"location": {
"type": "polygon",
"coordinates": [
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
]
}
}
第一个数组表示多边形的外环边界,其他数组表示内部形状(”孔“)。
{
"location": {
"type": "polygon",
"coordinates" : [
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],
[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]
]
}
}
GeoJSON不强制指定顶点的顺序,而在国际日期变更线和极点附近的多边形是极有可能造成混乱的。为了解决这种混乱,开放地理空间信息联盟(OGC)简单特征访问规范定义了这些定点顺序:
- 外环:逆时针方向。
- 内环、孔:顺时针方向。
对于不跨越国际日期变更线的多边形,顶点的顺序对于Elasticsearch来说是没有关系的。Elasticsearch完全按照OGC制定的规范请求顶点,否则,会创建一个意外的多边形,并且返回不符合预期的查询/过滤结果。
orientation参数可以在地里形状数据类型字段创建映射的时候进行定义,规定顶点的顺序。也可以在每个文档中进行重写:
{
"location": {
"type": "polygon",
"orientation": "clockwise",
"coordinates": [
[
[-177.0, 10.0], [176.0, 15.0], [172.0, 0.0], [176.0, -15.0], [-177.0, -10.0],
[-177.0, 10.0] ], [ [178.2, 8.2], [-178.8, 8.2], [-180.8, -8.8], [178.2, 8.8] ]
]
}
}
MultiPoint是GeoJSON点的列表,如下所示:
{
"location" :{"type":"multipoint", "coordinates": [[102.0, 2.0], [103.0, 2.0]]}
}
MultiLineString是GeoJSON线的列表,如下所示:
{
"location": {"type":"multilinestring", "coordinates": [
[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0] ],
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0] ],
[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8] ]
]}
}
MultiPolygon是GeoJSON多边形的列表,如下所示:
{
"location": {
"type": "multipolygon",
"coordinates": [
[ [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]] ],
[ [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] ,
[[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]
],
]
}
}
GeometryCollection是GeoJSON地理集合对象的集合,如下所示:
{
"location": {
"type": "geometrycollection",
"geometries": [
{"type": "point", "coordinates": [100.0, 0.0]},
{"type": "linestring", "coordinates": [ [100.0, 0.0], [102.0, 1.0] ]},
]
}
}
Envelope包含矩形左上角和右下角的坐标值来表示矩形,例如:
{
"location": {"type": "envelope",
"coordinates": [ [-45.0, 45.0], [45.0, -45.0] ]}
}
Circle包含圆心和半径:
{
"location": {
"type": "circle", "coordinates": [-45.0, 45.0], "radius": "100m"
}
}
注意,radius是必要字段。距离的单位默认是米(METERS)。
(7)排序和取回形状索引
由于形状复杂的输入结构和索引表示,当前不能直接通过字段排序或取回形状。地理形状值只能通过_source字段取回。
3.2.4 专门数据类型
1. IPv4数据类型
IPv4字段本质上是一个长整型字段,接受IPv4地址并作为长整型值进行索引。
添加映射:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"id_addr": {"type": "ip"}
}
}
}
}
插入数据:
PUT http://127.0.0.1:9200/secisland/secilog/1
{"ip_addr":"192.168.1.1"}
IPv4数据类型的字段参数见表3-10
表3-10 IP字段参数
参数 | 说明 |
---|---|
boost | 字段级索引加权,接受浮点型数字参数,默认为1.0 |
doc_values | 定义字段是否应该以列跨度的方式存储在磁盘上,以便用于排序、聚合或者脚本。接受true(默认)或false参数 |
include_in_all | 决定字段是否应该包含在_all字段中。接受true或false参数。如果索引设置为no或者父对象字段设置include_in_all为false,参数默认值为false;其他情况下,默认值为true |
index | 决定字段是否可以被用户搜索。接受参数not_analyzed(默认)以及no |
null_value | 接受一个IPv4值替换所有null值。默认为null,意味着字段作为缺失字段 |
precision_step | 控制索引的额外索引词的数量来使范围查询更快速。默认值为16 |
store | 决定字段值是否应该存储以及从_source字段分别获取。接受参数true或false(默认) |
注:IPv6地址现在还不提供支持
2. 单词计数数据类型
单词计数型字段本质上是一整数型字段,接受并分析字符串值,然后索引字符串中的单词的个数。下面举例说明。
添加映射:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"properties": {
"name": {
"type": "string",
"fields": {
"length": {"type":"token_count", "analyzer": "standard"}
}
}
}
}
}
}
插入数据:
PUT http://127.0.0.1:9200/secisland/secilog/1
{"name":"John Smith"}
严格来说,单词计数类型计算位置增量而不是统计单词。这意味着即使分析器过滤掉一部分单词,它们也会被包含在计数中。单词计数类型的字段参数如表3-11所示。
表3-11 单词计数型字段参数
参数 | 说明 |
---|---|
analyzer | 必要字段,定义用来分析字符串值的分析器 |
boost | 字段级索引加权,接受浮点型数字参数,默认为1.0 |
doc_values | 定义字段是否应该以列跨度的方式存储在磁盘上,以便用于排序、聚合或者脚本。接受true(默认)或false参数 |
include_in_all | 决定字段是否应该包含在_all字段中。接受true或false参数。如果索引设置为no或者父对象字段设置include_in_all为false,参数默认值为false;其他情况下,默认值为true |
index | 决定字段是否可以被用户搜索。接受参数not_analyzed(默认)以及no |
null_value | 接受一个与字段相同类型的数字型值替换所有null值。默认为null,意味着字段被作为缺失字段 |
precision_step | 控制索引的额外索引词的数量来使范围查询更快速。默认值为32 |
store | 决定字段值是否应该存储以及从_source字段分别获取。接受参数true或false(默认) |
3.3 元字段
每个文档都有与之关联的元数据,元字段是为了保证系统正常运转的内置字段,比如index表示索引字段,_type表示映射类型字段和_id表示文档主键字段,这些字段都是以下划线开头的。当映射类型被创建的时候,可以自定义一些元字段的行为,例如标识元字段、文档来源元字段、索引元字段、路由元字段等。参见表3-12到表3-16.
表3-12 标识元字段
参数 | 说明 |
---|---|
_index | 文档所属的索引 |
_uid | 包含_type和_id的混合字段 |
_type | 问的的映射类型 |
_id | 文档的ID |
表3-13 文档来源元字段
参数 | 说明 |
---|---|
_source | 作为文档内容的原始JSON |
_size | _source元字段占用的字节数,通过mapper-size插件提供 |
表3-14 索引元字段
参数 | 说明 |
---|---|
_all | 索引所有字段的值 |
_field_names | 文档中所有包含非空值的字段 |
_timestamp | 关联文章的时间戳,可以手动指定或者自动生成 |
_ttl | 定义文档被自动删除之前的存活时间 |
表3-15 路由元字段
参数 | 说明 |
---|---|
_parent | 用于在映射类型之间创建父子关系 |
_rounting | 一个自定义的路由值,路由文档到一个特定的分片 |
表3-16 其他元字段
参数 | 说明 |
---|---|
_meta | 应用特定的元字段 |
3.3.1 _all字段
_all字段是一个特殊的包含全部内容的字段,在一个大字符串中关联所有其他字段的值,使用空格作为分隔符。可以被分析和索引但不会被存储。使用_all字段可以对文档的值进行搜索而不必知道包含所需值的字段名。当面对一个新的数据集的时候,_all字段是非常有用的选项。
插入数据:
PUT http://127.0.0.1:9200/secisland/secilog/1
{
"first_name": "John",
"last_name": "Smith",
"date_of_birth": "1980-10-24"
}
利用_all字段进行搜索:
GET http://127.0.0.1:9200/secisland/_search
{
"query": {"match": {"_all": "john smith 1970"}}
}
_all字段包含的索引词:["john", "smith", "1970", "10", "24"]
注解:date_of_birth字段作为日期型字段,会索引一个索引词1970-10-24 00:00:00 UTC。但是,_all字段将所有的值作为字符串,所以日期值作为三个字符串被索引:"1970","10", "24"。
_all字段就是一个字符串字段,接受与字符串型字段相同的参数,包括analyzer,index_options和store。
_all字段关联字段值的时候,丢失了短字段(高相关性)和长字段(低相关性)之间的区别。当相关性是重要搜索条件的时候,应该明确指出查询字段。
_all字段的使用需要额外的处理器周期,并且耗费更多的磁盘空间。如果不需要的话,可以完全禁用或者在每个字段的基础上自定义。
3.3.2 _field_names字段
_field_names字段索引文档中所有包含非空值的字段名称。_field_names字段拥有存在查询和缺失查询的情况下,查找指定字段拥有非空值的文档是否存在。
_field_names字段的值可以用于查询、聚合以及脚本:
PUT http://127.0.0.1:9200/secisland/secilog/1
{"title": "This is a document"}
PUT http://127.0.0.1:9200/secisland/secilog/1
{"title": "This is another document", "body": "This document has a body"}
GET http://127.0.0.1:9200/secisland/_search
{
"query": {"terms": {"_field_names":["title"]}},
"aggs": {
"Field names": {
"terms": {"field": "_field_names", "size": 10}
}
},
"script_fields": {
"Field names": {"scripts": "doc['_field_names']" }
}
}
3.3.3 _id字段
每个被索引的文档都关联一个_type字段和一个_id字段。_id字段没有索引,它的值可以从_uid字段自动生成。
_id字段的值可以在查询以及脚本中访问,但是在聚合或者排序的时候,要使用_uid字段而不能用_id字段。
在查询和脚本中使用_id字段的示例如下:
GET http://127.0.0.1:9200/secisland/_search
{
"query": {
"terms": {"_id": ["1", "2"]}
},
"script_fields": {
"UID": {"script": "doc['_id']"}
}
}
3.3.4 _index字段
在多个索引中执行查询的时候,有时需要添加查询子句来关联特定的索引文档。_index字段可以匹配包含某个文档的索引。在term或terms查询、聚合、脚本以及排序的时候,可以访问_index字段的值。
_index是一个虚拟字段,不作为一个真实的字段添加到Lucene索引中。这意味着可以在term或terms查询(或任何重写term查询的查询,比如match、query_string或者simple_query_string查询)中使用_index字段,但是不支持prefix、wildcard、regexp或fuzzy查询。示例如下:
GET http://127.0.0.1:9200/index_1,index_2/_search
{
"query": {"terms": {"_index": ["index_1", "index_2"] }},
"aggs": {"indices": {"terms": {"field":"_index", "size": 10}}},
"sort": [{"_index": {"order":"asc}}],
"script_fields": {"index_name": {"script": "doc['_index']" }}
}
3.3.5 _meta字段
每个映射类型都可以拥有自定义的元数据。这些元数据对Elasticsearch来说毫无用处,但是可以用来存储应用程序的特定元数据:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"user": {
"_meta": {
"class": "MyApp::User,
"version": {"min": "1.0", "max":"1.3"}
}
}
}
}
3.3.6 _parent字段
在同一个索引中通过创建映射类型可以在文档间建立父子关系。
创建映射的代码如下:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"my_parent": {},
"my_child": {
"_parent": {"type": "my_parent"}
}
}
}
插入父文档的代码如下:
PUT http://127.0.0.1:9200/secisland/my_parent/1
{"text": "This is a parent document"}
插入子文档,并指出父文档的代码如下:
PUT http://127.0.0.1:9200/secisland/my_child/2?parent=1
{"text": "This is a child document"}
PUT http://127.0.0.1:9200/secisland/my_child/3?parent=1
{"text": "This is another child document"}
1. 父子限制
父类型和子类型必须是不同的,即父子关系不能建立在相同类型的文档之间。
_parent的type设置智能只想一个当前不存在的类型。这意味着一个类型被创建之后就无法成为父类型。
父子文档必须索引在相同的分片上。parent编号用于作为子文档的路由值,确保子文档被索引到父文档所在的分片中。这意味着当获取、删除或更新子文档的时候,需要提供相同的parent值。
2. 整体序数
使用整体序数可以加快建立父子关系。分片发生任何改变之后,整体序数都徐帅进行重建。分片中存储的父编码值越多,为_parent字段重建整体序数所花 的时间就越长。
整体序数在默认情况下属于懒创建:刷新之后的第一次父子查询或聚合会出发整体序数的创建,这可能会给用户的使用引入一个明显的延迟。可以使用参数将整体序数的创建时间由查询触发改到刷新触发:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"my_parent": {},
"my_child": {
"_parent": {
"type": "my_parent",
"fielddata": {"loading": "eager_global_ordinals"}
}
}
}
}
3.3.7 _routing字段
文档在索引中利用下面的公式路由到特定的分片:
shard_num = hash(_routing) % num_primary_shards
_routing字段的默认值使用的是文档的_id字段。如果存在父文档,则使用文档的_parent编号。
可以通过为每个文档指定一个自定义的路由值来实现自定义的路由方式:
PUT http://127.0.0.1:9200/secisland/secilog/1?routing=user1
{"title": "This is a document"}
这个文档使用user1作为路由值,而不是它的ID,在获取、删除和更新文档的时候需要提供相同的路由值。
_routing字段可以在查询、聚合、脚本以及排序的时候访问:
GET http://127.0.0.1:9200/secisland/_search
{
"query": {
"terms": {"_routing": ["user1] }
},
"aggs": {
"Routing values": {
"terms": {"field": "_routing", "size": 10}
}
},
"sort": [
{
"_routing": {"order": "desc}
}
],
"script_fields": {
"Routing value": {"script":"doc['_routing']" }
}
}
1. 利用自定义路由进行搜索
自定义路由可以降低搜索压力。搜索请求可以仅仅发送到匹配指定路由值的分片而不是广播到所有分片,如下所示:
GET http://127.0.0.1:9200/secisland/_search?routing=user1,user2
{
"query": {
"match": {"title": "document" }
}
}
搜索请求仅在关联路由值user1和user2的分片上执行。
2. 使路由值成为必选项
使用自定义路由索引、获取、删除或更新文档时,提供路由值是很重要的。
忘记路由值会导致文档被一个以上的分片索引。作为保障,_routing字段可以被设置,应使自定义路由值成为所有CRUD操作的必选项:
PUT http://127.0.0.1:9200/secisland
{
"mappings": {
"secilog": {
"_routing": {"required": true}
}
}
}