公司專案的的日誌埋點是發到 Elasticsearch
上的,有時開發會去在 kibana
上查詢相關的日誌資訊,用於診斷使用者的問題。但是 kibana
太過重量級,查詢起來比較慢。於是學習了下 Elasticsearch
的用法,此文章個人也只是點到為止。工作時夠用就行了,所有有些地方可能並沒有詳細說明。只能充當入門讀物吧。
當前的筆記只介紹 Elasticsearch
的搜尋部分。
文章中的搜尋都是在 kibana
的 Dev tools
進行查詢的。
準備工作
需要安裝 Elasticsearch
、kibana
、elasticsearch-analysis-ik
具體的安裝方式,這裡就不再闡述了。(安裝完,記得重啟 Elasticsearch
)
重啟完成後,開啟 kibana
的 Dev tools
,輸入下面的DSL程式碼,並執行:
PUT books
{
"settings": {
"number_of_replicas": 1,
"number_of_shards": 3
},
"mappings": {
"IT": {
"properties": {
"id": {
"type": "long"
},
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"language": {
"type": "keyword"
},
"author": {
"type": "keyword"
},
"price": {
"type": "double"
},
"year": {
"type": "date",
"format": "yyyy-MM-dd"
},
"description": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
}
複製程式碼
執行好後,下載 books.json 檔案,並進行匯入。如果你安裝的 Elasticsearch
版本小於6.0
,使用下面的命令進行匯入 books.json
:
curl -XPOST "http://localhost:9200/_bulk?pretty" --data-binary @books.json
複製程式碼
如果你的 Elasticsearch
版本大於6.0
,則使用下面的命令進行匯入:
curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/_bulk?pretty" --data-binary @books.json
複製程式碼
基本搜尋
返回指定index的所有文件
GET books/_search
{
"query": {
"match_all": {}
}
}
複製程式碼
可以簡寫為:
GET books/search
複製程式碼
查詢指定欄位中包含給定單詞的文件
使用term
來進行查詢,term
查詢不會被解析,只有查詢的詞和文件中的詞精確匹配才會被搜尋到,應用場景為:查詢人名、地名等需要精準匹配的需求。
查詢title欄位中含有思想
的書籍
GET books/_search
{
"query": {
"term": {
"title": "思想"
}
}
}
複製程式碼
返回如下:
對查詢結果進行分頁
有時查詢時,會返回成千上萬的資料,這種情況下,分頁的作用就出來了。
分頁有兩個屬性,分別是from
、size
- from: 從何處開始
- size: 返回的文件最大數量
可以理解為:我從from
位置把剩下的文件全部返回,然後size
限制了返回的數量。
用js程式碼來詮釋就是:
const from = 100 - 1; // 陣列從0開始,需要減一
const size = 10;
const data = [1, 2, 3, ..., 999, 1000];
const fromDate = data.splice(from);
const result = fromData.splice(0, size);
console.log(result) //=> [100, 101, 102, 103, 104, 105, 106, 107, 108, 109]
複製程式碼
限制返回欄位
一般我們查詢時,都是為了觀察某一個欄位,而不是想看全部的欄位。而如果是預設情況下,Elasticsearch
會返回的文件的全部欄位資訊。會對工作造成一定的影響。於是,Elasticsearch
提供了一個介面,用於限制返回的欄位。假設我只需要 title
和 author
欄位:
GET books/_search
{
"_source": ["title", "author"],
"query": {
"term": {
"title": "java"
}
}
}
複製程式碼
結果如圖:
基於最小評分過濾
因為 Elasticsearch
在做普通的搜尋時,是採用相關性進行搜尋的,而相關性是由評分
取決的。所以當我們進行模糊搜尋時,Elasticsearch
可能會返回一些相關性不那麼高的文件。所以我們可以通過 Elasticsearch
提供的介面,來設定一個評分最低標準,低於這個標準的文件,將不會出現在結果頁中。
比如,我想搜尋 title
裡包含 java
的文件,並且評分不低於0.7
:
GET books/_search
{
"min_score": 0.7,
"query": {
"term": {
"title": "java"
}
}
}
複製程式碼
結果如圖:
高亮關鍵字
有時,我們會把 Elasticsearch
結果直接匯入到網頁中,這個時候需要高亮關鍵字,讓使用者更加清楚自己想要的東西,Elasticsearch
已經提供了一個介面,比如我想讓搜尋出來的結果中的關鍵字高亮:
GET books/_search
{
"_source": ["title"],
"min_score": 0.7,
"query": {
"term": {
"title": "java"
}
},
"highlight": {
"fields": {
"title": {}
}
}
}
複製程式碼
結果如圖:
預設的標籤是<em></em>
,如果你想自定義,可以使用: pre_tags
和 post_tags
。最終查詢程式碼為:
GET books/_search
{
"_source": ["title"],
"min_score": 0.7,
"query": {
"term": {
"title": "java"
}
},
"highlight" : {
"pre_tags" : ["<h1>"],
"post_tags" : ["</h1>"],
"fields" : {
"title" : {}
}
}
}
複製程式碼
結果如圖:
全文查詢
上節基本都是以 term
進行搜尋,但其實 Elasticsearch
提供了很多搜尋方法,本章就是介紹 Elasticsearch
有哪些搜尋方法、分別起的作用。
本章對 common_terms query
、query_string query
、simple_query_string query
沒有解釋說明,因為使用起來較少,而且解釋起來較為麻煩。如果想了解,可以參考網上的文章。這裡就不在闡述了。
match query
我們先使用 term
進行一次查詢:
GET books/_search
{
"_source": ["title", "author"],
"query": {
"term": {
"title": "java程式設計"
}
}
}
複製程式碼
你會發現,其結果為空(但是資料庫裡是有這個資料的),如圖:
這是因為 term
是匹配分詞後的詞項來進行查詢的。比如剛剛我們查的 java程式設計
,在 Elasticsearch
進行分詞時,會把 java程式設計
分為:java
和 程式設計
。導致匹配不起來。
用程式碼詮釋的話就是:
const keyword = 'java程式設計';
const data = ['java', '程式設計'];
const result = data.includes(keyword);
console.log(result) //=> false
複製程式碼
現在我們把 term
換成 match
來嘗試下:
GET books/_search
{
"_source": ["title", "author"],
"query": {
"match": {
"title": "java程式設計"
}
}
}
複製程式碼
結果如圖:
可以發現,已經有結果了,但是為什麼會有兩個呢?
原因是因為 match
會對你的關鍵字進行分詞,然後去匹配文件分詞後的結果,只要文件裡的詞項能匹配關鍵字分詞後的任何一個,都會返回到結果裡。
程式碼詮釋:
const data = ['java', '程式設計', '思想']; // 分詞後的文件裡的資料
const keywords = ['java', '程式設計', '思想']; // 分詞後的關鍵字
const result = (() => {
for (let x = 0; x < data.length; x++) {
const dataItem = data[x];
for (let y = 0; y < keywords.length; y++) {
const keywordItem = keywords[y];
if (dataItem === keywordItem) {
return true;
}
}
}
return false;
})()
複製程式碼
如果我只想讓它返回一個呢,並且只能用 match
來做,可以麼?
是可以的,match
提供了一個屬性:operator
。可以用這個來幫助完成這個需求:
GET books/_search
{
"_source": ["title", "author"],
"query": {
"match": {
"title": {
"query": "java程式設計",
"operator": "and"
}
}
}
}
複製程式碼
最終的結果如圖:
原理是因為 operator
屬性的值為 and
,這樣的話,就告訴 Elasticsearch
我要讓我的關鍵字都能和文件裡的詞項匹配上。有一個沒匹配上,我都不要。
如果 operator
屬性的值為 or
,那結果就和之前是一樣的了。
match_phrase query
你可以把這個方法理解為自帶了 operator
屬性的值為 and
的 match
。
這個方法有兩個限制條件,只有都滿足,才會在結果中顯示出:
- 分詞後的所有詞項都在該欄位中,相當於
operator: "and"
- 順序要一致
順序一致指的是什麼呢?
假設你使用 match
來匹配: 程式設計java
,那麼結果還是和上面一樣。所以如果你需要要求順序一致性,那麼你就可以使用 match_phrase
來做。
如果使用 程式設計java
來搜尋:
如果使用 java程式設計
:
match_phrase_prefix query
這個方法和 match_phrase
方法類似,不過這個方法可以可以把最後一個詞項作為字首進行匹配,想象一下:使用者在搜尋欄中搜尋 辣雞UZ
,然後下面列表中出現了 辣雞UZI
。
首先 match_phrase_prefix
會先分詞為: 辣雞
,然後找了一個文件,再然後匹配 辣雞
後面的字串是否以 UZ
開頭的。這個時候文件滿足條件,就返回出結果。可以假想後面一直有一個(.*)
的萬用字元,如:辣雞UZ(.*)
。
知道原理了,我們現在寫一個查詢語句:
GET books/_search
{
"_source": ["title", "author"],
"query": {
"match_phrase_prefix": {
"title": "java編"
}
}
}
複製程式碼
結果如圖:
multi_match query
multi_match
是 match
的升級方法,可以用來搜尋多個欄位。
比如我不想只在 title
裡搜尋 java程式設計
,我還想在 description
裡進行搜尋。那應該怎麼做呢?
Elasticsearch
已經提供了 multi_match
專門用來處理這件事情:
GET books/_search
{
"_source": ["title", "description"],
"query": {
"multi_match": {
"query": "java程式設計",
"fields": ["title", "description"]
}
}
}
複製程式碼
最終結果如圖:
並且 multi_match
還支援萬用字元。上面的查詢語句,可以寫成:
GET books/_search
{
"_source": ["title", "description"],
"query": {
"multi_match": {
"query": "java程式設計",
"fields": ["title", "*tion"]
}
}
}
複製程式碼
詞項查詢
上一章是全文查詢,這一章是詞項查詢。他們倆的區別在於:
- 全文查詢:會對查詢語句(query)進行分詞,然後匹配文件裡分詞後的資料
- 詞項查詢:不會對查詢語句進行分詞
term query
第一章節已經介紹過了,這裡就不再闡述了。
terms query
terms
是 term
查詢的升級版本,可以用來查詢文件中某一欄位,是否包含了其關鍵字。比如,我想查詢 title
欄位中包含了 優化
或者 基礎
的文件:
GET books/_search
{
"_source": ["title"],
"query": {
"terms": {
"title": ["優化", "基礎"]
}
}
}
複製程式碼
其結果如圖:
range query
從名字就能猜測出 range
是範圍匹配。可以匹配 number
、date
、string
(字串範圍查詢比較特殊,比較少用,就不再闡述了)
range
支援以下查詢引數:
- gt: 大於
- gte: 大於等於
- lt: 小於
- lte: 小於等於
number 範圍查詢
現在我想查詢價格低於70,並大於等於50的書籍。虛擬碼既:(price >= 50 && price < 70)
:
GET books/_search
{
"_source": ["title", "price"],
"query": {
"range": {
"price": {
"gte": 50,
"lt": 70
}
}
}
}
複製程式碼
其結果如圖:
date 範圍查詢
如果我想查詢,出版日期在 2016-1-1
到 2016-12-31
之間的書籍,那麼DSL查詢語句就如同以下這樣:
GET books/_search
{
"_source": ["title", "publish_time"],
"query": {
"range": {
"publish_time": {
"gte": "2016-1-1",
"lte": "2016-12-31",
"format": "yyyy-MM-dd"
}
}
}
}
複製程式碼
其結果如圖:
exists query
匹配有這個屬性的文件。比如我想找到存在 title
欄位的文件:
GET books/_search
{
"_source": "title",
"query": {
"exists": {
"field": "title"
}
}
}
複製程式碼
結果會返回所有的文件。那麼如何定義 有這個屬性
呢?
定義的規則如下:
{"title": "js"}
: 存在{"title": ""}
: 存在{"title": ["js"]}
: 存在{"title": ["js", null]}
: 存在(有一個值不為空就行){"title": null}
: 不存在{"title": []}
不存在{"title": [null]}
不存在{"foo": "bar"}
: 不存在
perfix query
用來匹配文件分詞後的詞項中的字首。我們先寫個DSL進行匹配下:
GET books/_search
{
"_source": "description",
"query": {
"prefix": {
"description": "wi"
}
}
}
複製程式碼
其結果如圖:
為何 wi
可以匹配到這個呢?因為 Elasticsearch
會對 description
進行分詞,其中會把 winPython
分為 win
Python
。那麼這兩個就是文件分詞後的詞項,而 prefix
匹配每個詞項的開頭是否匹配,相當於js的 startsWith
方法。用程式碼詮釋的話就是:
const dataItem = ['win', 'python'];
const prefixKeyword = 'wi';
const result = dataItem.some(item => item.startsWith(prefixKeyword));
console.log(result); //=> true
複製程式碼
wildcard query
wildcard
為萬用字元查詢。不過目前只支援 *
和 ?
。所代表的含義為:
*
: 零個或多個?
: 一個或多個
注意:wildcard
不是匹配全文,還是會對文件的欄位進行分詞,然後應用於每個詞項
比如,我現在想查詢 wi*
的文件:
GET books/_search
{
"_source": "description",
"query": {
"wildcard": {
"description": "wi*"
}
}
}
複製程式碼
其結果如圖:
首先 Elasticsearch
會先對 description
進行分詞為:win
和 python
。然後 wi*
會應用到每個詞項裡,其中 win
符合規則,則顯示在結果中。
如果我用 win?
,則不會有任何的結果,因為 ?
代表的是一個或多個。那麼匹配到 win
的時候,後面沒有字串了,則結果為空。
regexp query
其為正規表示式查詢,原理同 wildcard
,這裡就不在闡述了。
fuzzy query
可以把 fuzzy
理解為模糊查詢。比如使用者輸入關鍵字時,一不小心輸入錯了,變成了 javascrpit
,那麼 fuzzy
的作用就出來了。它仍可以搜尋到 javascript
:
GET books/_search
{
"_source": "description",
"query": {
"fuzzy": {
"description": "javascrpit"
}
}
}
複製程式碼
其結果如圖:
複合查詢
複合查詢就是把簡單的查詢組合在一起,從而實現更加複雜的查詢。並且複合查詢還可以控制另一個查詢的行為。
constant_score query
不太常用,用於對返回結果的文件進行打分。
這裡就不在闡述了,如果感興趣,可見:[Elasticsearch] 控制相關度 (四) - 忽略TF/IDF
bool query
這個查詢方法,還是非常重要的。這個方法提供了以下操作方法:
- must: 文件必須滿足
must
下面的查詢條件,相當於AND
或者&&
- should: 文件可以匹配
should
下的查詢條件,匹配不出來也沒事。相當於OR
或者||
- must_not: 和
must
相反,必須不滿足must_not
下面的查詢條件,相當於!==
- filter: 其功能和
must
一樣,但是不會打分,也就說不會影響文件的_score
欄位
現在,我們想要查詢:書籍作者(author
)是 葛一鳴
,書籍名稱(title
)裡包含 java
的書籍,價格(price
)不能高於 70
低於 40
,並且書籍描述(description
)可以包含或者不包含 虛擬機器
的書籍。
GET books/_search
{
"query": {
"bool": {
"filter": {
"term": {
"author": "葛一鳴"
}
},
"must": [
{
"match": {
"title": "java"
}
}
],
"should": [
{
"match": {
"description": "虛擬機器"
}
}
],
"must_not": [
{
"range": {
"price": {
"gt": 70,
"lt": 40
}
}
}
]
}
}
}
複製程式碼
其結果如圖:
dis_max query、function_score query、boosting query
這三個就不在闡述了,其主要作用是關係到 _score
,也就是關係到查詢的結果的評分。感興趣的,可以在網上搜下。
結尾
此文章是《從Lucene到Elasticsearch:全文檢索實戰》一書的讀書筆記,如果造成侵權,可與我聯絡,我將刪除此文。
這本書是十分棒的,如果大家有興趣想深入瞭解的話,可以去進行購買此書。
作者資訊
Black-Hole: 158blackhole@gmail.com
Blog: www.bugs.cc
Github: github.com/BlackHole1