Elasticsearch檢索實戰 和mysql的對比
隨著公司房源資料的急劇增多,現搜尋引擎 Solr 的搜尋效率和建立索引效率顯著降低,而 Elasticsearch 是一個實時的分散式搜尋和分析引擎,它是基於全文搜尋引擎 Apache Lucene 之上,接入 Elasticsearch 是必然之選。本文是我學習使用 Elasticsearch 檢索的筆記。
Elasticsearch 支援 RESTful API 方式檢索,查詢結果以 JSON 格式響應,文中示例資料見 這裡。有關 Elasticsearch 詳細使用說明,見 官方文件。
Url
檢索 url 中需包含 索引名,_search
為查詢關鍵字。例如 http://es.fanhaobai.com/rooms/_search
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 種檢索語句。
檢索語句
Query string
我們可以直接在 get 請求時的 url 後追加q=
查詢引數,這種方法常被稱作 query string 搜尋,因為我們像傳遞 url 引數一樣去傳遞查詢語句。例如查詢小區 id 為 1111027374551 的房源資訊:
GET /rooms/_search?q=resblockId:1111027374551
//查詢結果,無關資訊已省略
{
"hits": [
{
"_source": {
"resblockId": "1111027374551",
"resblockName": "國風北京二期",
... ...
}
}
]
}
雖然查詢字串便於查詢特定的搜尋,但是它也有侷限性。
DSL
DSL 查詢以 JSON 請求體的形式出現,它允許構建更加複雜、強大的查詢。DSL 方式查詢上述 query string 查詢條件則為:
POST /rooms/_search
{
"query": {
"term": {
"resblockId": "1111027374551"
}
}
}
term 語句為過濾型別之一,後面再進行說明。使用 DSL 語句查詢支援 filter(過濾器)、match(全文檢索)等複雜檢索場景。
基本檢索
Elasticsearch 支援為 2 種檢索行為,它們都是使用 DSL 語句來表達檢索條件,分別為 query (結構化查詢)和 filter(結構化搜尋)。
說明:後續將使用 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、1293、#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-query
API來驗證查詢合法性。
GET /room/_validate/query
{
"query": { "resblockName": { "match": "嘉" }}
}
合法的 query 返回資訊:
{
"valid": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
}
}
最後
別的業務線已經投入 Elasticsearch 使用有段時間了,找房業務線正由 Solr 切換為 Elasticsearch,各個系統有一個探索和磨合的過程。當然,Elasticsearch 我們已經服務化了,對 DSL 語法也進行了一些簡化,同時支援了定製化業務。
作者:fanhaobai
連結:https://www.jianshu.com/p/290774733c30
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。