Elasticsearch檢索實戰

首發於 樊浩柏科學院

隨着公司房源數據的急劇增多,現搜索引擎 Solr 的搜索效率和創建索引效率顯著下降,而 Elasticsearch 是一個實時的分佈式搜索和分析引擎,它是基於全文搜索引擎 Apache Lucene 之上,接入 Elasticsearch 是必然之選。本文是我學習使用 Elasticsearch 檢索的筆記。html

Elasticsearch 支持 RESTful API 方式檢索,查詢結果以 JSON 格式響應,文中示例數據見 這裏。有關 Elasticsearch 詳細使用說明,見 官方文檔git

Url

檢索 url 中需包含 索引名_search爲查詢關鍵字。例如 http://es.fanhaobai.com/rooms/_search 的 rooms 爲索引名,此時表示無任何條件檢索,檢索結果爲:github

GET /rooms/_search

{
   "took": 6,
   "timed_out": false,
   "_shards": { ... },
   "hits": {
      "total": 3,
      "max_score": 1,
      "hits": [
         {
            "_index": "rooms",
            "_type": "room_info",
            "_id": "3",
            "_score": 1,
            "_source": {
               "resblockId": "1111027377528",
               "resblockName": "金隅麗港城",
               "houseId": 1087599828743,
               "cityCode": 110000,
               "size": 10.5,
               "bizcircleCode": [ "18335711" ],
               "bizcircleName": [ "望京" ],
           "price": 2300
            }
         },
         {
            ... ...
            "_source": {
               "resblockId": "1111047349969",
               "resblockName": "融澤嘉園",
               "houseId": 1087817932553,
               "cityCode": 110000,
               "size": 10.35,
               "bizcircleCode": [ "611100314" ],
               "bizcircleName": [ "西二旗" ],
               "price": 2500
            }
         },
     ... ...
      ]
   }
}
注:Elasticsearch 官方偏向於使用 GET 方式(能更好描述信息檢索的行爲),GET 方式能夠攜帶請求體,可是因爲不被普遍支持,因此 Elasticsearch 也支持 POST 請求。後續查詢語言使用 POST 方式。

當咱們肯定了須要檢索文檔的 url 後,就可使用查詢語法進行檢索,Elasticsearch 支持如下 Query string(查詢字符串)和 DSL(結構化)2 種檢索語句。sql

檢索語句

Query string

咱們能夠直接在 get 請求時的 url 後追加q=查詢參數,這種方法常被稱做 query string 搜索,由於咱們像傳遞 url 參數同樣去傳遞查詢語句。例如查詢小區 id 爲 1111027374551 的房源信息:apache

GET /rooms/_search?q=resblockId:1111027374551

//查詢結果,無關信息已省略
{
   "hits": [
      {
         "_source": {
            "resblockId": "1111027374551",
            "resblockName": "國風北京二期",
            ... ...
         }
      }
   ]
}

雖然查詢字符串便於查詢特定的搜索,可是它也有侷限性。緩存

DSL

DSL 查詢以 JSON 請求體的形式出現,它容許構建更加複雜、強大的查詢。DSL 方式查詢上述 query string 查詢條件則爲:elasticsearch

POST /rooms/_search

{
   "query": {
      "term": {
         "resblockId": "1111027374551"
      }
   }
}

term 語句爲過濾類型之一,後面再進行說明。使用 DSL 語句查詢支持 filter(過濾器)、match(全文檢索)等複雜檢索場景。分佈式

基本檢索

Elasticsearch 支持爲 2 種檢索行爲,它們都是使用 DSL 語句來表達檢索條件,分別爲 query (結構化查詢)和 filter(結構化搜索)。ide

說明:後續將使用 SQL 對比 DSL 語法進行搜索條件示例。性能

結構化查詢

結構化查詢支持全文檢索,會對檢索結果進行相關性計算。使用結構化查詢,須要傳遞 query 參數:

{ "query": your_query }
//your_query爲{}表示空查詢
注:後續查詢中再也不列出 query 參數,只列出 your_query(查詢內容)。

match_all查詢

match_all 查詢簡單的匹配全部文檔。在沒有指定查詢方式時,它是默認的查詢。查詢全部房源信息:

-- SQL表述
SELECT * FROM rooms

match_all 查詢爲:

{ "match_all": {}}

match查詢

match 查詢爲全文搜索,相似於 SQL 的 LIKE 查詢。查詢小區名中包含「嘉」的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE resblockName LIKE '%嘉%'

match 查詢爲:

{ "match": { "resblockName": "嘉" }}
//結果
"hits": [
    {
        "_source": {
            "resblockId": "1111047349969",
            "resblockName": "融澤嘉園",
            ... ...
        }
    }
]

multi_match查詢

multi_match 查詢能夠在多個字段上執行相同的 match 查詢:

{
    "multi_match": {
        "query":  "京",
        "fields": [ "resblockName", "bizcircleName" ]
    }
}

range查詢

range 查詢能檢索出那些落在指定區間內的文檔,相似於 SQL 的 BETWEEN 操做。range 查詢被容許的操做符有:

操做符 操做關係
gt 大於
gte 大於等於
lt 小於
lte 小於等於

查詢價格在 (2000, 2500] 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE price BETWEEN 2000 AND 2500 AND price != 2000

range 查詢爲:

{
    "range": {
        "price": {
            "gt": 2000,
            "lte": 2500
        }
    }
}

term查詢

term 查詢用於精確值匹配,多是數字、時間、布爾。例如查詢房屋 id 爲 1087599828743 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE houseId = 1087599828743

term 查詢爲:

{ "term": { "houseId": 1087599828743 }}

terms查詢

terms 查詢同 term 查詢,但它容許指定多值進行匹配,相似於 SQL 的 IN 操做。例如查詢房屋 id 爲 1087599828743 或者 1087817932342 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE houseId IN (1087599828743, 1087817932342)

terms 查詢爲:

{ "terms": { "houseId": [ 1087599828743, 1087817932342 ] }}
term 查詢和 terms 查詢都不分析輸入的文本, 不會進行相關性計算。

exists查詢和missing查詢

exists 查詢和 missing 查詢被用於查找那些指定字段中有值和無值的文檔,相似於 SQL 中的 IS NOT NULL 和 IS NULL 查詢。查詢價格有效的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE price IS NOT NULL

exists 查詢爲:

{ "exists": { "field": "price" }}

bool查詢

咱們時常須要將多個條件的結構進行邏輯與和或操做,等同於 SQL 的 AND 和 OR,這時就應該使用 bool 子句合併多子句結果。 共有 3 種 bool 查詢,分別爲 must(AND)、must_not(NOT)、should(OR)。

操做符 描述
must AND 關係,必須 匹配這些條件才能檢索出來
must_not NOT 關係,必須不 匹配這些條件才能檢索出來
should OR 關係,至少匹配一條 條件才能檢索出來
filter 必須 匹配,不參與評分

查詢小區中包含「嘉」字或者房屋 id 爲 1087599828743 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE (resblockName LIKE '%嘉%' OR houseId = 1087599828743) AND (cityCode = 110000)

bool 查詢爲:

{
  "bool": {
     "must": {
        "term": {"cityCode": 110000 }
     },
     "should": [
        { "term": { "houseId": 1087599828743 }},
        { "match": { "resblockName": "嘉" }}
     ]
  }
}

使用 filter 語句來使得其子句不參與評分過程,減小評分能夠有效地優化性能。重寫前面的例子:

{
  "bool": {
     "should": [
        { "match": { "resblockName": "嘉" }}
     ],
     "filter" : {
        "bool": {
           "must": { "term": { "cityCode": 110000 }},
           "should": [
              { "term": { "houseId": 1087599828743 }}
           ]
        }
     }
  }
}

bool 查詢能夠相互的進行嵌套,已完成很是複雜的查詢條件。

constant_score查詢

constant_score 查詢將一個不變的常量評分應用於全部匹配的文檔。它被常常用於你只須要執行一個 filter(過濾器)而沒有其它查詢(評分查詢)的狀況下。

{
    "constant_score": {
        "filter": {
            "term": { "houseId": 1087599828743 } 
        }
    }
}

結構化搜索

結構化搜索的查詢適合肯定值數據(數字、日期、時間),這些類型數據都有明確的格式。結構化搜索結果始終是是或非,結構化搜索不關心文檔的相關性或分數,它只是簡單的包含或排除文檔,因爲結構化搜索使用到過濾器,在查詢時須要傳遞 filter 參數,因爲 DSL 語法查詢必須以 query 開始,因此 filter 須要放置在 query 裏,所以結構化查詢的結構爲:

{
    "query": {
        "constant_score": { 
            "filter": {
                //your_filters
            }
        }
    }
}
注:後續搜索中再也不列出 query 參數,只列出 your_filters(過濾內容)。

結構化搜索同樣存在不少過濾器 term、terms、range、exists、missing、bool,咱們在結構化查詢中都已經接觸過了。

term搜索

最爲經常使用的 term 搜索用於查詢精確值,能夠用它處理數字(number)、布爾值(boolean)、日期(date)以及文本(text)。查詢小區 id 爲 1111027377528 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE resblockId = "1111027377528"

term 搜索爲:

{ "term": { "resblockId": "1111027377528" }}

相似XHDK-A-1293-#fJ3這樣的文本直接使用 term 查詢時,可能沒法獲取到指望的結果。是由於 Elasticsearch 在創建索引時,會將該數據分析成 xhdk、a、129三、#fj3 字樣,這並非咱們指望的,能夠經過指定 not_analyzed 告訴 Elasticsearch 在創建索引時無需分析該字段值。

terms搜索

terms 搜索使用方式和 term 基本一致,而 terms 是搜索字段多值的狀況。查詢商圈 code 爲 18335711 或者 611100314 的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE bizcircleCode IN (18335711, 611100314)

terms搜索爲:

{ "terms": { "bizcircleCode": [ "18335711", "611100314" ] }}

range搜索

在進行範圍過濾查詢時使用 range 搜索,支持數字、字母、日期的範圍查詢。查詢面積在 [15, 25] 平米之間的房源信息:

-- SQL表述
SELECT * FROM rooms WHERE size BETWEEN 10 AND 25

range 搜索爲:

{
    "range": {
        "size": {
            "gte": 10,
            "lte": 25
        }
    }
}

range 搜索使用在日期上:

{
    "range": {
        "date": {
            "gt": "2017-01-01 00:00:00",
            "lt": "2017-01-07 00:00:00"
        }
    }
}

exists和missing搜索

exists 和 missing 搜索是針對某些字段值存在和缺失的查詢。查詢房屋面積存在的房源列表:

-- SQL表述
SELECT * FROM rooms WHERE size IS NOT NULL

exists 搜索爲:

{"exists": { "field": "size" }}

missing 搜索恰好和 exists 搜索相反,但語法一致。

bool組合搜索

bool 過濾器是爲了解決過濾多個值或字段的問題,它能夠接受多個其餘過濾器做爲子過濾器,並將這些過濾器結合成各式各樣的邏輯組合。

bool 過濾器的組成部分,同 bool 查詢一致:

{
   "bool": {
      "must":     [],
      "should":   [],
      "must_not": [],
   }
}

相似於以下 SQL 查詢條件:

SELECT * FROM rooms WHERE (bizcircleCode = 18335711 AND price BETWEEN 2000 AND 2500) OR (bizcircleCode = 611100314 AND price >= 2500)

使用 bool 過濾器實現爲:

{
   "bool": {
       "should": [
           { 
              "term": { "bizcircleCode": "18335711" },
              "range": { "price": { "gte": 2000, "lte": 25000 }}
           }, 
           { 
              "term": { "bizcircleCode": "611100314" },
              "range": { "price": { "gte": 2500 }}
           } 
       ]
   }
}
區別:結構化查詢會進行相關性計算,所以不會緩存檢索結果;而結構化搜索會緩存搜索結果,所以具備較高的檢索效率,在不須要全文搜索或者其它任何須要影響相關性得分的查詢中建議只使用結構化搜索。固然,結構化查詢和結構化搜索能夠配合使用。

聚合

該部分較複雜,已單獨使用文章進行說明,見 Elasticsearch檢索 — 聚合和LBS 部分。

_source子句

某些時候可能不須要返回文檔的所有字段,這時就可使用 _source 子句指定返回須要的字段。只返回須要的房源信息字段:

{
   "_source": [ "cityCode", "houseId", "price", "resblockName" ]
}

sort子句

簡單排序

排序是使用比較多的推薦方式,在 Elasticsearch 中,默認會按照相關性進行排序,相關性得分由一個浮點數進行表示,並在搜索結果中經過_score參數返回(未參與相關性評分時分數爲 1), 默認是按_score降序排序。

sort 方式有 desc、asc 兩種。將房源查詢結果按照價格升序排列:

{
   "sort": {
      "price": { "order": "asc" }}
   }
}

多級排序

當存在多級排序的場景時,結果首先按第一個條件排序,僅當結果集的第一個 sort 值徹底相同時纔會按照第二個條件進行排序,以此類推。

{
   "sort": [
      { "price": { "order": "asc" }},
      { "_score": { "order": "desc" }}  //price一直時,按照相關性降序
   ]
}

字段多指排序

當字段值爲 多值字段多指排序,Elasticsearch 會對於數字或日期類型將多值字段轉爲單值。轉化有 min 、max 、avg、 sum 這 4 種模式。

例如,將房源查詢結果按照商圈 code 升序排列:

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

分頁子句

和 SQL 使用 LIMIT 關鍵字返回單 page 結果的方法相同,Elasticsearch 接受 from(初始結果數量)和 size(應該返回結果數量) 參數:

{
   "size": 8,
   "from": 1
}

驗證查詢合法性

在實際應用中,查詢可能變得很是的複雜,理解起來就有點困難了。不過可使用validate-queryAPI來驗證查詢合法性。

GET /room/_validate/query
{
   "query": { "resblockName": { "match": "嘉" }}
}

合法的 query 返回信息:

{
   "valid":         false,
   "_shards": {
      "total":       1,
      "successful":  1,
      "failed":      0
   }
}

最後

別的業務線已經投入 Elasticsearch 使用有段時間了,找房業務線正由 Solr 切換爲 Elasticsearch,各個系統有一個探索和磨合的過程。固然,Elasticsearch 咱們已經服務化了,對 DSL 語法也進行了一些簡化,同時支持了定製化業務。另外,使用 elasticsearch-sql 插件可讓 Elasticsearch 也支持 SQL 操做。

相關文章 »

相關文章
相關標籤/搜索