1. 程式人生 > >圖解Elasticsearch中的_source、_all、store和index屬性

圖解Elasticsearch中的_source、_all、store和index屬性

Elasticsearch中有幾個關鍵屬性容易混淆,很多人搞不清楚_source欄位裡儲存的是什麼?store屬性的true或false和_source欄位有什麼關係?store屬性設定為true和_all有什麼關係?index屬性又起到什麼作用?什麼時候設定store屬性為true?什麼時候應該開啟_all欄位?本文通過圖解的方式,深入理解Elasticsearch中的_source、_all、store和index屬性。

這裡寫圖片描述

圖1 Elasticsearch中的_source、_all、store和index屬性解析

圖1所示, 第二象限是一份原始文件,有title和content2個欄位,欄位取值分別為”我是中國人”和” 熱愛共產黨”,這一點沒什麼可解釋的。我們把原始文件寫入Elasticsearch,預設情況下,Elasticsearch裡面有2份內容,一份是原始文件,也就是_source欄位裡的內容,我們在Elasticsearch中搜索文件,檢視的文件內容就是_source中的內容,如圖2,相信大家一定非常熟悉這個介面。 
這裡寫圖片描述
 
圖2 _source欄位舉例

另一份是倒排索引,倒排索引中的資料結構是倒排記錄表,記錄了詞項和文件之間的對應關係,比如關鍵詞”中國人”包含在文件ID為1的文件中,倒排記錄表中儲存的就是這種對應關係,當然也包括詞頻等更多資訊。Elasticsearch底層用的是Lucene的API,Elasticsearch之所以能完成全文搜尋的功能就是因為儲存的有倒排索引。如果把倒排索引拿掉,Elasticsearch是不是和mongoDB很像?

那麼文件索引到Elasticsearch的時候,預設情況下是對所有欄位建立倒排索引的(動態mapping解析出來為數字型別、布林型別的欄位除外),某個欄位是否生成倒排索引是由欄位的index屬性控制的,在Elasticsearch 5之前,index屬性的取值有三個:

  1. analyzed:欄位被索引,會做分詞,可搜尋。反過來,如果需要根據某個欄位進搜尋,index屬性就應該設定為analyzed。
  2. not_analyzed:欄位值不分詞,會被原樣寫入索引。反過來,如果某些欄位需要完全匹配,比如人名、地名,index屬性設定為not_analyzed為佳。
  3. no:欄位不寫入索引,當然也就不能搜尋。反過來,有些業務要求某些欄位不能被搜尋,那麼index屬性設定為no即可。

再說_all欄位,顧名思義,_all欄位裡面包含了一個文件裡面的所有資訊,是一個超級欄位。以圖中的文件為例,如果開啟_all欄位,那麼title+content會組成一個超級欄位,這個欄位包含了其他欄位的所有內容,當然也可以設定只儲存某幾個欄位到_all屬性裡面或者排除某些欄位。

回到圖一的第一象限,使用者輸入關鍵詞" 中國人",分詞以後,Elasticsearch從倒排記錄表中查詢哪些文件包含詞項"中國人 ",注意變化,分詞之前" 中國人"是使用者查詢(query),分詞之後在倒排索引中" 中國人"是詞項(term)。Elasticsearch根據文件ID(通常是文件ID的集合)返回文件內容給使用者,如圖一第四象限所示。

通常情況下,對於使用者查詢的關鍵字要做高亮處理,如圖3所示: 
這裡寫圖片描述

圖3 搜尋引擎中的關鍵字高亮

關鍵字高亮實質上是根據倒排記錄中的詞項偏移位置,找到關鍵詞,加上前端的高亮程式碼。這裡就要說到store屬性,store屬性用於指定是否將原始欄位寫入索引,預設取值為no。如果在Lucene中,高亮功能和store屬性是否儲存息息相關,因為需要根據偏移位置到原始文件中找到關鍵字才能加上高亮的片段。在Elasticsearch,因為_source中已經儲存了一份原始文件,可以根據_source中的原始文件實現高亮,在索引中再儲存原始文件就多餘了,所以Elasticsearch預設是把store屬性設定為no。

注意:如果想要對某個欄位實現高亮功能,_source和store至少保留一個。下面會給出測試程式碼。

至此,文章開頭提出的幾個問題都給出了答案。下面給出這幾個欄位常用配置的程式碼。

一、_source配置

_source欄位預設是儲存的, 什麼情況下不用保留_source欄位?如果某個欄位內容非常多,業務裡面只需要能對該欄位進行搜尋,最後返回文件id,檢視文件內容會再次到mysql或者hbase中取資料,把大欄位的內容存在Elasticsearch中只會增大索引,這一點文件數量越大結果越明顯,如果一條文件節省幾KB,放大到億萬級的量結果也是非常可觀的。 
如果想要關閉_source欄位,在mapping中的設定如下:

{
    "yourtype":{
        "_source":{
            "enabled":false
        },
        "properties": {
            ... 
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如果只想儲存某幾個欄位的原始值到Elasticsearch,可以通過incudes引數來設定,在mapping中的設定如下:

{
    "yourtype":{
        "_source":{
            "includes":["field1","field2"]
        },
        "properties": {
            ... 
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

同樣,可以通過excludes引數排除某些欄位:

{
    "yourtype":{
        "_source":{
            "excludes":["field1","field2"]
        },
        "properties": {
            ... 
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

測試,首先建立一個索引

PUT test
  • 1

設定mapping,禁用_source:

PUT test/test/_mapping
{
   "test": {
      "_source": {
         "enabled": false
      }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

寫入一條文件:

POST test/test/1
{
    "title":"我是中國人",
    "content":"熱愛共產黨"
}
  • 1
  • 2
  • 3
  • 4
  • 5

搜尋關鍵詞”中國人”:

GET test/_search
{
    "query": {
        "match": {
           "title": "中國人"
        }
    }
}
{
   "took": 9,
   "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
         }
      ]
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

從返回結果中可以看到,搜到了一條文件,但是禁用_source以後查詢結果中不會再返回文件原始內容。(注,測試基於ELasticsearch 2.3.3,配置檔案中已預設指定ik分詞。)

二、_all配置

_all欄位預設是關閉的,如果要開啟_all欄位,索引增大是不言而喻的。_all欄位開啟適用於不指定搜尋某一個欄位,根據關鍵詞,搜尋整個文件內容。 
開啟_all欄位的方法和_source類似,mapping中的配置如下:

{
   "yourtype": {
      "_all": {
         "enabled": true
      },
      "properties": {
            ... 
      }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

也可以通過在欄位中指定某個欄位是否包含在_all中:

{
   "yourtype": {
      "properties": {
         "field1": {
             "type": "string",
             "include_in_all": false
          },
          "field2": {
             "type": "string",
             "include_in_all": true
          }
      }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如果要把欄位原始值儲存,要設定store屬性為true,這樣索引會更大,需要根據需求使用。下面給出測試程式碼。 
建立test索引:

DELETE  test
PUT test
  • 1
  • 2

設定mapping,這裡設定所有欄位都儲存在_all中並且儲存原始值:

PUT test/test/_mapping
{
   "test": {
      "_all": {
         "enabled": true,
         "store": true
      }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

插入文件:

POST test/test/1
{
    "title":"我是中國人",
    "content":"熱愛共產黨"
}
  • 1
  • 2
  • 3
  • 4
  • 5

對_all欄位進行搜尋並高亮:

POST test/_search
{
   "fields": ["_all"], 
   "query": {
      "match": {
         "_all": "中國人"
      }
   },
   "highlight": {
      "fields": {
         "_all": {}
      }
   }
}
{
   "took": 3,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 1,
      "max_score": 0.15342641,
      "hits": [
         {
            "_index": "test",
            "_type": "test",
            "_id": "1",
            "_score": 0.15342641,
            "_all": "我是中國人 熱愛共產黨 ",
            "highlight": {
               "_all": [
                  "我是<em>中國人</em> 熱愛共產黨 "
               ]
            }
         }
      ]
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

Elasticsearch中的query_string和simple_query_string預設就是查詢_all欄位,示例如下:

GET test/_search
{
    "query": {
        "query_string": {
           "query": "共產黨"
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

三、index和score配置

index和store屬性實在欄位內進行設定的,下面給出一個例子,設定test索引不儲存_source,title欄位索引但不分析,欄位原始值寫入索引,content欄位為預設屬性,程式碼如下:

DELETE  test
PUT test
PUT test/test/_mapping
{
   "test": {
      "_source": {
         "enabled": false
      },
      "properties": {
         "title": {
            "type": "string",
            "index": "not_analyzed",
            "store": "true"
         },
         "content": {
            "type": "string"
         }
      }
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

對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>"
               ]
            }
         }
      ]
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

從返回結果中可以看到,雖然沒有儲存title欄位到_source, 但是依然可以實現搜尋高亮。

四、總結

通過圖解和程式碼測試,對Elasticsearch中的_source、_all、store和index進行了詳解,相信很容易明白。錯誤和疏漏之處,歡迎批評指正。