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": "[email protected]",
"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)。
以一張圖總結相關的知識點:
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. 核心數據類型
核心數據類型與我們常使用的強類型語言中的數據類型類似,可分爲以下幾類:
- 字符串型:包含text 、keyword。
-
數字類型:包含long , integer , short , byte , double , float , half_float , scaled_float。這裏偷個懶,各數字類型參見參考文獻1中的截圖描述。
- 日期類型:date。可表示爲字符串類型,如"2015-01-01" or "2015/01/01 12:10:30",也可表示爲秒或毫秒的整數。
- 布爾類型:boolean。真值可用true(對應json中的布爾值),“true”表示。
- 二進制類型:binary。值用以base64編碼的字符串表示。
- 區間類型:包含integer_range , float_range , long_range , double_range , date_range。
對於數字區間類型,示例如下:
//類型爲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等類型。
- Array類型:在ES中,沒有專門的“array”類型用於顯式定義,任何域都可以包含零個或多個值,即任何域都可以是一個數組,不過不同於JSON中的數組,ES中的數組各元素必須爲相同類型。
- Object類型:JSON對象可以是一個嵌套結構,在現實應用中我們常常使用這種嵌套結構來表達對象之間的層次關係。
我們看看下面這個映射的定義:
"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作爲域來進行查詢。
- nested類型:是object類型的延伸,主要用於對象數組。仍以參考文獻1中的示例:
對於對象:
"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的應用,包括:
- geo_point類型:可用於存儲某個地理座標的經緯度。示例如下:
// location爲geo_point類型
"location": {
"lat": 41.12,
"lon": -71.34
}
- geo_shape類型:用於存儲地理多邊形形狀,有興趣的讀者可以參考文獻1。
3.4. 特殊數據類型
特殊數據類型包括:
- ip類型:用於表示IPv4與IPv6的地址
- completion類型:提供自動輸入關聯完成功能,如常見的baidu搜索框。
- token_count類型:用於計算字符串token的長度,使用時需提供"analyzer"定義。
- percolate類型:定義爲percolate類型的字段會被ES分析爲一個查詢並保存下來,並可用在後繼對文檔的查詢中。Percolate可以理解爲一個預置的查詢。
- alias類型:定義一個已存在域的別名。
- join類型:該類型定義了文檔對象之間的父子關係(可定義多層,形成一顆層次樹),即同一索引中多個文檔對象可以存在依賴關係,如互聯網應用常見的博客文章與回覆,問題與回答之間的關係。參看文獻1中的如下示例:
定義映射字段:
{
"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類型定義了文檔之間的父子依賴關係,在查詢和聚合操作中可使用這種依賴關係。
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 |
總結一下:
- 與域數據格式及約束相關的參數:normalizer,format,ignore_above,ignore_malformed,coerce
- 與索引相關的參數:index,dynamic,enabled
- 與存儲策略相關的參數:store, fielddata,doc_values
- 分析器相關參數:analyzer,search_analyzer
- 其它參數:boost,copy_to,null_value
對於這些參數的描述主要基於筆者的理解,可能有不準確之處。實際上這些參數與ES的實現機制(如存儲結構,索引結構密切有關),只能在實際應用中去慢慢體會。
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中定義了視頻圖像信息數據庫的數據模型,該數據模型中定義了三十多個領域對象,對象之間具有關聯關係。視圖庫中的對象定義主要包含以下特徵:
- 視圖庫中每個對象都定義了唯一ID用於標識。
- 對象之間存在父子(或因果)關係,關聯方式爲:子對象中包含父對象的ID。如車道對象包含對應卡口的ID;告警對象包含布控對象的ID;人、車、物對象包含其對應的來源圖像ID和採集設備ID。
- 對象之中嵌套子對象:如人、車、物對象中都包含子圖像列表對象。
總的來說,視圖庫模型中的數據對象間的關聯關係比較簡單,也相對獨立。
視圖庫中的對象的屬性(字段)被約束爲三種類型:R(必選),R/O(條件可選,當滿足某個條件時該屬性必須存在),O(可選)。
不同於RDBMS,ES在映射對象的屬性時並沒有標明值是否不爲空的選項,因而需要在提交對象數據到ES前進行數據的有效性檢驗。在設計基於ES的視圖庫架構設計時,這樣的數據有效性檢驗服務(模塊)可放在API網關(或負載均衡網關)與ES之間,並根據實際場景的效率和完整性要求決定是否啓用。
5.2. GA/T 1400.3的數據類型分析
視圖庫規範定義對象字段的數據類型可爲:
- 基礎數據類型:包含字符串,整型數,長整型數,浮點數,日期時間,數組,對象。可與ES的數據類型對應。
- 擴展數據類型:定義了領域知識,對基礎數據類型做了一些約束形成擴展數據類型。如道路類型(SceneType)定義爲使用最大長度爲2的字符串表示的枚舉類型。
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. 參考文獻
- https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
- Clinton Gormley &Zachary Tong, Elasticsearch: The Definitive Guide,2015
- GA/T 1400.3 公安視頻圖像信息應用系統 第3部分:數據庫技術要求,2017
本系列文章:
編程隨筆-ElasticSearch知識導圖(1):全景
編程隨筆-ElasticSearch知識導圖(2):分佈式架構
編程隨筆-ElasticSearch知識導圖(3):映射
編程隨筆-ElasticSearch知識導圖(4):搜索
編程隨筆-ElasticSearch知識導圖(5):聚合
編程隨筆-ElasticSearch知識導圖(6):管理