ES中的映射(Mapping)实质上就是对文档对象结构的定义,也即对文档中各元素的描述。在ES中定义映射,就如同定义XML文档的XML Schema。
ES中的映射定义了文档模式(就如同在关系数据库中定义了关系模式),文档模式确定了存在ES中的文档的格式,结构和字段的数据类型。通过查看某个索引的映射可以了解文档的结构,以便使用查询语言(Query DSL)构建更符合我们要求的查询命令。
让我们首先看一下如下关于银行账号的文档示例:
{
"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)。
以一张图总结相关的知识点:
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 | 特殊数据类型 |
核心数据类型与我们常使用的强类型语言中的数据类型类似,可分为以下几类:
//类型为integer_range
"expected_attendees" : {
"gte" : 10,
"lte" : 20
}
对于日期区间类型,示例如下:
//类型为date_range
"time_frame" : {
"gte" : "2015-10-31 12:00:00",
"lte" : "2015-11-01"
}
复杂数据类型可用于表达对象之间的语义,包含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" }}
]
}
}
}
}
}
地理数据类型可用于LBS的应用,包括:
// location为geo_point类型
"location": {
"lat": 41.12,
"lon": -71.34
}
特殊数据类型包括:
{
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
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类型定义了文档之间的父子依赖关系,在查询和聚合操作中可使用这种依赖关系。
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 |
总结一下:
在ES中设计一个索引的映射和在关系数据库中设计关系模式,ER模型,在XML中设计XML Schema一样。需要完整包含领域知识并满足数据之间的约束。
在这一节中我们探讨一个使用ES构建视频图像信息数据库的实例。
视频图像信息数据库(以下简称视图库)基于GA/T 1400.3 标准定义,用于存储视频、图像等基本对象(二进制数据)和由这些基本对象分析(可自动)出的属性对象。
在GA/T 1400.4中,定义了访问视频图像信息数据库的接口,这些接口以基于HTTP的restful形式定义,以JSON格式传输数据。因而使用ES作为视频图像信息数据库的存储容器可以利用ES的JSON文档对象存储和。
在GA/T 1400.3中定义了视频图像信息数据库的数据模型,该数据模型中定义了三十多个领域对象,对象之间具有关联关系。视图库中的对象定义主要包含以下特征:
视图库规范定义对象字段的数据类型可为:
以视图库中的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 <br> doc_values:false <br>ignore_above : 41 | 不参与排序与聚合 |
InfoKind | int | type: integer<br>coerce: false | |
Source | string(2) | type:keyword<br> ignore_above : 2 | |
FileName | string(0..256) | type:keyword<br>ignore_above : 256 | |
StoragePath | string(256) | type:keyword<br>doc_values:false<br>ignore_above : 256 | 不参与排序与聚合 |
FileHash | string(32) | type:keyword<br>doc_values:false<br> ignore_above : 32 | 不参与排序与聚合 |
FileFormat | string(32) | type:keyword<br>ignore_above : 32 | |
Title | string(128) | type:keyword <br>ignore_above : 128 | |
SecurityLevel | String(1) | type:keyword<br> ignore_above : 1 | |
SubmiterName | string(0..50) | type:keyword <br>ignore_above : 50 | |
SubmiterOrg | string(0..100) | type:keyword <br>ignore_above : 100 | |
EntryTime | dateTime | type: date <br>format:yyyyMMddHHmmss | 格式为:YYYYMMDDhhmmss |
FileSize | int | type: integer <br> coerce: false |
我们使用如下命令在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
}
}
'
视图库中对象的字段不用进行全文检索,也可以使用关系数据库作为存储容器,但需要对JSON数据进行反序列化解析相应字段入库,查询出库时需要将多个字段序列化为JSON数据。固然在编程时可以使用ORM和JSON序列化中间件来完成工作,但在海量请求下,效率会有影响。使用ES可以利用ES的restful接口和JSON存储格式的天然特性以契合规范要求。
在视图库规范中有一些自定义的约束,这些涉及数据有效性检验的服务应该部署在ES入库之前。在本实例中,更多的是把ES作为一个Nosql数据库使用。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。