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個字段,字段取值分别為”我是中國人”和” 熱愛xx黨”,這一點沒什麼可解釋的。我們把原始文檔寫入Elasticsearch,預設情況下,Elasticsearch裡面有2份内容,一份是原始文檔,也就是_source字段裡的内容,我們在Elasticsearch中搜尋文檔,檢視的文檔内容就是_source中的内容,如圖2,相信大家一定非常熟悉這個界面。
圖2 _source字段舉例
另一份是反向索引,反向索引中的資料結構是倒排記錄表,記錄了詞項和文檔之間的對應關系,比如關鍵詞”中國人”包含在文檔ID為1的文檔中,倒排記錄表中存儲的就是這種對應關系,當然也包括詞頻等更多資訊。Elasticsearch底層用的是Lucene的API,Elasticsearch之是以能完成全文搜尋的功能就是因為存儲的有反向索引。如果把反向索引拿掉,Elasticsearch是不是和mongoDB很像?
那麼文檔索引到Elasticsearch的時候,預設情況下是對所有字段建立反向索引的(動态mapping解析出來為數字類型、布爾類型的字段除外),某個字段是否生成反向索引是由字段的index屬性控制的,在Elasticsearch 5之前,index屬性的取值有三個:
- analyzed:字段被索引,會做分詞,可搜尋。反過來,如果需要根據某個字段進搜尋,index屬性就應該設定為analyzed。
- not_analyzed:字段值不分詞,會被原樣寫入索引。反過來,如果某些字段需要完全比對,比如人名、地名,index屬性設定為not_analyzed為佳。
- 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": {
...
}
}
}
如果隻想存儲某幾個字段的原始值到Elasticsearch,可以通過incudes參數來設定,在mapping中的設定如下:
{
"yourtype":{
"_source":{
"includes":["field1","field2"]
},
"properties": {
...
}
}
}
同樣,可以通過excludes參數排除某些字段:
{
"yourtype":{
"_source":{
"excludes":["field1","field2"]
},
"properties": {
...
}
}
}
測試,首先建立一個索引
PUT test
設定mapping,禁用_source:
PUT test/test/_mapping
{
"test": {
"_source": {
"enabled": false
}
}
}
寫入一條文檔:
POST test/test/1
{
"title":"我是中國人",
"content":"熱愛xx黨"
}
搜尋關鍵詞”中國人”:
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
}
]
}
}
從傳回結果中可以看到,搜到了一條文檔,但是禁用_source以後查詢結果中不會再傳回文檔原始内容。(注,測試基于ELasticsearch 2.3.3,配置檔案中已預設指定ik分詞。)
二、_all配置
_all字段預設是關閉的,如果要開啟_all字段,索引增大是不言而喻的。_all字段開啟适用于不指定搜尋某一個字段,根據關鍵詞,搜尋整個文檔内容。
開啟_all字段的方法和_source類似,mapping中的配置如下:
{
"yourtype": {
"_all": {
"enabled": true
},
"properties": {
...
}
}
}
也可以通過在字段中指定某個字段是否包含在_all中:
{
"yourtype": {
"properties": {
"field1": {
"type": "string",
"include_in_all": false
},
"field2": {
"type": "string",
"include_in_all": true
}
}
}
}
如果要把字段原始值儲存,要設定store屬性為true,這樣索引會更大,需要根據需求使用。下面給出測試代碼。
建立test索引:
DELETE test
PUT test
設定mapping,這裡設定所有字段都儲存在_all中并且存儲原始值:
PUT test/test/_mapping
{
"test": {
"_all": {
"enabled": true,
"store": true
}
}
}
插入文檔:
POST test/test/1
{
"title":"我是中國人",
"content":"熱愛xx黨"
}
對_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": "我是中國人 熱愛xx黨 ",
"highlight": {
"_all": [
"我是<em>中國人</em> 熱愛xx黨 "
]
}
}
]
}
}
Elasticsearch中的query_string和simple_query_string預設就是查詢_all字段,示例如下:
GET test/_search
{
"query": {
"query_string": {
"query": "xx黨"
}
}
}
三、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"
}
}
}
}
對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, 但是依然可以實作搜尋高亮。
四、總結
通過圖解和代碼測試,對Elasticsearch中的_source、_all、store和index進行了詳解,相信很容易明白。錯誤和疏漏之處,歡迎批評指正。