Elasticsearch(四):Search運行機制

Search 執行

Search 執行時分為 QueryFetch 兩個階段。

Query 階段

  • coordinating node 接受請求后在所有主副分片中隨機選擇 shard 數個分片發送 search 請求。

  • 被選中的分片會分別執行請求并排序,返回 from + size 個文檔 id 和排序值給 coordinating node

Fetch 階段

  • coordinating node 獲取到文檔 id 后向相關分片發送 multi_get 請求。
  • 各分片返回文檔給 coordinating node
  • coordinating node 將排序和分頁后的結果返回給客戶端。

相關性算分

es 的每個 shard 對應一個 lucene index,即一個獨立的算分單位。當設置多個分片時,查詢的文檔在不同分片上會分別計算相關性得分,可能會導致最終的得分是不準的。es 提供參數 search_type=dfs_query_then_fetch 使得 es 能夠在獲得所有文檔后重新計算相關性得分,此種方式會消耗較多 cpu 和內存。

GET /test/_search?search_type=dfs_query_then_fetch

排序

sort 參數用于指定排序的字段和方式。_doc 排序使用文檔內部 id,即使用索引順序作為排序規則。

GET /test/_search
{
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    },
    {
      "_doc": {
        "order": "desc"
      }
    }
  ]
}

doc values 與 fielddata

es 不允許直接對 text 類型字段進行排序。由于排序是使用的是文檔內容,無法用到倒排索引,而是需要通過文檔 id 獲取文檔字段的原始內容。es 對此提供兩種實現方式

doc values

es 中除了 text 字段(不支持)都默認開啟了doc values,其在寫入文檔時與倒排索引一起生成并寫入磁盤,其結構為文檔 id 到文檔指定字段的 value。這樣在聚合分析時就不會占用內存,但是索引時會減慢索引的速度,占用額外的磁盤資源。如果一個字段明確不會被聚合分析,可以在 mapping 中通過 doc_values 參數關閉:

PUT /test
{
  "mappings": {
    "properties": {
      "hobby": {
        "type": "keyword",
        "doc_values": false
      }
    }
  }
}

fielddata

doc values 是不支持對 text 類型使用的,如果需要對能夠分詞的 text 類型進行排序,就需要使用 fielddatafielddata 在搜索時于內存中創建,其不會額外占用磁盤資源,但是當文檔較多時即時創建會花費過多時間、占用較多內存。fielddata 默認時關閉的,可以通過修改 mappingfielddata 參數使得字段的 fielddata 特性立即可用:

PUT /test
{
  "mappings": {
    "properties": {
      "hobby": {
        "type": "text",
        "fielddata": true
      }
    }
  }
}

使用 fielddata 不代表能夠真正對 text 類型的值進行排序,其結果為文檔 id 到每個分詞,即僅支持對分詞結果的一部分進行排序。

GET /test/_search
{
  "sort": [
    {
      "hobby": {
        "order": "desc"
      }
    }
  ]
}

// ...
    "hits" : [
      {
        "_index" : "test2",
        "_type" : "_doc",
        "_id" : "myi4Bm8BVyg6ro4E7nPl",
        "_score" : null,
        "_source" : {
          "hobby" : "play games"
        },
        "sort" : [
          "play"
        ]
      }
    ]
// ...

fielddata 會首先對分詞結果進行排序,來選擇用作文檔排序的詞,召回結果中會通過 sort 字段說明用作排序的詞。

docvalues_fields

使用 docvalue_fields 參數可以指定召回 fielddatadoc values 存儲的值:

GET /test2/_search
{
  "docvalue_fields": [
    "hobby"
  ]
}

// ...
    "hits" : [
      {
        "_index" : "test2",
        "_type" : "_doc",
        "_id" : "myi4Bm8BVyg6ro4E7nPl",
        "_score" : 1.0,
        "_source" : {
          "hobby" : "play games"
        },
        "fields" : {
          "hobby" : [
            "games",
            "play"
          ]
        }
      }
    ]
// ...

深度分頁

es 提供 fromsize 來指定分頁,但是每次執行分頁并非直接獲取 size 個數據,而是從每個分片獲取 from+size 個數據后再排序選取。頁數越深,占用的內存越多,耗時越長。es 通過 index.max_result_window 限定最多取 10000 數據。

scroll

es 提供 scroll 用來生成數據快照。當使用 scroll 請求返回單頁結果時,可以檢索出大量結果(甚至全部)生成快照。請求結果返回 _scroll_id 是進行下一頁查詢的參數,通過 scroll 可以完成對快照的遍歷。

指定使用 scroll 即其保留的時間,如 1m 代表快照保留一分鐘。

GET /test/_search?scroll=1m
{
  "size": 2
}

{
  "_scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAB_2MWbmFUODhqSGVRZldTQzk4OTRQaUVvdw==",
// ...
}

使用返回的 _scroll_id 作為參數進行下一次迭代,直到返回的結果為空:

GET /_search/scroll
{
  "scroll": "1m",
  "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAB_2MWbmFUODhqSGVRZldTQzk4OTRQaUVvdw=="
}

scroll 生成的是一份數據快照,因此不能用作實時搜索,盡量只使用 _doc 排序的方式。scroll 會占用內存,可以選擇刪除 scroll

// 指定刪除 scroll
DELETE /_search/scroll
{
  "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAACABAWbmFUODhqSGVRZldTQzk4OTRQaUVvdw=="
}

// 刪除所有 scroll
DELETE /_search/scroll/_all

search after

search after 是通過前次查詢指定的排序值對當前查詢進行定位,使得各分片返回的文檔數控制在 size 個內。search after 是實時的,使用的排序值必須能夠唯一排序定位,不支持通過 from 參數指定查詢頁數,并且只能往后翻頁。

GET /test/_search
{
  "size": 1,
  "sort": [
    {
      "_id": {
        "order": "desc"
      }
    }
  ]
}

// ...
    "hits" : [
        // ...
        "sort" : [
          "rCdxA28BVyg6ro4EWU8p"
        ]
// ...

使用上次返回結果的排序定位值指定 search_after 參數:

GET /test/_search
{
  "size": 1,
  "sort": [
    {
      "_id": {
        "order": "desc"
      }
    }
  ],
  "search_after": [
    "rCdxA28BVyg6ro4EWU8p"
  ]
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容