@IT·互联网Elastic SearchElastic Stack

编程随笔-ElasticSearch知识导图(3):映射

2019-02-18  本文已影响23人  简单是美美

1. 啥是映射

  ES中的映射(Mapping)实质上就是对文档对象结构的定义,也即对文档中各元素的描述。在ES中定义映射,就如同定义XML文档的XML Schema。
  ES中的映射定义了文档模式(就如同在关系数据库中定义了关系模式),文档模式确定了存在ES中的文档的格式,结构和字段的数据类型。通过查看某个索引的映射可以了解文档的结构,以便使用查询语言(Query DSL)构建更符合我们要求的查询命令。

2. 从一个示例开始

  让我们首先看一下如下关于银行账号的文档示例:

{
    "account_number": 1,
    "balance": 39225,
    "firstname": "Amber",
    "lastname": "Duke",
    "age": 32,
    "gender": "M",
    "address": "880 Holmes Lane",
    "employer": "Pyrami",
    "email": "amberduke@pyrami.com",
    "city": "Brogan",
    "state": "IL"
}

  ES对该文档的自动生成的映射是下面这个样子的:

{
    "bank": {
        "mappings": {
            "account": {
                "properties": {
                    "account_number": {
                        "type": "long"
                    },
                    "address": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "age": {
                        "type": "long"
                    },
                    "balance": {
                        "type": "long"
                    },
                    "city": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "email": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "employer": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "firstname": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "gender": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "lastname": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "state": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    }
                }
            }
        }
    }
}

  由这个自动生成的映射可以看到:ES自动将account_number、balance、age这些属性映射为long类型,其它的属性都映射为text类型。text类型的属性常用于全文搜索,但是并不进入内存中索引,因此text类型并不可用于聚合和排序(系统会报错:"Fielddata is disabled on text fields by default. Set fielddata=true on [address] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead.")。
  ES允许为一个对象属性定义多个域(fields),每个域是该属性的一个facet(我思考很久,还是觉得这个词最合适),如“address”属性类型为text,为它定义 一个域为keyword,该域的类型为“keyword”,不会被分析器(analyzer)分析,可用于排序、聚合和精确查找(请注意ignore_above这个属性,限制了用于keyword的有效字符数目)。
  在DSL查询语言中查询时,使用“address”时,经分析器分析后,"880 Holmes Lane"可能被分解为“880”,“Holmes”,“Lane”进入全文搜索。看看下面的两个查询命令:

curl -iXGET 'localhost:9200/bank/_search?pretty' -H 'Content-Type: application/json'  -d'
{"query":
    {"match":
       {"address.keyword":"880 Holmes Lane"}
    }
}'

  查询出来只有一个结果,精确匹配“880 Holmes Lane”。

curl -iXGET 'localhost:9200/bank/_search?pretty' -H 'Content-Type: application/json'  -d'
{"query":
    {"match":
       {"address":"880 Holmes Lane"}
    }
}'

  查询出来多个结果,查询条件“880 Holmes Lane”被分析器分析后检索,"591 Nolans Lane"也被检索出来(其中包含了一个分析器分解后的Lane)。
  以一张图总结相关的知识点:


图1

3. 域的数据类型

  ES中域的主要数据类型如下表所示,还可由一些插件扩展数据类型(这里不赘述了):

数据类型 分类
text , keyword 字符串
long , integer , short , byte , double , float , half_float , scaled_float 数字
date 日期
boolean 布尔
binary 二进制
integer_range , float_range , long_range , double_range , date_range 区间类型
Array, object, nested 复杂数据类型
geo_point, geo_shape 地理数据类型
binary 二进制
ip, completion,token_count,percolator,join, alias 特殊数据类型

3.1. 核心数据类型

  核心数据类型与我们常使用的强类型语言中的数据类型类似,可分为以下几类:

 //类型为integer_range
"expected_attendees" : { 
    "gte" : 10,
    "lte" : 20
  }

  对于日期区间类型,示例如下:

   //类型为date_range
"time_frame" : { 
    "gte" : "2015-10-31 12:00:00", 
    "lte" : "2015-11-01"
  }

3.2. 复杂数据类型

  复杂数据类型可用于表达对象之间的语义,包含Array, object, nested等类型。

"manager": { 
          "properties": {
            "age":  { "type": "integer" },
            "name": { 
              "properties": {
                "first": { "type": "text" },
                "last":  { "type": "text" }
              }
            }
          }
        }

  其对应的对象为:

"manager": { 
    "age":     30,
    "name": { 
      "first": "John",
      "last":  "Smith"
    }
  }

  其中域manager就是一个对象类型,其中的name是它的子对象。对于对象类型,缺省设置“type”为”object”,因此不用显式定义“type”。
  对于上面的对象类型,ES在索引时将其转换为"manager.age", "manager.name.first" 这样扁平的key,因此查询时也可以使用这样的扁平key作为域来进行查询。

"user" : [ 
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]

  如果使用动态映射,会被ES索引为如下形式:

"user.first" : [ "alice", "john" ],
"user.last" :  [ "smith", "white" ]

  这样的索引形式在查询时会丢失对象中”first”与“last”之间的关联关系。
  如果将user映射为如下形式:

"user": {
          "type": "nested" 
        }

  ES在索引时会保留对象域之间的关联关系,在查询时找对正确的对象。
  如使用如下查询则找不到任何命中对象(不存在“Alice Smith”这个对象):

{
  "query": {
    "nested": {
      "path": "user",
      "query": {
        "bool": {
          "must": [
            { "match": { "user.first": "Alice" }},
            { "match": { "user.last":  "Smith" }} 
          ]
        }
      }
    }
  }
}

3.3. 地理数据类型

  地理数据类型可用于LBS的应用,包括:

       // location为geo_point类型
"location": { 
    "lat": 41.12,
    "lon": -71.34
  }

3.4. 特殊数据类型

  特殊数据类型包括:

{
  "text": "This is an answer",
  "my_join_field": {
    "name": "answer", 
    "parent": "1" 
  }
}

  my_join_field定义了"question"与"answer"之间关系为父子关系。
  观察对于该映射的一个文档实例,路径为“my_index/_doc/1”:

{
  "text": "This is a question",
  "my_join_field": "question" 
}

  该文档的一个子文档对象示例如下,在my_join_field需要定义父亲的ID(这里根据上面的父实例,为1):

{
  "text": "This is an answer",
  "my_join_field": {
    "name": "answer", 
    "parent": "1" 
  }
}

  需要注意的是,一个父文档可以有多个子文档,父子文档应部署在同一个分片上。因而在向ES提交父子文档时,应在URI中使用相同的routing参数。
  join类型定义了文档之间的父子依赖关系,在查询和聚合操作中可使用这种依赖关系。

4. 映射参数

  JSON是JS对象序列化的字符串,ES接收一个JSON字符串形式的文档对象,本质上是存入一个JS对象,JS定义了对象,数组,字符串,数字,布尔型和null等数据类型。
  ES中的域数据类型可视为对JS对象数据类型的扩展,如join,区间类型等都表示为js对象。
  在定义域映射时,ES定义了相关的映射参数,这里简单列举并描述,详细信息可以查看文献1。

参数 描述
analyzer 定义对文本数据的分析器
normalizer 对文本数据规范化
boost 用于提升字段搜索的权重
coerce 当为false时,强制输入值必须符合映射的域数据类型
copy_to 将当前域的值复制到另一个域中
doc_values 当该域不参与排序域聚合操作时,可设置为false使得不在磁盘上存储Doc value(以列式存储的文档值)以节约磁盘空间。缺省为true
dynamic 该参数控制在对象中检测到的新的域(未在映射中定义)是否加入到域中,当为false或strict时,新域不会加入到映射中。缺省为true
enabled 主要应用于object类型的域,当设置为false,该域不被索引。缺省为true
fielddata 对于text类型的域,如果该参数设置为true,该域的数据在第一次使用时会载入常驻于内存中。缺省为false
format 定义域数据的格式,用于日期类型
ignore_above 定义字符串的有效长度
ignore_malformed 如果设置为true,当字段值与映射定义不一致时,不会抛出错误。缺省为false
index 缺省为true,当设置为false时,该域不被索引,不可被搜索
null_value 定义该域为空值时的格式,如使用“NULL”这样的字符串
search_analyzer 定义搜索时的analyzer,可与定义映射时使用的analyzer不同
store 该值设置为true时,当前域的原始值也存储下来(在_source之外)。默认为false

  总结一下:

5. 一个设计实例

  在ES中设计一个索引的映射和在关系数据库中设计关系模式,ER模型,在XML中设计XML Schema一样。需要完整包含领域知识并满足数据之间的约束。
  在这一节中我们探讨一个使用ES构建视频图像信息数据库的实例。

5.1. GA/T 1400.3的数据模型分析

  视频图像信息数据库(以下简称视图库)基于GA/T 1400.3 标准定义,用于存储视频、图像等基本对象(二进制数据)和由这些基本对象分析(可自动)出的属性对象。
  在GA/T 1400.4中,定义了访问视频图像信息数据库的接口,这些接口以基于HTTP的restful形式定义,以JSON格式传输数据。因而使用ES作为视频图像信息数据库的存储容器可以利用ES的JSON文档对象存储和。
  在GA/T 1400.3中定义了视频图像信息数据库的数据模型,该数据模型中定义了三十多个领域对象,对象之间具有关联关系。视图库中的对象定义主要包含以下特征:

5.2. GA/T 1400.3的数据类型分析

  视图库规范定义对象字段的数据类型可为:

5.3. File对象的映射分析

  以视图库中的File对象(GA/T 1400.3附录A.7)为例,我们看看如何定义它的映射。
  在GA/T 1400.3中,它的XML Schema是这样定义的:

<complexType name="File">
<sequence>
<element name="FileID" type=" BasicObjectIdType"/>
<element name="InfoKind" type="InfoType" use="required"/>
<element name="Source" type="DataSourceType" use="required"/>
<element name="FileName" type="FileNameType" use="required"/>
<element name="StoragePath" type="string" />
<element name="FileHash" type="string" use="required"/>
<element name="FileFormat" type="string" use="required"/>
<element name="Title" type="string" use="required"/>
<element name="SecurityLevel" type="SecretLevelType" />
<element name="SubmiterName" type="NameType" />
<element name="SubmiterOrg" type="string" />
<element name="EntryTime" type="dateTime" />
<element name="FileSize" type="int"/>
</sequence>
</complexType>

  一个文件对象的对象实例如下所示。

{
    "FileObject": {
        "FileID": "31000000001190000138022019021416121100001",
        "InfoKind": 1, 
        "Source": "3",  
        "FileName": "tollgate_3_lane_4_20190214161211.jpg",
        "StoragePath": "/tollgate/3/lane/4/images",
        "FileHash": "38b8c2c1093dd0fec383a9d9ac940515",
        "FileFormat": "Jpeg",
        "Title": "tollgate_3_lane_4_20190214161211",
        "SecurityLevel": "3",
        "SubmiterName": "zhangkai",
        "SubmiterOrg": "pudong",
        "EntryTime": "20190214161214",
        "FileSize": 94208
    }
}

  分析该对象中的各属性字段,整理出下表:

字段名称 标准中的数据类型定义 ES中对应类型 备注
FileID string(41) type:keyword
doc_values:false
ignore_above : 41
不参与排序与聚合
InfoKind int type: integer
coerce: false
Source string(2) type:keyword
ignore_above : 2
FileName string(0..256) type:keyword
ignore_above : 256
StoragePath string(256) type:keyword
doc_values:false
ignore_above : 256
不参与排序与聚合
FileHash string(32) type:keyword
doc_values:false
ignore_above : 32
不参与排序与聚合
FileFormat string(32) type:keyword
ignore_above : 32
Title string(128) type:keyword
ignore_above : 128
SecurityLevel String(1) type:keyword
ignore_above : 1
SubmiterName string(0..50) type:keyword
ignore_above : 50
SubmiterOrg string(0..100) type:keyword
ignore_above : 100
EntryTime dateTime type: date
format:yyyyMMddHHmmss
格式为:YYYYMMDDhhmmss
FileSize int type: integer
coerce: false

5.4. File对象的映射定义

  我们使用如下命令在ES中创建索引file(注意这里的index.mapping.coerce被设置为false):

curl -iXPUT 'localhost:9200/file?pretty' -H "Content-type: application/json" -d' 
{
  "settings": {
     "number_of_shards":3,
     "number_of_replicas":1,
     "index.mapping.coerce": false
  }
}
'

  使用如下命令修改file索引的映射:

curl -iXPUT 'localhost:9200/file/_mapping/object?pretty' -H "Content-type: application/json" -d'
{
    "properties": {
        "FileObject": {
            "properties": {
                "FileID": {
                    "type": "keyword",
                    "doc_values": false,
                    "ignore_above": 41
                },
                "InfoKind": {
                    "type": "integer",
                    "coerce": false
                },
                "Source": {
                    "type": "keyword",
                    "ignore_above": 2
                },
                "FileName": {
                    "type": "keyword",
                    "ignore_above": 256
                },
                "StoragePath": {
                    "type": "keyword",
                    "doc_values": false,
                    "ignore_above": 256
                },
                "FileHash": {
                    "type": "keyword",
                    "doc_values": false,
                    "ignore_above": 32
                },
                "FileFormat": {
                    "type": "keyword",
                    "ignore_above": 32
                },
                "Title": {
                    "type": "keyword",
                    "ignore_above": 128
                },
                "SecurityLevel": {
                    "type": "keyword",
                    "ignore_above": 1
                },
                "SubmiterName": {
                    "type": "keyword",
                    "ignore_above": 50
                },
                "SubmiterOrg": {
                    "type": "keyword",
                    "ignore_above": 100
                },
                "EntryTime": {
                    "type": "date",
                    "format": "yyyyMMddHHmmss"
                },
                "FileSize": {
                    "type": "integer",
                    "coerce": false
                }
            }
        }
    }
}
'

  使用如下命令查看file的映射信息:

curl -iXGET 'localhost:9200/file/_mapping?pretty'

  可以看到返回的映射信息:

{
  "file" : {
    "mappings" : {
      "object" : {
        "properties" : {
          "FileObject" : {
            "properties" : {
              "EntryTime" : {
                "type" : "date",
                "format" : "yyyyMMddHHmmss"
              },
              "FileFormat" : {
                "type" : "keyword",
                "ignore_above" : 32
              },
              "FileHash" : {
                "type" : "keyword",
                "doc_values" : false,
                "ignore_above" : 32
              },
              "FileID" : {
                "type" : "keyword",
                "doc_values" : false,
                "ignore_above" : 41
              },
              "FileName" : {
                "type" : "keyword",
                "ignore_above" : 256
              },
              "FileSize" : {
                "type" : "integer",
                "coerce" : false
              },
              "InfoKind" : {
                "type" : "integer",
                "coerce" : false
              },
              "SecurityLevel" : {
                "type" : "keyword",
                "ignore_above" : 1
              },
              "Source" : {
                "type" : "keyword",
                "ignore_above" : 2
              },
              "StoragePath" : {
                "type" : "keyword",
                "doc_values" : false,
                "ignore_above" : 256
              },
              "SubmiterName" : {
                "type" : "keyword",
                "ignore_above" : 50
              },
              "SubmiterOrg" : {
                "type" : "keyword",
                "ignore_above" : 100
              },
              "Title" : {
                "type" : "keyword",
                "ignore_above" : 128
              }
            }
          }
        }
      }
    }
  }
}

  现在我们可以向file索引提交数据对象了,使用如下命令:

curl -iXPOST 'localhost:9200/file/object/31000000001190000138022019021416121100001?pretty'  -H "Content-type: application/json" -d'
{
    "FileObject": {
        "FileID": "31000000001190000138022019021416121100001",
        "InfoKind": 1, 
        "Source": "3",  
        "FileName": "tollgate_3_lane_4_20190214161211.jpg",
        "StoragePath": "/tollgate/3/lane/4/images",
        "FileHash": "38b8c2c1093dd0fec383a9d9ac940515",
        "FileFormat": "Jpeg",
        "Title": "tollgate_3_lane_4_20190214161211",
        "SecurityLevel": "3",
        "SubmiterName": "zhangkai",
        "SubmiterOrg": "pudong",
        "EntryTime": "20190214161214",
        "FileSize": 94208
    }
}
'

5.5. 小结

  视图库中对象的字段不用进行全文检索,也可以使用关系数据库作为存储容器,但需要对JSON数据进行反序列化解析相应字段入库,查询出库时需要将多个字段序列化为JSON数据。固然在编程时可以使用ORM和JSON序列化中间件来完成工作,但在海量请求下,效率会有影响。使用ES可以利用ES的restful接口和JSON存储格式的天然特性以契合规范要求。

  在视图库规范中有一些自定义的约束,这些涉及数据有效性检验的服务应该部署在ES入库之前。在本实例中,更多的是把ES作为一个Nosql数据库使用。

6. 参考文献

  1. https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

  2. Clinton Gormley &Zachary Tong, Elasticsearch: The Definitive Guide,2015

上一篇下一篇

猜你喜欢

热点阅读