ElasticSearch索引與搜索


在系列的第一篇文章中我們介紹了ElasticSearch的基本概念和操作,本文將繼續介紹ElasticSearch查詢和索引功能。

目錄:

查詢

結構化搜索將查詢條件以json的形式包含在http請求的body中,通常情況下搜索請求應該使用GET方法但不是所有的客戶端和服務端都支持GET請求包含body。 因此,ElasticSearch支持使用GET或POST進行搜索。

本節示例均使用默認analysismapping配置, 此狀態下ElasticSearch與普通的文檔數據庫幾乎無異。我們將在下一節中介紹如何進行模糊查詢,使它成為真正的搜索引擎。

精確查詢

精確查詢通常起到過濾的作用,因為不使用分析器解析通常非常迅速。

查詢可以使用from和size參數對結果進行分頁:

{
  "from": 0,
  "size": 10,
  "query": {
    "term": {
        "nickname": "easy"
    }
  }
}

term查詢

term查詢用於進行精確匹配:

POST /blog/user/_search
{
  "query": {
    "term": {
        "nickname": "easy"
    }
  }
}

response:
{
    "took": 23,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 2,
        "max_score": 0.45532417,
        "hits": [
            {
                "_index": "blog",
                "_type": "user",
                "_id": "1",
                "_score": 0.45532417,
                "_source": {
                    "nickname": "easy",
                    "username": "user1",
                    "status": "normal"
                }
            },
            {
                "_index": "blog",
                "_type": "user",
                "_id": "2",
                "_score": 0.43648314,
                "_source": {
                    "nickname": "easy",
                    "username": "user2"
                    "status": "normal"
                }
            }
        ]
    }
}

在響應的hits.hits字段中我們可以看到匹配的文檔,文檔_score字段是采用TF-IDF算法得出匹配程度得分,結果集中的文檔按照得分降序排列。

上述查詢可以用sql表示為:

SELECT * FROM user
WHERE nickname = "easy";

terms查詢

terms查詢可以視為多個term查詢的組合:

POST /blog/user/_search
{
  "query": {
    "terms": {
      "nickname": ["easy", "ease"]
    }
  }
}

response:
{
    "took": 18,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 2,
        "max_score": 1.0970675,
        "hits": [
            {
                "_index": "blog",
                "_type": "user",
                "_id": "2",
                "_score": 1.5779335,
                "_source": {
                    "nickname": "ease",
                    "status": "normal"
                }
            },
            {
                "_index": "blog",
                "_type": "user",
                "_id": "4",
                "_score": 1.0970675,
                "_source": {
                    "nickname": "easy",
                    "content": "simple",
                    "status": "normal"
                }
            }
        ]
    }
}

range查詢

range查詢用於數值等類型進行過濾,比如示例中過濾粉絲數大於等於2的用戶:

GET /user/naive/_search
{
    "query": {
        "range": {
            "followerCount": { 
                "gte": 2
            },
            "registerTime": {
                "gt": "2017-01-01 00:00:00",
                "lt": "2018-01-01 00:00:00" 
            }
        }
    }
}

示例中的range查詢可以用SQL描述為:

SELECT * FROM user_naive
WHERE followerCount >= 2 
    AND unix_timestamp(registerTime) > unix_timestamp('2017-01-01 00:00:00')
    AND unix_timestamp(registerTime) < unix_timestamp('2018-01-01 00:00:00');

可用的范圍表達式有:

  • lt: 小於(less than)
  • lte: 小於等於(less than / equals)
  • gt: 大於(greater than)
  • gte: 大於等於(greater than / equals)

可以進行范圍查詢的字段:

  • 數值
  • 日期
  • 字符串: 按照詞典順序排列

全文查詢

用戶輸入字符串通常無法進行精確查詢,全文查詢可以使用分析器解析查詢字符串然后根據相關度篩選查詢結果。

match查詢

match查詢全文字段(類型為analyzed)時,會使用分析器將查詢字符串解析成詞條列表,然后進行匹配。

查詢字符串"easy simple"會被standard分析器解析為[easy, simple], 這個查詢等價於:

{
    "terms": {
        "nickname": ["easy", "simple"]
    }
}
POST /blog/user/_search
{
  "query": {
    "match": {
        "nickname": "easy simple"
    }
  }
}

response:
{
    "took": 19,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 1.1382749,
        "hits": [
            {
                "_index": "blog",
                "_type": "user",
                "_id": "3",
                "_score": 1.1382749,
                "_source": {
                    "nickname": "simple",
                    "status": "normal"
                }
            },
            {
                "_index": "blog",
                "_type": "user",
                "_id": "1",
                "_score": 1.049597,
                "_source": {
                    "nickname": "easy",
                    "status": "normal"
                }
            }
        ]
    }
}

若以eas sim為關鍵詞進行term查詢不會匹配到任何文檔。

multi_match查詢

multi_match用於對多個字段進行同一個查詢:

{
  "query": {
    "multi_match": {
        "analyzer": "keyword",
        "query": "easy",
        "fields": [
            "nickname.ngram",
            "username.ngram"
        ]
    }
  }
}

上述查詢等同於對nickname.ngramusername.ngram兩個字段進行match查詢,對結果進行綜合排序。

排序方式有:

  • best_fields
  • most_fields
  • cross_fields

script查詢

script查詢可以使用腳本描述查詢條件,進行復雜的查詢。

{
  "query": {
      "script": {
        "script": {
          "source": "doc['count'].value > params.limit",
          "lang": "painless",
          "params": {
              "limit": 1
          }
        }
      }
}

我們通常使用ElasticSearch提供的painless腳本語言編寫腳本,也可以使用Groovy等腳本語言。

組合查詢

組合查詢用於將多個查詢組合起來,以表達更復雜的查詢條件或排序規則。

組合查詢都是可以自由嵌套的,如我們可以bool查詢中使用另一個bool查詢或dis_max作為must子查詢。

bool查詢

Bool查詢用於組合多個條件查詢相關度最高的文檔, 下面展示了一個Bool查詢請求:

POST /user/naive/_search
{
  "query": {
    "bool": {
      "must": {
          "match": {
            "nickname": "easy"
          }
      },
      "must_not": {
        "match": {
          "nickname": "hard"
        }
      },
      "should": {
        "match": {
          "nickname": "simple"
        }
      },
      "filter": [
        {
          "term": {
            "status": "normal"
          }
        }
      ]
    }
  }
}

response:
{
    "took": 20,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 1.3847495,
        "hits": [
            {
                "_index": "blog",
                "_type": "user",
                "_id": "4",
                "_score": 1.3847495,
                "_source": {
                    "nickname": "easy",
                    "content": "simple",
                    "status": "normal"
                }
            },
            {
                "_index": "blog",
                "_type": "user",
                "_id": "5",
                "_score": 0.45532417,
                "_source": {
                    "nickname": "easy",
                    "content": "bitter",
                    "status": "normal"
                }
            }
        ]
    }
}

上述bool查詢的目標為:

  • must條件必須滿足, 即nickname字段必須與詞條easy匹配,字段的匹配程度會影響得分
  • must_not條件必須不滿足, 即nickname字段不能與詞條hard匹配
  • should條件不做要求, 但滿足should條件的文檔會獲得更高的相關度評分_score。 當should查詢中包含多個字段時, 會將各字段得分的和作為總分。所以查詢到兩個nickname與easy匹配的文檔,但是contentsimple的字段獲得了更高的評分。
  • filter條件必須滿足,但是匹配程度不會影響得分。

filter子查詢一般使用精確查詢,因為過濾器不進行相關度計算且使用過濾器緩存, 其執行速度非常快。

dis_max查詢

上文已經提到bool查詢的should查詢會將各字段得分之和作為總分,然而在實際應用中通常一個字段高度匹配的文檔可能比擁有多個字段低匹配更符合用戶的期望。

dismax查詢同樣用於組合多個查詢,但是按照匹配程度最高的字段確定總分:

{
  "query": {
    "dis_max": {
      "queries": [
      	{
      		"match": {
        		"nickname": "easy"
        	}
        },
      	{
      		"match": {
        		"content": "easy"
        	}
        }
      ]
    }
  }
}

function_score 查詢

function_score可以更加自由地控制評分過程:

{
  "query": {
    "function_score": {
      "filter": { 
        "term": { "uid": 1 }
      },
      "functions": [ 
        {
          "filter": { "term": { "features": "a" }}, 
          "weight": 1
        },
        {
          "filter": { "term": { "features": "b" }}, 
          "weight": 2 
        }
      ],
      "score_mode": "sum", 
    }
  }
}

一致性隨機

function_score可以對結果進行隨機排序:

{
  "query": {
    "random_score": {
      "seed": 2
    },
    "bost_mode": "replace",
    "query": {
      "dis_max": {
        "queries": [
          {
            "multi_match": {
              "analyzer": "keyword",
              "query": "finley",
              "boost": 1.2,
              "fields": [
                "username.ngram"
              ]
            }
          },
          {
            "multi_match": {
              "analyzer": "keyword",
              "query": "finley",
              "boost": 1,
              "fields": [
                "nickname.ngram"
              ]
            }
          }
        ]
      }
    }
  }
}

只要隨機種子random_score.seed不變結果的排序就不會變化,這種方式被稱為一致性隨機評分。

一致性隨機評分機制可以為每個用戶生成一個獨有的排序,並支持翻頁瀏覽。

排序

ElasticSearch的搜索結果默認按照_score進行降序排列,_score是根據TF-IDF算法得出文檔與查詢調價匹配度。

在一些情況下我們希望自定義排序方式, 比如按創建時間排列。

POST /blog/user/_search
{
    "query" : {
        "term" : { 
            "uid" : 1 
        }
    },
    "sort": { 
        "date": { 
            "order": "desc" 
        }
    }
}

script 排序

{
    "query" : {
        "term" : { 
            "uid" : 1 
        }
    },
    "sort": { 
        "script_score": { 
            "source": "_score / (params.current - doc['create_at'].value)"
            "params": {
                "current": 1534606168
            } 
        }
    }
}

索引管理

ElasticSearch中的Index是最高級的邏輯的結構, 類似於MySQL中的數據庫(schema),可以配置獨立的搜索策略和存儲策略。

ElasticSearch通過倒排索引進行搜索,所謂倒排索引是指對文檔進行分析提取關鍵詞,然后建立關鍵詞到文檔的索引,當我們搜索關鍵詞時就可以找到它所關聯的文檔。

我們可以通過在Index中配置分析器和映射來設置倒排索引的策略。

分析器是通用的從文檔提取關鍵詞的方法,即將文檔中某個字段映射為關鍵字的方法。例如:過濾停用詞,分詞,添加同義詞等。

映射則是具體指定文檔中的某個字段應該使用什么分析器來提取關鍵詞。

分析器

這個拆分的過程即是分析的過程, 分析執行的操作包括不限於: 字符過濾, 分詞, 停用詞過濾, 添加同義詞.

ES提供了很多內置分析器:

  • standard: 默認分析器, 根據Unicode定義刪除標點並將詞條小寫
  • simple: 根據空格分詞並將詞條小寫
  • whitespace: 根據空格分詞但不將詞條小寫
  • english: 過濾英文停用詞並將詞條還原為詞根, 詳情參考官方文檔.
  • ngram: 滑動窗口分析器,取文本中所有子串作為關鍵詞。 比如對easy進行處理可以得到關鍵詞:e, a, s, y, ea, as, sy, eas, asy, easy
  • edge-ngram: 邊緣固定滑動窗口分析器,取文本所有從頭開始的子串作為關鍵詞。 比如對easy進行處理可以得到關鍵詞:e, ea, eas, easy。常用於聯想搜索,根據用戶輸入前幾個字符進行搜索。

此外, 也可以通過配置字符過濾器(char_filter), 詞過濾器(filter), 分詞器(tokenizer)的方式來自定義分析器。

這里展示基於ngram的分析器定義:

PUT /blog
{
	"settings": {
		"analysis": {
			"filter": {
				"grams_filter": {
					"type": "ngram",
					"min_gram": 1,
					"max_gram": 5
				}	
			},
            "analyzer": {
                "gram_analyzer": {
                    "type": "custom",
                    "tokenizer": "standard",
                    "filter": [
                        "lowercase",
                        "grams_filter"
                    ]
                }
            }
		}
	}
	},
	mappings: {...}
}

自定義分析器的更多信息可以查閱官方文檔:

類型和映射

映射則是具體指定文檔中的某個字段應該使用什么分析器來提取關鍵詞:

PUT /blog
{
  "settings": { ... },
  "mappings": {
		"user": {
			"properties": {
				"nickname": {
					"type": "string",
					"analyzer": "gram_analyzer",
					"fields": {
						"keyword:": {
							"type": "keyword"
						}
					}
				},
				"status": {
					"type": "text",
					"fields": {
						"keyword:": {
							"type": "keyword"
						}
					}
				}
			}
		}
	}
}

上述JSON中user項定義了一個根對象, 它通常與文檔對應。根對象下可以包含下列幾個字段:

文檔中每一個字段都有3個配置項:

  • type: 指定字段類型, 如:text, long, doubledate.
  • index: 指定字段索引的類型:
    • no: 不可被搜索
    • not_analyzed: 必須精確匹配
    • analyzed: 使用分析器建立倒排索引
  • analyzer: 該字段使用的默認分析器
  • fields: 字段的屬性, 可以配置獨立的type, index和analyzer。用於將一個字段映射為不同類型適應不同用途。

管理索引

在分析器及映射兩節中展示了創建索引所需的PUT請求片段,將類型和映射一節中PUT請求的settings字段, 用分析器一節中的settings字段替換即可得到完整創建索引請求。

發送DELETE請求可以刪除索引:

  • DELETE /user: 刪除user索引
  • DELET /user1,user2: 刪除user1和user2兩個suoyin
  • DELETE /user*: 根據通配符刪除索引
  • DELET /_all, DELETE /*: 刪除所有索引

GET /_cat/indices可以列出ElasticSearch上的所有索引。

GET /blog?pretty列出索引blog的所有信息。

中文搜索

若需要進行中文搜索我們需要使用中文分析器,它的核心是中文分詞器(tokenizer)。

這里我們使用插件elastic-analysis-ik進行中文搜索。

PUT /post/Post
{
    "settings": {
        "analysis": {
            "analyzer": {
                "cn_smart": {
                    "type": "custom",
                    "tokenizer": "ik_smart",
                    "filter": [
                        {
                            "type": "length",
                            "min": "3"
                        }
                    ]
                },
                "max_word": {
                    "type": "custom",
                    "tokenizer": "ik_max_word",
                    "filter": [
                        {
                            "type": "length",
                            "min": "3"
                        }
                    ]
                }
            }
        }
    },
    "mappings": {
		"user": {
			"properties": {
				"content": {
					"type": "text",
                    "analyzer": "cn_smart",
                    "fields": {
                        "max_word": {
                            "type": "text",
                            "analyzer": "max_word"
                        }
                    }
                }
            }
        }
    }
}

使用match進行查詢:

{
    "query" : { 
        "match" : { 
            "content" : "搜索" 
        }
    }
}
{
    "query" : { 
        "match" : { 
            "content.max_word" : "搜索" 
        }
    }
}

ik_smart會進行標准的中文分詞, ik_max_word則會試圖窮盡所有分詞


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM