搜索 – 基本工具
到目前為止,我們已經學習了Elasticsearch的分布式NOSQL文檔存儲,我們可以直接把JSON文檔扔到Elasticsearch中,然后直接通過ID來進行調取。但是Elasticsearch真正的強大之處在於將混亂變得有意義——將大數據變成大量的信息。
這也是我們使用JSON文檔而不是無規則數據的原因。Elasticsearch不僅僅只是存儲文檔,同時它還索引了這些文檔以便搜索。文檔中每一個字段都被索引並且可以被查詢。不僅如此,在一個查詢中,Elasticsearch可以使用所有索引,並且以驚人的速度返回結果。這是傳統數據庫永遠也不能企及的。
這個搜索可以是:
- 類似於
年齡
、性別
、加入日期
等結構化數據,類似於在SQL中進行查詢。 - 全文搜索,查找整個文檔中匹配關鍵字的內容,並根據相關性
- 或者結合兩者。
雖然很多搜索操作是安裝好Elasticsearch就可以用的,但是想發揮它的潛力,你需要明白以下內容:
名字 | 說明 |
---|---|
映射 (Mapping) | 每個字段中的數據如何被解釋 |
統計 (Analysis) | 可搜索的全文是如何被處理的 |
查詢 (Query DSL) | Elasticsearch使用的靈活強的查詢語言 |
上述的每一個內容都是一個大的主題,我們將會在之后的《深入搜索》中詳細探討它們。 本章中我們將針對先去介紹它們三個的基本概念 —— 已經足夠能幫助你理解搜索是如何運作的了。
我們將向你介紹search
API的簡單實用方式。
測試數據
我們本章使用的文檔可以在下面的git中找到:https://gist.github.com/clintongormley/8579281
你可以下載然后導入到你的shell中以方便你的學習使用。
5.1 空白搜索
空白搜索
搜索API最常用的一種形式就是空白搜索,也就是不加任何查詢條件的,只是返回集群中所有文檔的搜索。
GET /_search
返回內容如下(有刪減):
{
"hits" : { "total" : 14, "hits" : [ { "_index": "us", "_type": "tweet", "_id": "7", "_score": 1, "_source": { "date": "2014-09-17", "name": "John Smith", "tweet": "The Query DSL is really powerful and flexible", "user_id": 2 } }, ... 9 個結果被隱藏 ... ], "max_score" : 1 }, "took" : 4, "_shards" : { "failed" : 0, "successful" : 10, "total" : 10 }, "timed_out" : false }
hits
返回內容中最重要的內容就是hits
,它指明了匹配查詢的文檔的總數
,hits
數組里則會包含前十個匹配文檔——也就是搜索結果。
hits
數組中的每一條結果都包含了文檔的_index
, _type
以及_id
信息,以及_source
字段。這也就意味着你可以直接從搜索結果中獲取到整個文檔的內容。這與其他搜索引擎只返回給你文檔編號,還需要自己去獲取文檔是截然不同的。
每一個元素還擁有一個_score
字段。這個是相關性評分,這個數值表示當前文檔與查詢的匹配程度。通常來說,搜索結果會先返回最匹配的文檔,也就是說它們會按照_score
由高至低進行排列。在這個例子中,我們並沒有聲明任何查詢,因此_score
就都會返回1
max_score
數值會顯示所有匹配文檔中的_score
的最大值。
took
took
數值告訴我們執行這次搜索請求所耗費的時間有多少毫秒。
shards
_shards
告訴了我們參與查詢分片的總數,以及有多少successful
和failed
。通常情況下我們是不會得到失敗的反饋,但是有的時候它會發生。如果我們的服務器突然出現了重大事故,然后我們丟失了同一個分片中主從兩個版本的數據。在查詢請求中,無法提供可用的備份。這種情況下,Elasticsearch就會返回`failed提示,但是它還會繼續返回剩下的內容。
timeout
timed_out
數值告訴了我們查詢是否超時。通常,搜索請求不會超時。如果相比完整的結果你更需要的是快速的響應時間,這是你可以指定timeout
值,例如10
、"10ms"
(10毫秒)或者"1s"
(1秒鍾):
GET /_search?timeout=10ms
Elasticsearch會盡可能地返回你指定時間內它所查到的內容。
Timeout並不是終止者
這里應該強調一下timeout
並不會終止查詢,它只是會在你指定的時間內返回當時已經查詢到的數據,然后關閉連接。在后台,其他的查詢可能會依舊繼續,盡管查詢結果已經被返回了。
使用超時是因為你要保障你的品質,並不是因為你需要終止你的查詢。
5.2 多索引多類型
多索引,多類型
你是否注意到了《空白搜索》一章節的文檔中包含了很多不同的類型 —— user
與tweet
,它們也分別來自us
、gb
這兩個不同的索引?
當我們沒有特別指定一個索引或者類型的時候,我們將會搜索整個集群中的所有文檔。Elasticsearch會把搜索請求轉發給集群中的每一個主從分片,然后按照結果的相關性得到前十名,並將它們返回給我們。
然而,往往我們只需要在某一個特定的索引的幾個類型中進行搜索。我們可以通過在URL中定義它來實現這個功能:
URL | 說明 |
---|---|
/_search |
搜索所有的索引和類型 |
/gb/_search |
搜索索引gb 中的所有類型 |
/gb,us/_search |
搜索索引gb 以及us 中的所有類型 |
/g*,u*/_search |
搜索所有以g 或u 開頭的索引中的所有類型 |
/gb/user/_search |
搜索索引gb 中類型user 內的所有文檔 |
/gb,us/user,tweet/_search |
搜索索引gb 和 索引us 中類型user 以及類型tweet 內的所有文檔 |
/_all/user,tweet/_search |
搜索所有索引中類型為user 以及tweet 內的所有文檔 |
當你在一個索引中搜索的時候,Elasticsearch或將你的搜索請求轉發給相應索引中的所有主從分片,然后收集每一個分片的結果。在多個索引中搜索也是相同的流程,只不過是增加了一些參與分片。
重要提示
搜索一個擁有五個主分片的索引與搜索五個都只擁有一個主分片是完全一樣的。
在后面,你將會了解到如何利用這一點,來根據你的需要靈活打造系統。
5.3 分頁
分頁
在《空白搜索》一節中,搜索結果告訴我們在集群中共有14個文檔匹配我們的(空白)查詢。但是在hits
數組中只有10個文檔。我們怎樣才能看到其他的呢?
與SQL使用LIMIT
來控制單“頁”數量類似,Elasticsearch使用的是from
以及size
兩個參數:
參數 | 說明 |
---|---|
size |
每次返回多少個結果,默認值為10 |
from |
忽略最初的幾條結果,默認值為0 |
假設每頁顯示5條結果,那么1至3頁的請求就是:
GET /_search?size=5 GET /_search?size=5&from=5 GET /_search?size=5&from=10
當心不要一次請求過多或者頁碼過大的結果。它們會在返回前排序。一個請求會經過多個分片。每個分片都會生成自己的排序結果。然后再進行集中整理,以確保最終結果的正確性。
分布式系統中的大頁碼頁面
為了說明白為什么頁碼過大的請求會產生問題,我們就先預想一下我們在搜索一個擁有5個主分片的索引。當我們請求第一頁搜索的時候,每個分片產生自己前十名,然后將它們返回給請求節點,然后這個節點會將50條結果重新排序以產生最終的前十名。
現在想想一下我們想獲得第1,000頁,也就是第10,001到第10,010條結果,與之前同理,每一個分片都會先產生自己的前10,010名,然后請求節點統一處理這50,050條結果,然后再丟棄掉其中的50,040條!
現在你應該明白了,在分布式系統中,大頁碼請求所消耗的系統資源是呈指數式增長的。這也是為什么網絡搜索引擎不會提供超過1,000條搜索結果的原因。
TIP
在《重索引》一章中,我們將詳細探討如何才能高效地獲取大量數據。
5.4 查詢語句
精簡 搜索
搜索的API分為兩種:其一是通過參數來傳遞查詢的“精簡版”查詢語句(query string),還有一種是通過JSON來傳達豐富的查詢的完整版請求體(request body),這種搜索語言被稱為查詢DSL。
查詢語句在行命令中運行點對點查詢的時候非常實用。比如我想要查詢所有tweet
類型中,所有tweet
字段為"elasticsearch"
的文檔:
GET /_all/tweet/_search?q=tweet:elasticsearch
下一個查詢是想要尋找name
字段為"john"
且tweet
字段為"mary"
的文檔,實際的查詢就是:
+name:john +tweet:mary
但是經過百分號編碼(percent encoding)處理后,會讓它看起來稍顯神秘:
GET /_search?q=%2Bname%3Ajohn+%2Btweet%3Amary
前綴"+"
表示必須要滿足我們的查詢匹配條件,而前綴"-"
則表示絕對不能匹配條件。沒有+
或者-
的表示可選條件。匹配的越多,文檔的相關性就越大。
字段_all
下面這條簡單的搜索將會返回所有包含"mary"
字符的文檔:
GET /_search?q=mary
在之前的例子中,我們搜索tweet
或者name
中的文字。然而,搜索的結果顯示"mary"
在三個不同的字段中:
-
- 用戶的名字為"Mary"
- 6個"Mary"發送的推文
- 1個"@mary"
那么Elasticsearch是如何找到三個不同字段中的內容呢?
當我們在索引一個文檔的時候,Elasticsearch會將所有字段的數值都匯總到一個大的字符串中,並將它索引成一個特殊的字段_all
:
{
"tweet": "However did I manage before Elasticsearch?", "date": "2014-09-14", "name": "Mary Jones", "user_id": 1 }
就好像我們已經添加了一個叫做_all
的字段:
"However did I manage before Elasticsearch? 2014-09-14 Mary Jones 1"
除非指定了字段名,不然查詢語句就會搜索字段_all
。
TIP: 在你剛開始創建程序的時候你可能會經常使用_all
這個字段。但是慢慢的,你可能就會在請求中指定字段。當字段_all
已經沒有使用價值的時候,那就可以將它關掉。之后的《字段all》一節中將會有介紹
更加復雜的查詢
再實現一個查詢:
-
- 字段
name
包含"mary"
或"john"
date
大於2014-09-10
_all
字段中包含"aggregations"
或"geo"
- 字段
+name:(mary john) +date:>2014-09-10 +(aggregations geo)
最終處理完的語句可讀性可能很差:
?q=%2Bname%3A(mary+john)+%2Bdate%3A%3E2014-09-10+%2B(aggregations+geo)
正如你所看到的,這個簡明查詢語句是出奇的強大。在查詢語句語法中,有關於它詳細的介紹。借助它我們就可以在開發的時候提高很多效率。
不過,你也會發現簡潔帶來的易讀性差和難以調試,以及它的脆弱:當其中出現-
, :
, /
或者 "
時,它就會返回錯誤提示。
最后要提一句,任何用戶都可以通過查詢語句來訪問臃腫的查詢,或許會得到一些私人的信息,或許會通過大量的運算將你的集群壓垮!
TIP
出於以上原因,我們不建議你將查詢語句直接暴露給用戶,除非是你信任的可以訪問數據與集群的權限用戶。
與此同時,在生產環境中,我們經常會使用到查詢語句。在了解更多關於搜索的知識前,我們先來看一下它是怎樣運作的。