1. 程式人生 > >Elasticsearch(六)-排序

Elasticsearch(六)-排序

排序

相關性排序

預設情況下,結果集會按照相關性進行排序 – 相關性越高,排名越靠前。 這一章我們會講述相關性是什麼以及它是如何計算的。 在此之前,我們先看一下sort引數的使用方法。

排序方式

為了使結果可以按照相關性進行排序,我們需要一個相關性的值。在ElasticSearch的查詢結果中, 相關性分值會用_score欄位來給出一個浮點型的數值,所以預設情況下,結果集以_score進行倒序排列。

有時,即便如此,你還是沒有一個有意義的相關性分值。比如,以下語句返回所有tweets中 user_id 是否 包含值 1:

GET /_search
{
    "query"
: { "filtered" : { "filter" : { "term" : { "user_id" : 1 } } } } }

過濾語句與 _score 沒有關係,但是有隱含的查詢條件 match_all 為所有的文件的 _score 設值為 1。 也就相當於所有的文件相關性是相同的。

欄位值排序

下面例子中,對結果集按照時間排序,這也是最常見的情形,將最新的文件排列靠前。 我們使用 sort 引數進行排序:

GET /_search
{
    "query" : {
        "filtered" : {
            "filter" : { "term" : { "user_id" : 1 }}
        }
    },
    "sort": { "date": { "order": "desc" }}
}

你會發現這裡有兩個不同點:

"hits" : {
    "total" :           6,
    "max_score" :       null, <1>
    "hits" : [ {
        "_index"
: "us", "_type" : "tweet", "_id" : "14", "_score" : null, <1> "_source" : { "date": "2014-09-24", ... }, "sort" : [ 1411516800000 ] <2> }, ... }

<1> _score 欄位沒有經過計算,因為它沒有用作排序。

<2> date 欄位被轉為毫秒當作排序依據。

首先,在每個結果中增加了一個 sort 欄位,它所包含的值是用來排序的。 在這個例子當中 date 欄位在內部被轉為毫秒,即長整型數字1411516800000等同於日期字串 2014-09-24 00:00:00 UTC。

其次就是 _score 和 max_score 欄位都為 null。計算 _score 是比較消耗效能的, 而且通常主要用作排序 – 我們不是用相關性進行排序的時候,就不需要統計其相關性。 如果你想強制計算其相關性,可以設定track_scores為 true。

預設排序

作為縮寫,你可以只指定要排序的欄位名稱:

“sort”: “number_of_children”`

欄位值預設以順序排列,而 _score 預設以倒序排列。

多級排序

如果我們想要合併一個查詢語句,並且展示所有匹配的結果集使用第一排序是date,第二排序是 _score:

GET /_search
{
    "query" : {
        "filtered" : {
            "query":   { "match": { "tweet": "manage text search" }},
            "filter" : { "term" : { "user_id" : 2 }}
        }
    },
    "sort": [
        { "date":   { "order": "desc" }},
        { "_score": { "order": "desc" }}
    ]
}

排序是很重要的。結果集會先用第一排序欄位來排序,當用用作第一欄位排序的值相同的時候, 然後再用第二欄位對第一排序值相同的文件進行排序,以此類推。

多級排序不需要包含 _score – 你可以使用幾個不同的欄位,如位置距離或者自定義數值。

字串引數排序

字元查詢也支援自定義排序,在查詢字串使用sort引數就可以:
GET /_search?sort=date:desc&sort=_score&q=search

為多值欄位排序

在為一個欄位的多個值進行排序的時候, 其實這些值本來是沒有固定的排序的– 一個擁有多值的欄位就是一個集合, 你準備以哪一個作為排序依據呢?

對於數字和日期,你可以從多個值中取出一個來進行排序,你可以使用min, max, avg 或 sum這些模式。 比說你可以在 dates 欄位中用最早的日期來進行排序:

"sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
}

多值欄位字串排序

被分析器(analyser)處理過的字元稱為analyzed field, analyzed字串欄位同時也是多值欄位,在這些欄位上排序往往得不到你想要的值。 比如你分析一個字元 “fine old art”,它最終會得到三個值。例如我們想要按照第一個詞首字母排序, 如果第一個單詞相同的話,再用第二個詞的首字母排序,以此類推,可惜 ElasticSearch 在進行排序時 是得不到這些資訊的。

當然你可以使用 min 和 max 模式來排(預設使用的是 min 模式)但它是依據art 或者 old排序, 而不是我們所期望的那樣。

為了使一個string欄位可以進行排序,它必須只包含一個詞:即完整的not_analyzed字串(未經分析器分詞並排序的原字串)。 當然我們需要對欄位進行全文字搜尋的時候還必須使用被 analyzed 標記的欄位。

在 _source 下相同的字串上排序兩次會造成不必要的資源浪費。 而我們想要的是同一個欄位中同時包含這兩種索引方式,我們只需要改變索引(index)的mapping即可。 方法是在所有核心欄位型別上,使用通用引數 fields對mapping進行修改。 比如,我們原有mapping如下:

"tweet": {
    "type":     "string",
    "analyzer": "english"
}

改變後的多值欄位mapping如下:

"tweet": { <1>
    "type":     "string",
    "analyzer": "english",
    "fields": {
        "raw": { <2>
            "type":  "string",
            "index": "not_analyzed"
        }
    }
}

<1> tweet 欄位用於全文字的 analyzed 索引方式不變。

<2> 新增的 tweet.raw 子欄位索引方式是 not_analyzed。

現在,在給資料重建索引後,我們既可以使用 tweet 欄位進行全文字搜尋,也可以用tweet.raw欄位進行排序:

GET /_search
{
    "query": {
        "match": {
            "tweet": "elasticsearch"
        }
    },
    "sort": "tweet.raw"
}

警告: 對 analyzed 欄位進行強制排序會消耗大量記憶體。

相關性簡介

我們曾經講過,預設情況下,返回結果是按相關性倒序排列的。 但是什麼是相關性? 相關性如何計算?

每個文件都有相關性評分,用一個相對的浮點數字段 _score 來表示 – _score 的評分越高,相關性越高。

查詢語句會為每個文件新增一個 _score 欄位。評分的計算方式取決於不同的查詢型別 – 不同的查詢語句用於不同的目的:fuzzy 查詢會計算與關鍵詞的拼寫相似程度,terms查詢會計算找到的內容與關鍵片語成部分匹配的百分比,但是一般意義上我們說的全文字搜尋是指計算內容與關鍵詞的類似程度。

ElasticSearch的相似度演算法被定義為 TF/IDF,即檢索詞頻率/反向文件頻率,包括一下內容:

檢索詞頻率::

檢索詞在該欄位出現的頻率?出現頻率越高,相關性也越高。 欄位中出現過5次要比只出現過1次的相關性高。

反向文件頻率::

每個檢索詞在索引中出現的頻率?頻率越高,相關性越低。 檢索詞出現在多數文件中會比出現在少數文件中的權重更低, 即檢驗一個檢索詞在文件中的普遍重要性。

欄位長度準則::

欄位的長度是多少?長度越長,相關性越低。 檢索詞出現在一個短的 title 要比同樣的詞出現在一個長的 content 欄位。

單個查詢可以使用TF/IDF評分標準或其他方式,比如短語查詢中檢索詞的距離或模糊查詢裡的檢索詞相似度。

相關性並不只是全文字檢索的專利。也適用於yes|no的子句,匹配的子句越多,相關性評分越高。

如果多條查詢子句被合併為一條複合查詢語句,比如 bool 查詢,則每個查詢子句計算得出的評分會被合併到總的相關性評分中。

理解評分標準

當除錯一條複雜的查詢語句時,想要理解相關性評分 _score 是比較困難的。ElasticSearch 在 每個查詢語句中都有一個explain引數,將 explain 設為 true 就可以得到更詳細的資訊。

GET /_search?explain <1>
{
   "query"   : { "match" : { "tweet" : "honeymoon" }}
}

<1> explain 引數可以讓返回結果新增一個 _score 評分的得來依據。

資料欄位

當你對一個欄位進行排序時,ElasticSearch 需要進入每個匹配到的文件得到相關的值。 倒排索引在用於搜尋時是非常卓越的,但卻不是理想的排序結構。

當搜尋的時候,我們需要用檢索詞去遍歷所有的文件。

當排序的時候,我們需要遍歷文件中所有的值,我們需要做反倒序排列操作。

為了提高排序效率,ElasticSearch 會將所有欄位的值載入到記憶體中,這就叫做”資料欄位”。

重要: ElasticSearch將所有欄位資料載入到記憶體中並不是匹配到的那部分資料。 而是索引下所有文件中的值,包括所有型別。

將所有欄位資料載入到記憶體中是因為從硬碟反向倒排索引是非常緩慢的。儘管你這次請求需要的是某些文件中的部分資料, 但你下個請求卻需要另外的資料,所以將所有欄位資料一次性載入到記憶體中是十分必要的。

ElasticSearch中的欄位資料常被應用到以下場景:

  1. 對一個欄位進行排序
  2. 對一個欄位進行聚合
  3. 某些過濾,比如地理位置過濾
  4. 某些與欄位相關的指令碼計算

毫無疑問,這會消耗掉很多記憶體,尤其是大量的字串資料 – string欄位可能包含很多不同的值,比如郵件內容。 值得慶幸的是,記憶體不足是可以通過橫向擴充套件解決的,我們可以增加更多的節點到叢集。

現在,你只需要知道欄位資料是什麼,和什麼時候記憶體不足就可以了。