一、Mapping概述
映射
為了能夠把日期字段處理成日期,把數字字段處理成數字,把字符串字段處理成全文本(Full-text)或精確的字符串值,Elasticsearch需要知道每個字段里面都包含了什么類型。這些類型和字段的信息存儲(包含)在映射(mapping)中。
正如《數據吞吐》一節所說,索引中每個文檔都有一個類型(type)。 每個類型擁有自己的映射(mapping)或者模式定義(schema definition)。一個映射定義了字段類型,每個字段的數據類型,以及字段被Elasticsearch處理的方式。映射還用於設置關聯到類型上的元數據。
這里只是入門。
例如,可以使用映射來定義:
- 字符串字段是否作為全文本搜索字段
- 哪些字段包含數字,日期或地理信息
- 文檔中所有字段的值是否應該被索引到_all字段
- 日期值的格式
- 自定義規則來控制動態添加的字段的映射
映射類型與type:即一個索引中有多個type,從邏輯上對文檔進行划分、每個索引有一個或多個映射類型,類型是對Document划分的邏輯組,索引中每個文檔都有一個類型(type),每個類型擁有自己的映射或者模式定義(schema definition) 。每個映射類型包括:
- 關聯到類型上的元數據,比如:_index, _type, _id, and _source
- 字段或屬性的定義,比如:字段類型,每個字段的數據類型,以及字段被ES處理的方式。
數據類型大致分類:
- 簡單類型,比如:string, date, long, double, boolean 或 ip等
- 嵌套對象
- 特殊類型,比如:geo_point, geo_shape, 或completion等
二、分詞器
這里的分詞器和lucene中的分詞器差不多。
lucene基礎:http://www.cnblogs.com/carl10086/p/6020379.html
分析和分析器
分析(analysis)是這樣一個過程:
- 首先,標記化一個文本塊為適用於倒排索引單獨的詞(term)
- 然后標准化這些詞為標准形式,提高它們的“可搜索性”或“查全率”
這個工作是分析器(analyzer)完成的。一個分析器(analyzer)只是一個包裝用於將三個功能放到一個包里:
功能1:字符過濾器
首先字符串經過字符過濾器(character filter),它們的工作是在標記化前處理字符串。字符過濾器能夠去除HTML標記,或者轉換
"&"
為"and"
。功能2:分詞器
下一步,分詞器(tokenizer)被標記化成獨立的詞。一個簡單的分詞器(tokenizer)可以根據空格或逗號將單詞分開(譯者注:這個在中文中不適用)。
功能3:標記過濾
最后,每個詞都通過所有標記過濾(token filters),它可以修改詞(例如將
"Quick"
轉為小寫),去掉詞(例如停用詞像"a"
、"and"
、"the"
等等),或者增加詞(例如同義詞像"jump"
和"leap"
)Elasticsearch提供很多開箱即用的字符過濾器,分詞器和標記過濾器。這些可以組合來創建自定義的分析器以應對不同的需求。我們將在《自定義分析器》章節詳細討論。
內建的分析器
不過,Elasticsearch還附帶了一些預裝的分析器,你可以直接使用它們。下面我們列出了最重要的幾個分析器,來演示這個字符串分詞后的表現差異:
"Set the shape to semi-transparent by calling set_trans(5)"
標准分析器
標准分析器是Elasticsearch默認使用的分析器。對於文本分析,它對於任何語言都是最佳選擇(譯者注:就是沒啥特殊需求,對於任何一個國家的語言,這個分析器就夠用了)。它根據Unicode Consortium的定義的單詞邊界(word boundaries)來切分文本,然后去掉大部分標點符號。最后,把所有詞轉為小寫。產生的結果為:
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
簡單分析器
簡單分析器將非單個字母的文本切分,然后把每個詞轉為小寫。產生的結果為:
set, the, shape, to, semi, transparent, by, calling, set, trans
空格分析器
空格分析器依據空格切分文本。它不轉換小寫。產生結果為:
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
語言分析器
特定語言分析器適用於很多語言。它們能夠考慮到特定語言的特性。例如,english
分析器自帶一套英語停用詞庫——像and
或the
這些與語義無關的通用詞。這些詞被移除后,因為語法規則的存在,英語單詞的主體含義依舊能被理解(譯者注:stem English words
這句不知道該如何翻譯,查了字典,我理解的大概意思應該是將英語語句比作一株植物,去掉無用的枝葉,主干依舊存在,停用詞好比枝葉,存在與否並不影響對這句話的理解。)。
english
分析器將會產生以下結果:
set, shape, semi, transpar, call, set_tran, 5
注意"transparent"
、"calling"
和"set_trans"
是如何轉為詞干的。
當分析器被使用
當我們索引(index)一個文檔,全文字段會被分析為單獨的詞來創建倒排索引。不過,當我們在全文字段搜索(search)時,我們要讓查詢字符串經過同樣的分析流程處理,以確保這些詞在索引中存在。
全文查詢我們將在稍后討論,理解每個字段是如何定義的,這樣才可以讓它們做正確的事:
- 當你查詢全文(full text)字段,查詢將使用相同的分析器來分析查詢字符串,以產生正確的詞列表。
- 當你查詢一個確切值(exact value)字段,查詢將不分析查詢字符串,但是你可以自己指定。
現在你可以明白為什么《映射和分析》的開頭會產生那種結果:
date
字段包含一個確切值:單獨的一個詞"2014-09-15"
。_all
字段是一個全文字段,所以分析過程將日期轉為三個詞:"2014"
、"09"
和"15"
。
當我們在_all
字段查詢2014
,它一個匹配到12條推文,因為這些推文都包含詞2014
:
GET /_search?q=2014 # 12 results
當我們在_all
字段中查詢2014-09-15
,首先分析查詢字符串,產生匹配任一詞2014
、09
或15
的查詢語句,它依舊匹配12個推文,因為它們都包含詞2014
。
GET /_search?q=2014-09-15 # 12 results !
當我們在date
字段中查詢2014-09-15
,它查詢一個確切的日期,然后只找到一條推文:
GET /_search?q=date:2014-09-15 # 1 result
當我們在date
字段中查詢2014
,沒有找到文檔,因為沒有文檔包含那個確切的日期:
GET /_search?q=date:2014 # 0 results !
測試分析器
尤其當你是Elasticsearch新手時,對於如何分詞以及存儲到索引中理解起來比較困難。為了更好的理解如何進行,你可以使用analyze
API來查看文本是如何被分析的。在查詢字符串參數中指定要使用的分析器,被分析的文本做為請求體:
GET /_analyze?analyzer=standard&text=Text to analyze
結果中每個節點在代表一個詞:
{
"tokens": [
{
"token": "text",
"start_offset": 0,
"end_offset": 4,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "to",
"start_offset": 5,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "analyze",
"start_offset": 8,
"end_offset": 15,
"type": "<ALPHANUM>",
"position": 3
}
]
}
token
是一個實際被存儲在索引中的詞。position
指明詞在原文本中是第幾個出現的。start_offset
和end_offset
表示詞在原文本中占據的位置。
analyze
API 對於理解Elasticsearch索引的內在細節是個非常有用的工具,隨着內容的推進,我們將繼續討論它。
指定分析器
當Elasticsearch在你的文檔中探測到一個新的字符串字段,它將自動設置它為全文string
字段並用standard
分析器分析。
你不可能總是想要這樣做。也許你想使用一個更適合這個數據的語言分析器。或者,你只想把字符串字段當作一個普通的字段——不做任何分析,只存儲確切值,就像字符串類型的用戶ID或者內部狀態字段或者標簽。
為了達到這種效果,我們必須通過映射(mapping)人工設置這些字段。
三、例子
PUT /gb <1>
{
"mappings": {
"tweet" : {
"properties" : {
"tweet" : {
"type" : "string",
"analyzer": "english"
},
"date" : {
"type" : "date"
},
"name" : {
"type" : "string"
},
"user_id" : {
"type" : "long"
}
}
}
}
}
即使沒有在es中顯示指定映射,ES可以自動猜測字段類型,進行自動映射。如果不符要求,運行期再手動修改也一樣。
{
"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"
}
}
}
}
以上是官網提供的SAMPLE,提供了2個type,user和blogpost
如何查看某個具體type的映射:
curl -XGET 'http://localhost:9200/mytest/_mapping/product?pretty'
我們將映射的最高一層稱為根對象,此處的根對象有properties和_all設置
通常根對象可能包含以下內容:
- 一個 properties 節點,列出了文檔中可能包含的每個字段的映射
- 多個元數據字段,每一個都以下划線開頭,例如 _type, _id 和 _source
- 設置項,控制如何動態處理新的字段,如analyzer, dynamic_date_formats 和dynamic_templates
- 其他設置,可以同時應用在根對象和其他 object 類型的字段上,如enabled, dynamic 等
properties屬性:
1. type: 字段的數據類型,例如 string 和 date,形如:
{ "字段1": { "type": "integer" } }
2. index: index參數控制字符串以何種方式被索引,它包含以下三個值當中的一個:
(1)analyzed: 分詞 索引。換言之,以全文形式索引此字段。
(2)not_analyzed: 索引 不分詞,使之可以被搜索,但是索引內容和指定值一樣。不分析此字段。
(3)no:不索引這個字段。這個字段不能為搜索到。
其中string類型字段默認值是analyzed。如果我們想映射字段為確切值,需要設置它為not_analyzed:
"字段1": { "type": "string", "index": "not_analyzed" }
注意:除了String外的其他簡單類型(long、double、date等等)也接受index參數,但相應的值只能是no和not_analyzed,它們的值不能被分析
3. analyzer: 確定在索引和或搜索時全文字段使用的 分析器,形如:
對於analyzed類型的字符串字段,使用analyzer參數來指定哪一種分析器將在搜索和索引的時候
使用。默認的,ES使用standard分析器,但是你可以通過指定一個內建的分析器來更改它,例如
whitespace、simple或english。
{ "字段1": { "type": "string", "analyzer": "english" } }
4. fielddata: 設置對字段數據裝載內存的處理方式,對同一個索引下相同的名字的設置必須是一樣的
(1) fielddata.format: 設置是否裝載字段數據到內存,只有裝載到內存的數據才能執行排序,聚合等等操作
"properties": {
"text": {
"type": "string",
"fielddata": {
"format": "disabled"
}
}
}
(2) fielddata.loading: 裝載方式,支持 lazy, eager, eager_global_ordinals三種
(3) fielddata.filter: 裝載時對數據進行過濾,支持頻率,支持正則表達式等等
"properties": { "tag": { "type": "string", "fielddata": { "filter": { "frequency": { "min": 0.001, "max": 0.1, "min_segment_size": 500 } } } } }
三、常見字段類型簡介
以下內容主要是介紹在設計mapping映射關系時可以選擇的字段類型以及相關參數。
字段數據類型大致上可以分為以下幾類,幾乎是夠用了。。
1:核心數據類型,包括:
- String類型:string
- Numeric類型:long, integer, short, byte, double, float
- Date類型:date
- Boolean類型:boolean
- Binary類型:binary
2:復雜數據類型,包括:
- Array類型:Array,ES並沒有專用的Array,直接使用[]來表示數組
- Object類型:單一的 JSON 對象
- Nested類型:嵌套的多個 JSON 對象數組
3:地理數據類型,包括:
- Geo-point類型:geo_point的緯度/經度點
- Geo-Shape類型:復雜的地理形狀,比如多邊形
4:特殊數據類型,包括:
- IPv4類型:IPv4地址
- Completion類型:提供自動完成建議
- Token count類型:計算字符串中token的數量
- mapper-murmur3類型:murmur3用來計算索引時的hash值,並把它們存儲在索引里面
- Attachment類型:映射附件的格式,比如:Ms Office,ePub等
下面逐個類型進行詳細說明
(1) Binary類型
這個類型可以看做是可以以Base64編碼的字符串,這個類型缺省是只存儲,但是不可查詢。接受的參數如下:
1:doc_values:是否被存儲在磁盤,以參加后續的排序、聚合等,缺省true
2:store:是否存儲到_source,並能從_source檢索,缺省false
關於store的說明:如果設置為false,當_source=false時,無法獲取,演示如下:
//1. 創建索引mytest,或者先刪除,再創建 //2. 定義mytest/t2的mapping映射 curl -XPUT 'http://localhost:9200/mytest/_mapping/t2?pretty' -d ' { "_source": { "enabled": false }, "properties": { "content": { "type": "string", "store": "true" }, "name": { "type": "string", "store": "false" } } } ' //3. 插入數據 curl -XPUT 'http://localhost:9200/mytest/t2/1' -d ' { "content": "hello", "type": "world" } ' //4. 通過fields查詢,返回結果只有content curl -XGET 'http://localhost:9200/mytest/t2/1?pretty&fields=content,type'
(2) Boolean類型
認為是false的值有很多,比如:false, "false", "off", "no", "0", "" (empty string), 0, 0.0
接受的參數:
1:doc_values 、 store
2:boost:權重,缺省1.0
3:index:這個字段是否可以被查詢,接受not_analyzed (缺省) 和no
4:null_value:設置為null時候的值,缺省是null
(3) 日期類型
符合缺省或者指定日期格式的值會被當作日期類型,缺省是
"strict_date_optional_time||epoch_millis" ,支持很多格式,通常我們會明確指定自己的格式。
接受的參數:
1: boost、 doc_values、 store、 index、 null_value
2:format:日期格式,比如:yyyy-MM-dd HH:mm:ss
3:ignore_malformed:數據不滿足格式的時候,true表示忽略,false拋出一個異常,並拒絕整個文檔
4:include_in_all:是否把字段值包含在_all中,如果index設置為no或者父對象的字段的這個參數設置的是
false,缺省是false;其它情況缺省是true
5:precision_step:設置索引的term的數量,以加快范圍查詢,缺省16
(4) String類型
熟悉的字符串,支持以下參數:
1: boost、doc_values、include_in_all、store、index、null_value
2:analyzer:用來分析字符串字段值的分析器,包括索引和查詢(如果沒有設置查詢索引的分析器)兩個階段,缺省使用index的分析器或者standard分析器
3:fielddata:字段值是否能放置到內存中,用於排序、聚合等,接受disabled或paged_bytes(缺省)
4:fields:Multi-fields允許這同一個字符串值,以多種方式索引,以滿足不同的目的。
5:ignore_above:不要索引或分析任何比這個值長的字符串。缺省為0(disabled)。
6:index_options:為了查詢或高亮顯示,應該在index中存儲什么信息。缺省對analyzed字段是positions,對not_analyzed是docs
7:norms:計算查詢得分時,是否要考慮字段長度,缺省對analyzed字段是
{ “enabled”: true,“loading”: “lazy” } ,對not_analyzed是{ "enabled": false },后面會詳述查詢得分
8:position_increment_gap:距離查詢時,最大允許查詢的距離,默認是100
9:search_analyzer:查詢時使用的分析器,缺省是analyzer設置的分析器
10:search_quote_analyzer:搜索中碰到短語使用的分析器,缺省是search_analyzer設置的分析器
11:similarity:設置計算相似度的算法,缺省TF/IDF
12:term_vector:是否為一個analyzed字段保存term詞計數的值,缺省是no
(5) Token Count類型
這個類型實質上是integer,功能是分析字符串,然后記錄字符串分析后的token數量,例如:
PUT my_index { "mappings": { "my_type": { "properties": { "name": { "type": "string", "fields": { "length": { "type": "token_count", "analyzer": "standard" } } } } } } }
接受的參數:
1:analyzer、boost、doc_values、include_in_all、store、index、null_value
2:precision_step:控制索引的額外term的數量,以加快范圍查詢,缺省32
(6) 多值字段-數組
比如可以索引一個標簽數組來代替單一字符串:
{ "tag": [ "search", "nosql" ]}
對於數組不需要特殊的映射。任何一個字段可以包含零個、一個或多個值,同樣對於全文字段將被分析並產生多個詞。
這意味着數組中所有值必須為同一類型,不能把日期和字符串混合。如果創建一個新字段,這個字段索引了一個數組,ES將使用第一個值的類型來確定這個新字段的類型。
(7) 空字段
Lucene沒法存放null值,所以一個null值的字段被認為是空字段。這四個字段將被識別為空字段而不被索引:
"empty_string": "",
"null_value": null,
"empty_array": [],
"array_with_null_value": [ null ]
(8) 內部對象
ES會動態的檢測新對象的字段,並映射它們為object類型,然后將每個字段加到properties字段
下,例如:
curl -XPUT http://localhost:9200/mytest2?pretty -d ' { "mappings": { "blog": { "properties": { "createTime": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }, "name": { "type": "object", "properties": { "full": { "type": "string" }, "first": { "type": "string" }, "last": { "type": "string" } } }, "title": { "type": "string" } } } } } '
說明:內部對象是怎樣被索引的
Lucene並不了解內部對象。 一個 Lucene 文件包含一個鍵-值對應的扁平表單。 為了讓 ES可以有效的索引內部對象,將文件轉換為以下格式:注意都是數組...
{ "tweet": [ elasticsearch, flexible, very ], "user.id": [ @johnsmith ], "user.gender": [ male ], "user.age": [ 26 ], "user.name.full": [ john, smith ], "user.name.first": [ john ], "user.name.last": [ smith ] }
內部欄位可被歸類至name,例如"first"。 為了區別兩個擁有相同名字的欄位,我們可以使用完整路徑,例如"user.name.first" 或甚至類型名稱加上路徑:"tweet.user.name.first"。
注意: 在以上扁平化文件中,並沒有欄位叫作user也沒有欄位叫作user.name。Lucene 只索引階層或簡單的值,而不會索引復雜的文檔結構
(9) 內部對象數組
一個包含內部對象的數組如何索引,假如有個數組如下所示:
{ "followers": [ { "age": 35, "name": "Mary White" }, { "age": 26, "name": "Alex Jones" }, { "age": 19, "name": "Lisa Smith" } ] }
此文件會如我們以上所說的被扁平化,但其結果會像如此:
{ "followers.age": [ 19, 26, 35 ], "followers.name": [ alex, jones, lisa, smith, mary, white ] }
但是這種合並數組的方式會帶來問題,{age: 35}與{name: Mary White}之間的關聯會消失,因每個多值的欄位會變成一個值集合,而非有序的陣列。 這讓我們可以知道:是否有26歲的追隨者?
但我們無法取得准確的資料如:
是否有26歲的追隨者且名字叫Alex Jones?
可以通過關聯內部對象(Nested Object)解決此類問題,稱之為嵌套對象。
四、元數據類型
ES支持的元數據類型分成如下幾類:
1:Identity meta-fields:_index、_type、_id、_uid(_type和_id的組合值)
2:Document source meta-fields:_source、_size(_source的bytes)
3:Indexing meta-fields:_all、_field_names(文檔中值非null的字段名稱)、_timestamp(文檔關聯
的時間戳,手動設置或自動生成)、_ttl(文檔的存活時間)
4:Routing meta-fields:_parent(設置文檔的parent-child關系)、_routing
5:Other meta-fielde:_meta(具體應用使用的特殊的元數據)
(1) _source
默認情況下,ES用JSON字符串來表示源數據,即沒有經過分詞等等處理的數據,並保存在_source字段中。
可以在mapping中定義_source的處理方式: false表示禁用
{ "mappings": { "my_type": { "_source": { "enabled": false } } } }
(2) _all
泛指所有的字段,也可以是所有的類型,索引等,泛指所有的
_all指字段時可以看做是一個特殊的字段,所有被include_in_all選項控制的字段都包含在內。但是要注意_all會被當作用一個字符串對象。哪怕字段中原本的mapping類型是date,在_all中依然會當做一個字符串,而date和string的索引方式是不同的。
同樣可以在mapping中禁用:
{ "my_type": { "_all": { "enabled": false }
可以在type,字段多個層次去控制是否需要被包含在_all中
PUT /my_index/my_type/_mapping { "my_type": { "include_in_all": false, "properties": { "title": { "type": "string", "include_in_all": true }, ... } } }
_all字段僅僅是一個經過分詞的string字段。它使用默認的分析器來分析它的值,而不管這值本來所在字段指定的分析器。
PUT /my_index/my_type/_mapping { "my_type": { "_all": { "analyzer": "whitespace" } } }
(3) _id
即唯一id,注意一個唯一的文檔由index type id唯一決定,其中type僅僅只是一個邏輯上的概念
最好是所有type中不同文檔的字段名都不相同
五、動態映射
ES支持自動的動態映射,我們很少去修改ES默認的設置,僅僅是某些特殊需求下才會修改動態映射的默認行為
如何禁止自動映射?
生產環境中,可能出於更加規范的目的強制要求必須手動建立映射去禁止自動映射,可以在配置文件中配置。也可以使用api
PUT /_settings { "index.mapper.dynamic":false}
_default_: 為沒有 手動指定mapping的type指定默認mapping內容
PUT my_index { "mappings": { "_default_": { "_all": { "enabled": false } }, "user": {}, "blogpost": { "_all": { "enabled": true } } } }
_default的意思就表示是類型的默認映射關系,如果沒有指定,就用default,比如上面的us.
動態映射,不重要,僅僅演示
缺省的,ES會為沒有設置映射的字段進行動態映射,可以通過 dynamic 設置來控制動態映射,它接受下面幾個選項:
(1)true:自動添加字段(默認)
(2)false:忽略字段
(3)strict:當遇到未知字段時拋出異常
dynamic 設置可以用在根對象或任何 object 對象上。你可以將 dynamic 默認設置為 strict,
而在特定內部對象上啟用它,例如:
PUT /my_index { "mappings": { "my_type": { "dynamic": "strict", "properties": { "title": { "type": "string" }, "name": { "type": "object", "dynamic": true } } } } }
六、總結
ES通過映射來控制索引的行為,並邏輯上對文檔進行划分,擁有相同映射的稱為一個type。
通常,建議在以下情況中使用自定義字段映射:
(1) 區分full-text和exact value,即是否需要分詞
(2) 使用特定的分詞器,比如中文需要使用ik
(3) 優化部分匹配字段
(4) 指定自定義的日期格式
...
當然ES也支持指定完映射之后修改,但是請注意,已經存在的字段不要隨意修改,因為當前這個字段可能已經建立了索引分詞等等。修改必然伴隨着代價。
可以新增字段和設置其類型,對已有數據修改,可能導致錯誤並且不能正確的被搜索到。