準備
首先先宣告下,我這裡使用的 ES 版本 5.2.0.
為了便於理解,這裡以如下 index 為格式,該格式是通過 PMACCT 抓取的 netflow 流量資訊, 文中所涉及的到的例子,全基於此 index.
本篇涉及的內容可以理解為 ES 的入門內容,主要針對詞項的過濾,為基礎篇。
{
"_index": "shflows_agg_1600358400",
"_type": "shflows_agg",
"_id": "node1_1600359600_0_172698718_shflows_agg_0",
"_score": 1.0,
"_source": {
"collector": "node1",
"src_port": "443",
"timestamp": 1600359600,
"device_ip": "1.1.1.1",
"flows": "40",
"dst_host": "2.2.2.2",
"TAG": 10001,
"router_ip": 172698718,
"dst_port": "16384",
"pkts": 40000,
"bits": 320000000000,
"src_host": "3.3.3.3"
}
},
在正式介紹搜尋前,先明確一個概念。很多人在學習 ES 查詢前,容易對 Term 和全文查詢進行混淆。
首先,Term 是表達語義的最小單位,在搜尋和利用統計語言模型時都需要處理 Term.
對應在 ES 裡,針對 Term 查詢的輸入來說,不會做任何的分詞處理,會把輸入作為一個整體,在 ES 的倒排索引中進行詞項的匹配,然後利用算分公式將結果返回。並可以通過 Constant Score 將查詢轉換為一個 Filtering,避免算分,利用快取,從而提高效能。
雖然輸入時,不做分詞處理,但在搜尋時,會做分詞處理。這樣有時就會出現無法搜尋出結果的情況,比如有 name 為 ‘Jack’ 的 doc. 但如果在搜尋時,輸入 Jack,ES 是無法查詢到的。必須改成小寫的 jack 或者使用 keyword 進行查詢。
Term 查詢包含:
- Term Query
- Range Query
- Exists Query
- Prefix Query
- Wildcard Query
而全文查詢,是基於全文字的查詢。
在 ES 中,索引(輸入)和搜尋時都會分詞。先將查詢的字串傳遞到合適分詞器中,然後生成一個供查詢的詞項列表。
全文查詢包括:
- Match Query
- Match Phrase Query
- Query String Query
而下面的例子全都是基於 Term 查詢。
ES 搜尋概述
ES 搜尋 API 可以分為兩大類:
- 基於 URL 的引數搜尋, 適合簡單的搜尋。
- 基於 Request Body 的搜尋(DSL),適合更為複雜的搜尋。
確定查詢的索引範圍:
/_search
: 叢集上的所有索引
/index1/_search
: index1 索引
/index1,index2/_search
: index1 和 index2 索引
/index*/_search
: 以 index 開頭的所有索引
URL 查詢
指定欄位查詢:
使用 q 指定引數,通過KV 間鍵值對查詢。
舉例1:查詢裝置 IP 為 1.1.1.1
的相關文件資訊:
/shflows_agg_*/_search?q=device_ip:1.1.1.1
{
"profile": "true"
}
profile 的意思是檢視查詢過程
結果:可以看到 type 為 TermQuery,搜尋時根據指定欄位:"device_ip:10.75.44.94"
"profile": {
"shards": [
{
"id": "[e_Ac3cNJRtmVxFW9DwOwjA][shflows_agg_1600531200][0]",
"searches": [
{
"query": [
{
"type": "TermQuery",
"description": "device_ip:1.1.1.1",
"time": "445.8407320ms",
............
泛查詢
不明確指定查詢的 key,只指定 value,會對文件中所有 key 進行匹配
舉例2:查詢各個屬性中帶有 1.1.1.1 字元的文件, 比如如果 src_host 或者 dst_host 中出現 1.1.1.1,相關文件也會被查詢出來。
/shflows_agg_*/_search?q=10.75.44.94
{
"profile": "true"
}
結果:可以看到 description 變為 _all
"profile": {
"shards": [
{
"id": "[e_Ac3cNJRtmVxFW9DwOwjA][shflows_agg_1600531200][0]",
"searches": [
{
"query": [
{
"type": "TermQuery",
"description": "_all:1.1.1.1",
......
DSL 查詢
方法:通過在 body 中,編寫 json 進行更為複雜的查詢
查詢所有文件
舉例1:查詢當前 index 所有文件:
/shflows_agg_index1/_search
{
"query": {
"match_all": {} # 返回所有 doc
}
}
對文件進行排序和分頁
舉例2:查詢當前 index 所有文件,按照時間排序
/shflows_agg_index1/_search
{
"from": 10,
"size": 20,
"sort": [{"timestamp": "desc"}],
"query": {
"match_all": {} # 返回所有 doc
}
}
指定文件返回的引數
舉例:指定文件中,返回的僅是指定的引數
/shflows_agg_index1/_search
{
"_source": ["timestamp", "device_ip"],
"query": {
"match_all": {} # 返回所有 doc
}
}
使用指令碼欄位,對文件中的多個值進行指令碼運算
舉例:將文件中的,源 ip 和源埠進行拼接,並以 ip_address 進行命名:
/shflows_agg_index1/_search
{
"script_fields": {
"ip_address":{
"script": {
"lang": "painless",
"inline": "params.comment + doc['device_ip'].value + ':' + doc['dst_port'].value",
"params" : {
"comment" : "ip address is: "
}
}
}
},
"query": {
"match_all": {}
}
}
結果:在 fields 裡多出了新的指令碼拼接後的欄位
{
"took": 84,
"timed_out": false,
"_shards": {
"total": 10,
"successful": 10,
"failed": 0
},
"hits": {
"total": 36248845,
"max_score": 1.0,
"hits": [
{
"_index": "shflows_agg_1600358400",
"_type": "shflows_agg",
"_id": "node1_1600359600_0_172698718_shflows_agg_0",
"_score": 1.0,
"fields": {
"ip_address": [
"ip address is: 10.75.44.94:16384"
]
}
},
{
"_index": "shflows_agg_1600358400",
"_type": "shflows_agg",
"_id": "node1_1600359600_0_172698718_shflows_agg_5",
"_score": 1.0,
"fields": {
"ip_address": [
"ip address is: 10.75.44.94:443"
]
}
},
.......
Query Context OR Filter Context 查詢
在 ES 中,搜尋過程有 Query 和 Filter 上下文兩種:
- Query 查詢:在搜尋過程中會進行相關性的算分操作
- Filter 查詢:不需要進行算分,所以可以利用快取,獲得更好的效能
在 Query 和 Filter 查詢裡可以進行:
- 等值查詢(term)
- 範圍查詢(range)
舉例:如查詢 dst_port 為 443 的 doc,並打分
/shflows_agg_index1/_search
{
"profile": "true",
"explain": true,
"query": {
"term": {"dst_port": 443}
}
}
結果:
{
"took": 191,
"timed_out": false,
"_shards": {
"total": 11,
"successful": 11,
"failed": 0
},
"hits": {
"total": 3871488,
"max_score": 2.2973032,
"hits": [
{
"_shard": "[shflows_agg_1600358400][0]",
"_node": "RWTixYPtTieZaRgAH0NOkQ",
"_index": "shflows_agg_1600358400",
"_type": "shflows_agg",
"_id": "node1_1600359600_0_172698718_shflows_agg_5",
"_score": 2.2973032, ####### 可以看到這裡有計算的分數
"_source": {
"collector": "node1",
"src_port": "16384",
"timestamp": 1600359600,
使用 filter 查詢:
/shflows_agg_index1/_search
{
"profile": "true",
"explain": true,
"query": {
# 使用 constant_score 不進行算分操作
"constant_score": {
"filter": {
"term": {
"dst_port": 443
}
}
}
}
}
結果:
"hits": {
"total": 3872768,
"max_score": 1.0, # 1.0 為固定值
.....
"profile": {
"shards": [
{
"id": "[e_Ac3cNJRtmVxFW9DwOwjA][shflows_agg_1600531200][0]",
"searches": [
{
"query": [
{
"type": "ConstantScoreQuery", ## 不變分數查詢
"description": "ConstantScore(dst_port:443)",
舉例:terms 查詢,查詢 dst_port 為 443 和 22 doc
/shflows_agg_index1/_search
{
"profile": "true",
"explain": true,
"query": {
# 使用 constant_score 不進行算分操作
"constant_score": {
"filter": {
"terms": {
"dst_port": [443,22]
}
}
}
}
}
舉例:資料範圍查詢
{
"profile": "true",
"explain": true,
"query": {
"constant_score": {
"filter": {
"range": {
"timestamp": {
# 大於等於
"gte": 1601049600,
# 小於等於
"lte": 1601308800
}
}
}
}
}
}
Bool 複合查詢:多個條件進行篩選
在 ES 可以通過 bool 查詢,將一個或者多個查詢子句組合或者巢狀到一起,實現更為複雜的查詢。
bool 查詢共包含 4 個子句:
- must:搜尋的結果必須匹配,參與算分
- should:選擇性匹配,類似於 OR,滿足一個條件就可以,參與算分
- must_not: 必須不能匹配,屬於 Filter context,不貢獻算分
- filter:必須匹配,屬於 Filter context ,不貢獻算分。
must_not 和 filter 效能更好,不需要算分。
舉例:查詢時間範圍在 1601171628 和 1601175228 之間,目的埠為 80,源目的 IP 在 [1.1.1.1 ,1.1.1.2, 1.1.1.3] 中任意一個的 doc 資訊。
{
"profile": "true",
"explain": true,
"query": {
"bool": {
"must": [
{
"range": {
"timestamp": {
"gte": 1601171628,
"lte": 1601175228
}
}
},
{
"term": {
"dst_port": 80
}
},
{
"bool": {
# 注意這裡 should 在 must 的陣列裡,如果和 must 同級,是無法影響 must 的結果的。
"should": [
{
"term": {
"src_host": "1.1.1.1"
}
},
{
"term": {
"src_host": "1.1.1.1"
}
},
{
"term": {
"src_host": "1.1.1.1"
}
}
]
}
}
]
}
}
}