前言
Elasticsearch中有大量關鍵概念容易混淆,對於初學者來說是噩夢:
_source字段里存儲了什么?index屬性的作用是什么?- 何時應該開啟
_all字段? store屬性和_source字段有什么關系?store屬性和_all字段有什么關系?- 什么情況下不用保留
_source字段?
本文通過問答及展開描述的方式,深入理解Elasticsearch中的_source、_all字段和store、index屬性。
概念解釋
Q:
_source字段里存儲了什么?
A:原始文檔的內容。
如圖1所示, 第二象限是一份原始文檔,有title和content2個字段,字段取值分別為"我是中國人"和"熱愛***",這一點沒什么可解釋的。我們把原始文檔寫入Elasticsearch,默認情況下,Elasticsearch里面有2份內容,一份是原始文檔,也就是_source字段里的內容,我們在Elasticsearch中搜索文檔,查看的文檔內容就是_source中的內容,如圖2,相信大家一定非常熟悉這個界面。
另一份是倒排索引,倒排索引中的數據結構是倒排記錄表,記錄了詞項和文檔之間的對應關系,比如關鍵詞"中國人"包含在文檔ID為1的文檔中,倒排記錄表中存儲的就是這種對應關系,當然也包括詞頻等更多信息。Elasticsearch底層用的是Lucene的API,Elasticsearch之所以能完成全文搜索的功能就是因為存儲有倒排索引。如果把倒排索引拿掉,Elasticsearch是不是和mongoDB很像?
Q:
index屬性的作用是什么?
A:控制field是否生成倒排索引以及生成索引時是否做分詞。
那么文檔索引到Elasticsearch的時候,默認情況下是對所有字段創建倒排索引的(動態mapping解析出來為數字類型、布爾類型的字段除外),某個字段是否生成倒排索引是由字段的index屬性控制的,在Elasticsearch 5之前,index屬性的取值有三個:
- analyzed:字段被索引,會做分詞,可搜索。反過來,如果需要根據某個字段進行搜索,
index屬性就應該設置為analyzed。 - not_analyzed:字段值不分詞,會被原樣寫入索引。反過來,如果某些字段需要完全匹配,比如人名、地名,
index屬性設置為not_analyzed為佳。 - no:字段不寫入索引,當然也就不能搜索。反過來,有些業務要求某些字段不能被搜索,那么
index屬性設置為no即可。
Q:何時應該開啟
_all字段?
A:_all字段開啟適用於不指定搜索某一個字段,根據關鍵詞,搜索整個文檔內容。
再說_all字段,顧名思義,_all字段是把所有其它字段中的值,以空格為分隔符組成一個大字符串,然后被分析和索引,但是不存儲,也就是說它能被查詢,但不能被取回顯示,是一個超級字段。以圖中的文檔為例,如果開啟_all字段,那么title和content的值會組成一個超級字段,當然也可以設置只存儲某幾個字段到_all屬性里面或者排除某些字段。_all字段在查詢時占用更多的CPU和占用更多的磁盤空間,如果確實不需要它可以完全的關閉它或者基於字段定制。
_all能讓你在不知道要查找的內容是屬於哪個具體字段的情況下進行搜索,例如:
PUT my_index/user/1
{
"first_name": "John",
"last_name": "Smith",
"date_of_birth": "1970-10-24"
}
GET my_index/_search
{
"query": {
"match": {
"_all": "john smith 1970"
}
}
}
得到的結果是:
{
"took": 20,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.84748024,
"hits": [
{
"_index": "my_index",
"_type": "user",
"_id": "1",
"_score": 0.84748024,
"_source": {
"first_name": "John",
"last_name": "Smith",
"date_of_birth": "1970-10-24"
}
}
]
}
}
Q:
store屬性和_source字段有什么關系?
A:_source是字段,store是字段的屬性之一,Elasticsearch默認會存儲一份_source字段作為原始文檔。store屬性和_source字段配合進行設置可以決定字段的檢索方式和是否高亮展示。
回到圖一的第一象限,用戶輸入關鍵詞"中國人",分詞以后,Elasticsearch從倒排記錄表中查找哪些文檔包含詞項"中國人 ",注意變化,分詞之前" 中國人"是用戶查詢(query),分詞之后在倒排索引中" 中國人"是詞項(term)。Elasticsearch根據文檔ID(通常是文檔ID的集合)返回文檔內容給用戶,如圖一第四象限所示。
通常情況下,對於用戶查詢的關鍵字要做高亮處理,如圖3所示:
關鍵字高亮實質上是根據倒排記錄中的詞項偏移位置,找到關鍵詞,加上前端的高亮代碼。這里就要說到store屬性,store屬性用於指定是否將原始字段寫入索引,默認取值為false。如果在Lucene中,高亮功能和store屬性是否存儲息息相關,因為需要根據偏移位置到原始文檔中找到關鍵字才能加上高亮的片段。兩者不同取值時對字段檢索和高亮的影響如下表:
| _source\store | true | false |
|---|---|---|
| enabled | store為true的字段從倒排索引里檢索,浪費IO次數; |
所有字段根據Client類解析存儲的 _source JSON串進行檢索,僅需一次IO; |
| disabled | store為true的字段從倒排索引里檢索,其他字段能檢索不能高亮展示; |
所有字段只能檢索,不能高亮展示; |
在Elasticsearch,因為_source中已經存儲了一份原始文檔,可以根據_source中的原始文檔實現高亮,在索引中再存儲原始文檔就多余了,所以Elasticsearch默認是把store屬性設置為false。
如果想要對某個字段實現高亮功能,_source和store至少保留一個。下面給出一個例子,設置test索引不保存_source,title字段索引但不分析,字段原始值寫入索引,content字段為默認屬性,代碼如下:
- 新建test index:
PUT test/test/_mapping
{
"test": {
"_source": {
"enabled": false
},
"properties": {
"title": {
"type": "string",
"index": "not_analyzed",
"store": "true"
},
"content": {
"type": "string"
}
}
}
}
- 對title字段進行搜索並高亮:
GET test/_search
{
"query": {
"match": {
"title": "我是中國人"
}
},
"highlight": {
"fields": {
"title": {}
}
}
}
- 返回結果:
{
"took": 6,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.30685282,
"hits": [
{
"_index": "test",
"_type": "test",
"_id": "1",
"_score": 0.30685282,
"highlight": {
"title": [
"<em>我是中國人</em>"
]
}
}
]
}
}
從返回結果中可以看到,雖然沒有保存title字段到_source, 但是依然可以實現搜索高亮。
{"store": true}既然這么費力不討好,但是仍然有兩個應用場景:
- 文檔很長,檢索所有文檔或者存儲所有文檔、獲取所有field的代價比較大;
- 僅僅針對某幾個字段進行re-index的時候;
Q:
store屬性和_all字段有什么關系?
A:_all字段擁有store屬性,是從屬關系。store屬性決定了_all字段是否可以被高亮展示。
首先store是_all字段的一個屬性,_all字段默認不屬於_source,並且store屬性默認為false,所以不能被高亮展示。如果希望被高亮展示,則有兩種方式:
- 設置
_all字段的store為true,代價是大量的冗余存儲; - 對原始字段進行高亮;
Q:什么情況下不用保留
_source字段?
A:需要存儲海量文檔,Elasticsearch在架構中只做索引,存儲選型為類似HBase的NoSQL,不存儲_source可以極大減少存儲開銷(Elasticsearch往往用的是SSD)。
_source字段默認是存儲的, 什么情況下不用保留_source字段?如果某個字段內容非常多,業務只需要能對該字段進行搜索,最后返回文檔id,查看文檔內容會再次到HBase中取數據,把大字段的內容存在Elasticsearch中只會增大索引,這一點文檔數量越大結果越明顯,如果一條文檔節省幾KB,放大到億萬級的量結果也是非常可觀的。 如果想要關閉_source字段,在mapping中的設置如下:
{
"yourtype":{ "_source":{ "enabled":false }, "properties": { ... } } }
如果只想存儲某幾個字段的原始值到Elasticsearch,可以通過incudes參數來設置,在mapping中的設置如下:
{
"yourtype":{
"_source":{
"includes":["field1","field2"]
},
"properties": {
...
}
}
}
同樣,可以通過excludes參數排除某些字段:
{
"yourtype":{
"_source":{
"excludes":["field1","field2"]
},
"properties": {
...
}
}
}
小結
至此,文章開頭提出的幾個問題都給出了答案。特別參考了這篇文章:圖解Elasticsearch中的_source、_all、store和index屬性。
追加:
1、_source
_source字段我在們進行檢索時相當重要,
ES默認檢索只會返回ID,如果在{"enabled":false}情況下,你需通過根據這個ID去去倒排索引中去取每個Field數據,效率不高。
而反之,在{"enabled":true}的情況下可以根據ID直接檢索對應source JSON的字段,不用去倒排索引去按Field取數據。
盡管_source非常有用, 但它確實會占用索引的存儲空間, 所以也可以禁用(enabled false)、啟用狀態的壓縮策略(compress)。
壓縮的策略有兩類:
- compress:是否進行壓縮,建議一般情況下將其設為true
- "includes" : ["author", "name"], "excludes" : ["sex"]
2、store
默認為no,
如果在{"store":yes}的情況下,ES會對該字段單獨存儲倒排索引,每次根據ID檢索的時候,會多走一次IO來從倒排索引取數據。
而如果_source enabled 情況下,ES可以直接根據Client類來解析_source JSON,只需一次IO就將所有字段都檢索出來了。
{"store":yes}既然這么費力不討好,但是仍然有兩個應用場景:
- 文檔很長,檢索所有文檔或者存儲所有文檔、獲取所有field的代價比較大
- 僅僅針對某幾個字段進行re-index的時候
3、總結
| _source\store | yes | no |
| enabled | store為yes的字段從倒排索引里檢索, 浪費IO次數 |
所有字段根據Client類解析實現存儲的JSON串 僅需一次IO |
| disabled | store為yes的字段從倒排索引里檢索, 其他字段能檢索不能展示 |
所有字段只能檢索,不能展示 |
PS. 索引后-->可查詢檢索,存儲后-->可展示
999、參考
elasticsearch的store屬性跟_source字段
來自:https://www.jianshu.com/p/0908b9ee65fc
https://www.cnblogs.com/zklidd/p/6149120.html
