極力推薦: 官網地址: https://www.elastic.co/guide/en/elasticsearch/reference/6.0
肺腑之言,學ES先學原生的語法,SpringData封裝的是太好用了,但是沒玩過原生的語法,可能不知道Spring提供的API在干什么
核心概念:
Near Realtime (NRT)
在ES中進行搜索是近實時的,意思是數據從寫入ES到可以被searchable僅僅需要1秒鍾,因此說基於ES執行的搜索和分析可以達到秒級
Cluster
集群 , 集群是一個或多個node的集合,他們一起保存你存放進去的數據,用戶可以在所有的node之間進行檢索,一般的每個集群都會有一個唯一的名稱標識,默認的名稱標識為 elasticsearch
, 這個名字很重要,因為node想加入cluster時,需要這個名稱信息
確保別在不同的環境中使用相同的集群名稱,進而避免node加錯集群的情況,一顆考慮下面的集群命名風格logging-stage
和logging-dev
和logging-pro
Node
單台server就是一個node,他和 cluster一樣,也存在一個默認的名稱,但是它的名稱是通過UUID生成的隨機串,當然用戶也可以定制不同的名稱,但是這個名字最好別重復,這個名稱對於管理來說很在乎要,因為需要確定,當前網絡中的哪台服務器,對應這個集群中的哪個節點
node存在一個默認的設置,默認的,當每一個node在啟動時都會自動的去加入一個叫elasticsearch的節點,這就意味着,如果用戶在網絡中啟動了多個node,他們會彼此發現,然后組成集群
在單個的cluster中,你可以擁有任意多的node,假如說你的網絡上沒有有其他正在運行的節點,然后你啟動一個新的節點,這個新的節點自己會組件一個集群
Index
Index是一類擁有相似屬性的document的集合,比如你可以為消費者的數據創建一個index,為產品創建一個index,為訂單創建一個index
index名稱(必須是小寫的字符), 當需要對index中的文檔執行索引,搜索,更新,刪除,等操作時,都需要用到這個index
一個集群中理論上你可以創建任意數量的index
Type
Type可以作為index中的邏輯類別,為了更細的划分,比如用戶數據type,評論數據type,博客數據type
在設計時,盡最大努力讓擁有更多相同field的document會分為同一個type下
Document
document就是ES中存儲的一條數據,就像mysql中的一行記錄一樣,可以是一條用戶的記錄,一個商品的記錄等等
一個不嚴謹的小結:
為什么說這是不嚴謹的小結呢? 就是說下面三個對應關系只能說的從表面上看起來比較相似,但是ES中的type其實是一個邏輯上的划分,數據在存儲是時候依然是混在一起存儲的(往下看下文中有寫,),然而mysql中的不同表的兩個列是絕對沒有關系的
Elasticsearch | 關系型數據庫 |
---|---|
Document | 行 |
type | 表 |
index | 數據庫 |
Shards & Replicas
問題引入:
如果讓一個Index自己存儲1TB的數據,響應的速度就會下降為了解決這個問題,ES提供了一種將用戶的Index進行subdivide的騷操作,就是將index分片, 每一片都叫一個Shards,實現了將整體龐大的數據分布在不同的服務器上存儲
什么是shard?
shard分成replica shard和primary shard,顧名思義一個是主shard一個是備份shard, 負責容錯以及承擔部分讀請求
shard可以理解成是ES中最小的工作單元,所有shard中的數據之和,才是整個ES中存儲的數據, 可以把shard理解成是一個luncene的實現,擁有完整的創建索引,處理請求的能力
下圖是兩個node,6個shard的組成的集群的划分情況
大家可以看到,這時無論java應用程序訪問的是node1還是node2,其實都能獲取到數據
shard的默認數量
新創建的節點會存在5個primary shard,后續不然能再改動primary shard的值,如果每一個primary shard都對應一個replica shard,按理說單台es啟動就會存在10個分片,但是現實是,同一個節點的replica shard和primary shard不能存在於一個server中,因此單台es默認啟動后的分片數量還是5個
如何拓容Cluster
首先明確一點: 一旦index創建完成了,primary shard的數量就不可能再發生變化
因此橫向拓展就得添加replica的數量, 因為replica shard的數量后續是可以改動的, 也就是說,如果后續我們將他的數量改成了2, 就意味着讓每個primary shard都擁有了兩個replica shard, 計算一下: 5+5*2=15 集群就會拓展成15個節點
如果想讓每一個shard都有最多的系統的資源,就增加服務器的數量,讓每一個shard獨占一個服務器,
舉個例子:
上圖中存在上下兩個node,每一個node,每個node中都有一個 自己的primary shard和其他節點的replica shard,為什么是強調自己和其他呢? 因為ES中規定,同一個節點的replica shard和primary shard不能存在於一個server中,但是不同節點的primary shard可以存在於同一個server上
當primary shard宕機時,它對應的replicas在其他的server不會受到影響,可以繼續響應用戶的讀請求,通過這種分片的機制,並且分片的地位相當,假設單個shard可以處理2000/s的請求,通過橫向拓展可以在此基礎上成倍提升系統的吞吐量,天生分布式,高可用
此外:每一個document肯定存在於一個primary shard和這個primary shard 對應的replica shard中, 絕對不會出現同一個document同時存在於多個primary shard中的情況
入門探索:
集群的健康狀況
GET /_cat/health?v
執行結果如下:
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1572595632 16:07:12 elasticsearch yellow 1 1 5 5 0 0 5 0 - 50.0%
解讀上面的信息,默認的集群名是elasticsearch
,當前集群的status是yellow
,后續列出來的是集群的分片信息,最后一個active_shards_percent
表示當前集群中僅有一半shard是可用的
狀態
存在三種狀態分別是red green yellow
- green : 表示當前集群所有的節點全部可用
- yellow: 表示所有的數據是可以訪問的,但是並不是所有的replica shard都是可以使用的(我現在是默認啟動一個node,而ES又不允許同一個node的primary shard和replica shard共存,因此我當前的node中僅僅存在5個primary shard,為status為黃色)
- red: 集群宕機,數據不可訪問
集群的索引信息
GET /_cat/indices?v
結果:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open ai_answer_question cl_oJNRPRV-bdBBBLLL05g 5 1 203459 0 172.3mb 172.3mb
顯示,狀態yellow表示存在replica shard不可用, 存在5個primary shard,並且每一個primary shard都有一個replica shard , 一共20多萬條文檔,未刪除過文檔,文檔占用的空間情況為172.3兆
創建index
PUT /customer?pretty
ES 使用的RestfulAPI,新增使用put,這是個很親民的舉動
添加 or 修改
如果是ES中沒有過下面的數據則添加進去,如果存在了id=1的元素就修改(全量替換)
- 格式:
PUT /index/type/id
全量替換時,原來的document是沒有被刪除的,而是被標記為deleted,被標記成的deleted是不會被檢索出來的,當ES中數據越來越多時,才會刪除它
PUT /customer/_doc/1?pretty
{
"name": "John Doe"
}
響應:
{
"_index": "customer",
"_type": "_doc",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
強制創建,加添_create
或者?op_type=create
PUT /customer/_doc/1?op_type=create
PUT /customer/_doc/1/_create
- 局部更新(Partial Update)
不指定id則新增document
POST /customer/_doc?pretty
{
"name": "Jane Doe"
}
指定id則進行doc的局部更新操作
POST /customer/_doc/1?pretty
{
"name": "Jane Doe"
}
並且POST相對於上面的PUT而言,不論是否存在相同內容的doc,只要不指定id,都會使用一個隨機的串當成id,完成doc的插入
Partial Update先獲取document,再將傳遞過來的field更新進document的json中,將老的doc標記為deleted,再將創建document,相對於全量替換中間會省去兩次網絡請求
檢索
格式: GET /index/type/
GET /customer/_doc/1?pretty
響應:
{
"_index": "customer",
"_type": "_doc",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"name": "John Doe"
}
}
刪除
刪除一條document
大部分情況下,原來的document不會被立即刪除,而是被標記為deleted,被標記成的deleted是不會被檢索出來的,當ES中數據越來越多時,才會刪除它
DELETE /customer/_doc/1
響應:
{
"_index": "customer",
"_type": "_doc",
"_id": "1",
"_version": 2,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
刪除index
DELETE /index1
DELETE /index1,index2
DELETE /index*
DELETE /_all
可以在elasticsearch.yml中將下面這個設置置為ture,表示禁止使用 DELETE /_all
action.destructive_required_name:true
響應
{
"acknowledged": true
}
更新文檔
上面說了POST關鍵字,可以實現不指定id就完成document的插入, POST
+ _update
關鍵字可以實現更新的操作
POST /customer/_doc/1/_update?pretty
{
"doc": { "name": "changwu" }
}
POST+_update進行更新的動作依然需要執行id, 但是它相對於PUT來說,當使用POST進行更新時,id不存在的話會報錯,而PUT則會認為這是在新增
此外: 針對這種更新操作,ES會先刪除原來的doc,然后插入這個新的doc
document api
multi-index & multi-type
- 檢索所有索引下面的所有數據
/_search
- 搜索指定索引下的所有數據
/index/_search
- 更多模式
/index1/index2/_search
/*1/*2/_search
/index1/index2/type1/type2/_search
/_all/type1/type2/_search
_mget api 批量查詢
- 在docs中指定
_index
,_type
,_id
GET /_mget
{
"docs" : [
{
"_index" : "test",
"_type" : "_doc",
"_id" : "1"
},
{
"_index" : "test",
"_type" : "_doc",
"_id" : "2"
}
]
}
- 在URL中指定index
GET /test/_mget
{
"docs" : [
{
"_type" : "_doc",
"_id" : "1"
},
{
"_type" : "_doc",
"_id" : "2"
}
]
}
- 在URL中指定 index和type
GET /test/type/_mget
{
"docs" : [
{
"_id" : "1"
},
{
"_id" : "2"
}
- 在URL中指定index和type,並使用ids指定id范圍
GET /test/type/_mget
{
"ids" : ["1", "2"]
}
- 為不同的doc指定不同的過濾規則
GET /_mget
{
"docs" : [
{
"_index" : "test",
"_type" : "_doc",
"_id" : "1",
"_source" : false
},
{
"_index" : "test",
"_type" : "_doc",
"_id" : "2",
"_source" : ["field3", "field4"]
},
{
"_index" : "test",
"_type" : "_doc",
"_id" : "3",
"_source" : {
"include": ["user"],
"exclude": ["user.location"]
}
}
]
}
_bulk api 批量增刪改
基本語法
{"action":{"metadata"}}\n
{"data"}\n
存在哪些類型的操作可以執行呢?
-
delete: 刪除文檔
-
create: _create 強制創建
-
index: 表示普通的put操作,可以是創建文檔也可以是全量替換文檔
-
update: 局部替換
上面的語法中並不是人們習慣閱讀的json格式,但是這種單行形式的json更具備高效的優勢
ES如何處理普通的json如下:
- 將json數組轉換為JSONArray對象,這就意味着內存中會出現一份一模一樣的拷貝,一份是json文本,一份是JSONArray對象
但是如果上面的單行JSON,ES直接進行切割使用,不會在內存中整一個數據拷貝出來
delete
delete比較好看僅僅需要一行json就ok
{ "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }
create
兩行json,第一行指明我們要創建的json的index,type以及id
第二行指明我們要創建的doc的數據
{ "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
{ "field1" : "value3" }
index
相當於是PUT,可以實現新建或者是全量替換,同樣是兩行json
第一行表示將要新建或者是全量替換的json的index type 以及 id
第二行是具體的數據
{ "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
{ "field1" : "value1" }
update
表示 parcial update,局部替換
他可以指定一個retry_on_conflict
的特性,表示可以重試3次
POST _bulk
{ "update" : {"_id" : "1", "_type" : "_doc", "_index" : "index1", "retry_on_conflict" : 3} }
{ "doc" : {"field" : "value"} }
{ "update" : { "_id" : "0", "_type" : "_doc", "_index" : "index1", "retry_on_conflict" : 3} }
{ "script" : { "source": "ctx._source.counter += params.param1", "lang" : "painless", "params" : {"param1" : 1}}, "upsert" : {"counter" : 1}}
{ "update" : {"_id" : "2", "_type" : "_doc", "_index" : "index1", "retry_on_conflict" : 3} }
{ "doc" : {"field" : "value"}, "doc_as_upsert" : true }
{ "update" : {"_id" : "3", "_type" : "_doc", "_index" : "index1", "_source" : true} }
{ "doc" : {"field" : "value"} }
{ "update" : {"_id" : "4", "_type" : "_doc", "_index" : "index1"} }
{ "doc" : {"field" : "value"}, "_source": true}
滾動查詢技術
滾動查詢技術和分頁技術在使用場景方面還是存在出入的,這里的滾動查詢技術同樣適用於系統在海量數據中進行檢索,比如過一次性存在10條數據被命中可以被檢索出來,那么性能一定會很差,這時可以選擇使用滾動查詢技術,一批一批的查詢,直到所有的數據被查詢完成他可以先搜索一批數據再搜索一批數據
采用基於_doc的排序方式會獲得較高的性能
每次發送scroll請求,我們還需要指定一個scroll參數,指定一個時間窗口,每次搜索只要在這個時間窗口內完成就ok
示例
GET /index/type/_search?scroll=1m
{
"query":{
"match_all":{}
},
"sort":["_doc"],
"size":3
}
響應
{
"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAACNFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAkRZSZlh2S05BYVNKZW85R19NS1Nlc1F3AAAAAAAAAI8WUmZYdktOQWFTSmVvOUdfTUtTZXNRdwAAAAAAAACQFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAjhZSZlh2S05BYVNKZW85R19NS1Nlc1F3",
"took": 9,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": null,
"hits": [
{
"_index": "my_index",
"_type": "_doc",
"_id": "2",
"_score": null,
"_source": {
"title": "This is another document",
"body": "This document has a body"
},
"sort": [
0
]
},
{
"_index": "my_index",
"_type": "_doc",
"_id": "1",
"_score": null,
"_source": {
"title": "This is a document"
},
"sort": [
0
]
}
]
}
}
再次滾動查詢
GET /_search/scroll
{
"scroll":"1m",
"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAACNFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAkRZSZlh2S05BYVNKZW85R19NS1Nlc1F3AAAAAAAAAI8WUmZYdktOQWFTSmVvOUdfTUtTZXNRdwAAAAAAAACQFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAjhZSZlh2S05BYVNKZW85R19NS1Nlc1F3"
}
_search api 搜索api
query string search
_search
API + 將請求寫在URI中
GET /bank/_search?q=*&sort=account_number:asc&pretty
同樣使用的是RestfulAPI, q=*
,表示匹配index=bank的下的所有doc,sort=account_number:asc
表示告訴ES,結果按照account_number字段升序排序,pretty
是告訴ES,返回一個漂亮的json格式的數據
上面的q還可以寫成下面這樣
GET /bank/_search?q=自定義field:期望的值
GET /bank/_search?q=+自定義field:期望的值
GET /bank/_search?q=-自定義field:期望的值
響應:
{
"took" : 63, // 耗費的時間
"timed_out" : false, // 是否超時了
"_shards" : { // 分片信息
"total" : 5, // 總共5個分片,它的搜索請求會被打到5個分片上去,並且都成功了
"successful" : 5, //
"skipped" : 0, // 跳過了0個
"failed" : 0 // 失敗了0個
},
"hits" : { //命中的情況
"total" : 1000, // 命中率 1000個
"max_score" : null, // 相關性得分,越相關就越匹配
"hits" : [ {
"_index" : "bank", // 索引
"_type" : "_doc", // type
"_id" : "0", // id
"sort": [0],
"_score" : null, // 相關性得分
// _source里面存放的是數據
"_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
}, {
"_index" : "bank",
"_type" : "_doc",
"_id" : "1",
"sort": [1],
"_score" : null,
"_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
}, ...
]
}
}
指定超時時間: GET /_search?timeout=10ms
在進行優化時,可以考慮使用timeout, 比如: 正常來說我們可以在10s內獲取2000條數據,但是指定了timeout,發生超時后我們可以獲取10ms中獲取到的 100條數據
query dsl (domain specified language)
下面我僅僅列出來了一點點, 更多的示例,參見官網 點擊進入官網
_search
API +將請求寫在請求體中
GET /bank/_search
{
"query": { "match_all": {} }, # 查詢全部
"query": { "match": {"name":"changwu zhu"} }, # 全文檢索,戶將輸入的字符串拆解開,去倒排索引中一一匹配, 哪怕匹配上了一個也會將結果返回
# 實際上,上面的操作會被ES轉換成下面的格式
#
# {
# "bool":{
# "should":[
# {"term":{"title":"changwu"}},
# {"term":{"title":"zhu"}}
# ]
# }
# }
#
"query": {
"match": { # 手動控制全文檢索的精度,
"name":{
"query":"changwu zhu",
"operator":"and", # and表示,只有同時出現changwu zhu 兩個詞的doc才會被命中
"minimum_should_match":"75%" # 去長尾,控制至少命中3/4才算是真正命中
}
}
}, # 全文檢索,operator 表示
# 添加上operator 操作會被ES轉換成下面的格式,將上面的should轉換成must
#
# {
# "bool":{
# "must":[
# {"term":{"title":"changwu"}},
# {"term":{"title":"zhu"}}
# ]
# }
# }
#
# 添加上 minimum_should_match 操作會被ES轉換成下面的格式
#
# {
# "bool":{
# "should":[
# {"term":{"title":"changwu"}},
# {"term":{"title":"zhu"}}
# ],
# "minimum_should_match":3
# }
# }
#
"query": {
"match": { #控制權重,
"name":{
"query":"changwu zhu",
"boost":3 # 將name字段的權重提升成3,默認情況下,所有字段的權重都是樣的,都是1
}
}
},
"query": {
# 這種用法不容忽略
"dis_max": { # 直接取下面多個query中得分最高的query當成最終得分
"queries":[
{"match":{"name":"changwu zhu"}},
{"match":{"content":"changwu"}}
]
}
},
# best field策略
"query": { # 基於 tie_breaker 優化dis_max
# tie_breaker可以使dis_max考慮其他field的得分影響
"multi_match":{
"query":"用於去匹配的字段",
"type":"most_fields",# 指定檢索的策略most_fields
"fields":["field1","field2","field3"]
}
},
# most field 策略, 優先返回命中更多關鍵詞的doc, (忽略從哪個,從多少個field中命中的,只要命中就行)
"query": { # 基於 tie_breaker 優化dis_max
# tie_breaker可以使dis_max考慮其他field的得分影響
"dis_max": { # 直接取下面多個query中得分最高的query當成最終得分, 這也是best field策略
"queries":[
{"match":{"name":"changwu zhu"}},
{"match":{"content":"changwu"}}
],
"tie_breaker":0.4
}
},
"query": { "match_none": {} }
"query": { "term": {"test_field":"指定值"} } # 精確匹配
"query": { "exits": {"field":"title"} } # title不為空(但是這時ES2.0中的用法,現在不再提供了)
"query": { # 短語檢索
# 順序的保證是通過 term position來保證的
# 精准度很高,但是召回率低
"match_phrase": { # 只有address字段中包含了完整的 mill lane (相連,順序也不能變) 時,這個doc才算命中
"address": "mill lane"
}
},
"query": { # 短語檢索
"match_phrase": {
"address": "mill lane",
# 指定了slop就不再要求搜索term之間必須相鄰,而是可以最多間隔slop距離
# 在指定了slop參數的情況下,離關鍵詞越近,移動的次數越少, relevance score 越高
# match_phrase + slop 和 proximity match 近似匹配作用類似
# 平衡精准度和召回率
"slop":1 # 指定搜索文本中的幾個term經過幾次移動后可以匹配到一個doc
}
},
# 混合使用match和match_phrase 平衡精准度和召回率
"query": {
"bool": {
"must": {
# 全文檢索雖然可以匹配到大量的文檔,但是它不能控制詞條之間的距離
# 可能java elasticsearch在Adoc中距離很近,但是它卻被ES排在結果集的后面
# 它的性能比match_phrase高10倍,比proximity高20倍
"match": {
"address": "java elasticsearch"
}
},
"should": {
# 借助match_phrase+slop可以感知term position的功能,為距離相近的doc貢獻分數,讓它們靠前排列
"match_phrase":{
"title":{
"query":"java elasticsearch",
"slop":50
}
}
}
},
# 重打分機制
"query": {
"match":{
"title":{
"query":"java elasticsearch",
"minimum_should_match":"50%"
}
},
"rescore":{ # 對全文檢索的結果進行重新打分
"window_size":50, # 對全文檢索的前50條進行重新打分
"query": {
"rescore_query":{ # 關鍵字
"match_phrase":{ # match_phrase + slop 感知 term persition,貢獻分數
"title":{
"query":"java elasticsearch",
"slop":50
}
}
}
}
}
# 前綴匹配, 相對於全文檢索,前綴匹配是不會進行分詞的,而且每次匹配都會掃描整個倒排索引,直到掃描完一遍才會停下來
# 不會計算相關性得分,前綴越短拼配到的越多,性能越不好
"query": { # 查詢多個, 在下面指定的兩個字段中檢索含有 `this is a test`的doc
"multi_match" : {
"query": "this is a test",
"fields": [ "subject", "message" ]
}
},
"query": { # 前綴搜索,搜索 user字段以ki開頭的 doc
"prefix" : { "user" : "ki" }
},
"query": { # 前綴搜索 + 添加權重
"prefix" : { "user" : { "value" : "ki", "boost" : 2.0 } }
},
# 通配符搜索
"query": {
"wildcard" : { "user" : "ki*y" }
},
"query": {
"wildcard" : { "user" : { "value" : "ki*y", "boost" : 2.0 } }
}
# 正則搜索
"query": {
"regexp":{
"name.first": "s.*y"
}
},
"query": {# 正則搜索
"regexp":{
"name.first":{
"value":"s.*y",
"boost":1.2
}
}
},
# 搜索推薦, 類似於百度,當用戶輸入一個詞條后,將其他符合條件的詞條的選項推送出來
# 原理和match_pharse相似,但是唯一的區別就是會將最后一個term當作前綴去搜索
# 下例中: 使用quick brown進行match 使用f進行前綴搜索,使用slop調整term persition,貢獻得分
"query": {
"match_phrase_prefix" : {# 前綴匹配
"message" : {
"query" : "quick brown f",
"max_expansions" : 10, # 指定前綴最多匹配多少個term,超過這個數量就不在倒排索引中檢索了,提升性能
"slop":10
}
}
},
# Function Score Query
# 用戶可以自定義一個function_secore 函數,然后將某個field的值和ES計算出來的分數進行運算
# 最終實現對自己指定的field進行分數的增強功能
"query": {
"function_score": {
"query": { "match_all": {} },
"boost": "5",
"random_score": {},
"boost_mode":"multiply"
}
},
# Fuzzy Query 模糊查詢會提供容錯的處理
"query": {
"fuzzy" : {
"user" : {
"value": "ki",
"boost": 1.0,
"fuzziness": 2, # 做大的糾錯數量
"prefix_length": 0,# 不會被“模糊化”的初始字符數。這有助於減少必須檢查的術語的數量。默認值為0。
"max_expansions": 100 # 模糊查詢將擴展到的最大項數。默認值為50
transpositions:true # 是否支持模糊變換(ab→ba)。默認的是假的
}
}
}
"query": {
"bool": { # 布爾查詢, 最終通過將它內置must,should等查詢的得分加起來/should,must的總數, 得到最終的得分
"must": [ # 必須匹配到XXX, 並且會得出相關性得分
{ "match": { "address": "mill" } }, # address中必須包含mill
],
# 在滿足must的基礎上,should條件不滿足也可以,但是如果也匹配上了,相關性得分會增加
# 如果沒有must的話,should中的條件必須滿足一個
"should": [ # 指定可以包含的值, should是可以影響相關性得分的
{ "match": { "address": "lane" } }
],
"must_not": [ # 一定不包含誰
{ "match": { "address": "mill" } },
],
"filter": { # 對數據進行過濾
"range": { # 按照范圍過濾
"balance": { # 指定過濾的字段
"gte": 20000, # 高於20000
"lte": 30000 # 低於30000
}
}
}
}
}
在上面的組合查詢中,每一個子查詢都會計算一下他的相關性分數,然后由最外層的bool綜合合並一個得分,但是 filter是不會計算分數的
默認的排序規則是按照score降序排序,但像上面說的那樣,如果全部都是filter的話他就不會計算得分,也就是說所有的得分全是1,這時候就需要定制排序規則,定義的語法我在上面寫了
其他輔助API
比如下面的高亮,排序,分頁,以及_source
指定需要的字段都可以進一步作用在query
的結果上
"highlight":{ # 高亮顯示
"fields":{ # 指定高亮的字段
"balance":{}
},
"sort": [ # 指定排序條件
{ "account_number": "asc" } # 按照賬戶余額降序
],
"from": 0, # 分頁
"size": 10, # 每頁的大小4,通過執行size=0,可以實現僅顯示聚合結果而不顯示命中的信息詳情
"_source": ["account_number", "balance"], # 默認情況下,ES會返回全文JSON,通過_source可以指定返回的字段
聚合分析
聚合分析是基於doc value這樣一個數據結果進行的,前面有說過,這個doc value 其實就是正排索引, 聚合分析就是根據某一個字段進行分組,要求這個字段是不能被分詞的,如果被聚合的字段被分詞,按照倒排索引的方式去索引的話,就不得不去掃描整個倒排索引(才可能將被聚合的字段找全,效率很低)
三個概念:
- 什么是bucket?
bucket就是聚合得到的結果
- 什么是metric?
metric就是對bucket進行分析,如最最大值,最小值,平均值
- 什么是下鑽?
下鑽就是在現有的分好組的bucket繼續分組,比如一個先按性別分組,再按年齡分組
聚合的關鍵字: aggs
和 query
地位並列
# 使用聚合時,天然存在一個metric,就是當前bucket的count
"aggs": { # 聚合
"group_by_state": { # 自定義的名字
"term": {
"field": "balance" # 指定聚合的字段, 意思是 group by balance
},
"terms": { # terms
"field": {"value1","value2","value3"} # 指定聚合的字段, 意思是 group by balance
}
}
},
"aggs": { # 聚合中嵌套聚合
"group_by_state": {
"terms": {
"field": "field1"
},
"aggs": { # 聚合中嵌套聚合
"average_balance": {
"avg": {
"field": "field2"
}
}
}
}
},
"aggs": { #嵌套聚合,並且使用內部聚合的結果集
"group_by_state": {
"terms": {
"field": "state.keyword",
"order": {
"average_balance": "desc" # 使用的下面聚合的結果集
}
},
"aggs": {
"average_balance": {
"avg": { # avg 求平均值 metric
"field": "balance"
}
},
"min_price": {
"min": { # metric 求最小值
"field": "price"
}
},
"max_price": {
"max": { # metric 求最大值
"field": "price"
}
},
"sum_price": {
"sum": { # metric 計算總和
"field": "price"
}
},
}
}
},
"aggs": { # 先按照年齡分組,在按照性別分組,再按照平均工資聚合
# 最終的結果就得到了每個年齡段,每個性別的平均賬戶余額
"group_by_age": {
"range": {
"field": "age",
"ranges": [
{
"from": 20,
"to": 30
}
]
},
"aggs": {
"group_by_gender": {
"terms": {
"field": "gender.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
},
# histogram,類似於terms, 同樣會進行bucket分組操作,接受一個field,按照這個field的值的各個范圍區間進行分組操作
# 比如我們指定為2000, 它會划分成這樣 0-2000 2000-4000 4000-6000 ...
"aggs": { # 聚合中嵌套聚合
"group_by_price": {
"histogram": {
"field": "price",
"interval":2000
},
"aggs": { # 聚合中嵌套聚合
"average_price": {
"avg": {
"field": "price"
}
}
}
}
},
"aggs" : {
"sales_over_time" : { # 根據日期進行聚合
"date_histogram" : {
"field" : "date",
"interval" : "1M",# 一個月為一個跨度
"format" : "yyyy-MM-dd",
"min_doc_count":0 #即使這個區間中一條數據都沒有,這個區間也要返回
}
}
}
}
}
}
filter aggregate
過濾加聚合,統計type=t-shirt的平均價格
POST /sales/_search?size=0
{
"aggs" : {
"t_shirts" : {
"filter" : { "term": { "type": "t-shirt" } },
"aggs" : {
"avg_price" : { "avg" : { "field" : "price" } }
}
}
}
}
嵌套聚合-廣度優先
說一個應用於場景: 我們檢索電影的評論, 但是我們先按照演員分組聚合,在按照評論的數量進行聚合
分析: 如果我們選擇深度優先的話, ES在構建演員電影相關信息時,會順道計算出電影下面評論數的信息,假如說有10萬個演員的話, 10萬*10=100萬個電影 每個電影下又有很多影評,接着處理影評, 就這樣內存中可能會存在幾百萬條數據,但是我們最終就需要50條,這種開銷是很大的
廣度優先的話,是我們先處理電影數,而不管電影的評論數的聚合情況,先從10萬演員中干掉99990條數據,剩下10個演員再聚合
"aggs":{
"target_actors":{
"terms":{
"field":"actors",
"size":10,
"collect_mode":"breadth_first" # 廣度優先
}
}
}
global aggregation
全局聚合,下面先使用query進行全文檢索,然后進行聚合, 下面的聚合實際上是針對兩個不同的結果進行聚合,第一個聚合添加了global
關鍵字,意思是ES中存在的所有doc進行聚合計算得出t-shirt的平均價格
第二個聚合針對全文檢索的結果進行聚合
POST /sales/_search?size=0
{
"query" : {
"match" : { "type" : "t-shirt" }
},
"aggs" : {
"all_products" : {
"global" : {},
"aggs" : {
"avg_price" : { "avg" : { "field" : "price" } }
}
},
"t_shirts": { "avg" : { "field" : "price" } }
}
}
Cardinality Aggregate 基數聚合
作用類似於count(distcint)
,會對每一個bucket中指定的field進行去重,然后取去重后的count
雖然她會存在5%左右的錯誤率,但是性能特別好
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : {
"cardinality" : { # 關鍵字
"field" : "type"
}
}
}
}
對Cardinality Aggregate的性能優化, 添加 precision_threshold
優化准確率和內存的開銷
下面的示例中將precision_threshold
的值調整到100意思是當 type的類型小於100時,去重的精准度為100%, 此時內存的占用情況為 100*8=800字節
加入我們將這個值調整為1000,意思是當type的種類在1000個以內時,去重的精准度100%,內存的占用率為1000*8=80KB
官方給出的指標是, 當將precision_threshold
設置為5時,錯誤率會被控制在5%以內
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : {
"cardinality" : { # 關鍵字
"field" : "type",
"precision_threshold":100
}
}
}
}
進一步優化,Cardinality底層使用的算法是 HyperLogLog++, 可以針對這個算法的特性進行進一步的優化,因為這個算法的底層會對所有的 unique value取hash值,利用這個hash值去近似的求distcint count, 因此我們可以在創建mapping時,將這個hash的求法設置好,添加doc時,一並計算出這個hash值,這樣 HyperLogLog++ 就無需再計算hash值,而是直接使用
PUT /index/
{
"mappings":{
"my_type":{
"properties":{
"my_field":{
"type":"text",
"fields":{
"hash":{
"type":"murmu3"
}
}
}
}
}
}
}
控制聚合的升降序
先按照顏色聚合,在聚合的結果上,再根據價格進行聚合, 最終的結果中,按照價格聚合的分組中升序排序, 這算是個在下轉分析時的排序技巧
GET /index/type/_search
{
"size":0,
"aggs":{
"group_by_color":{
"term":{
"field":"color",
"order":{ #
"avg_price":"asc"
}
}
},
"aggs":{
"avg_price":{
"avg":{
"field":"price"
}
}
}
}
}
Percentiles Aggregation
計算百分比, 常用它計算如,在200ms內成功訪問網站的比率,在500ms內成功訪問網站的比例,在1000ms內成功訪問網站的比例, 或者是銷售價為1000元的商品,占總銷售量的比例, 銷售價為2000元的商品占總銷售量的比例等等
示例: 針對doc中的 load_time字段, 計算出在不同百分比下面的 load_time_outliner情況
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time"
}
}
}
}
響應 : 解讀: 在百分之50的加載請求中,平均load_time的時間是在445.0, 在99%的請求中,平均加載時間980.1
{
...
"aggregations": {
"load_time_outlier": {
"values" : {
"1.0": 9.9,
"5.0": 29.500000000000004,
"25.0": 167.5,
"50.0": 445.0,
"75.0": 722.5,
"95.0": 940.5,
"99.0": 980.1000000000001
}
}
}
}
還可以自己指定百分比跨度間隔
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"percents" : [95, 99, 99.9]
}
}
}
}
優化: percentile底層使用的是 TDigest算法,用很多個節點執行百分比計算,近似估計,有誤差,節點越多,越精准
可以設置compression
的值, 默認是100 , ES限制節點的最多是 compression
*20 =2000個node去計算 , 因為節點越多,性能就越差
一個節點占用 32字節, 1002032 = 64KB
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"percents" : [95, 99, 99.9],
"compression":100 # 默認值100
}
}
}
}
優化相關性得分
- 第一種方式:
在content字段中全文檢索 java elasticsearch
時,給title中同時出現java elasticsearch
的doc的權重加倍
"query": {
"bool" : {# 前綴匹配
"match":{
"content":{
"query":"java elasticsearch"
}
},
"should":[
"match":{
"title":{
"query":"java elasticsearch",
"boost":2
}
}
]
}
}
- 第二種: 更換寫法,改變占用的權重比例
GET my_index/_doc/_search
{
"query":{
"should":[
{ "match":{"title":"this is"}}, # 1/3
{ "match":{"title":"this is"}}, # 1/3
{
"bool":{
"should":[
{"match":{"title":"this is"}}, # 1/6
{"match":{"title":"this is"}} # 1/6
]
}
}
]
}
}
- 第三種: 如果不希望使用相關性得分,使用下面的語法
GET my_index/_doc/_search
{
"query": {
"constant_score" : {
"filter" : {
"term" : { "title" : "this"} #
},
"boost" : 1.2
}
}
}
- 第四種: 靈活的查詢
查詢必須包含XXX,必須不包含YYY的doc
GET my_index/_doc/_search
{
"query":{
"bool": {
"must":{
"match":{
"title":"this is a "
}
},
"must_not":{
"match":{
"title":"another"
}
}
}
}
}
- 第五種: 查詢必須包含XXX,可以包含YYY,但是包含了YYY后它的權重就會減少指定的值
GET my_index/_doc/_search
{
"query":{
"boosting": {
"positive":{
"match":{
"title":"this is a "
}
},
"negative":{
"match":{
"title":"another"
}
},
"negative_boost": 0.2
}
}
}
- 第六種: 重打分機制
"query": {
"match":{
"title":{
"query":"java elasticsearch",
"minimum_should_match":"50%"
}
},
"rescore":{ # 對全文檢索的結果進行重新打分
"window_size":50, # 對全文檢索的前50條進行重新打分
"query": {
"rescore_query":{ # 關鍵字
"match_phrase":{ # match_phrase + slop 感知 term persition,貢獻分數
"title":{
"query":"java elasticsearch",
"slop":50
}
}
}
}
}
- 第七種: 混用match和match_phrase提高召回率
"query": {
"bool": {
"must": {
# 全文檢索雖然可以匹配到大量的文檔,但是它不能控制詞條之間的距離
# 可能java elasticsearch在Adoc中距離很近,但是它卻被ES排在結果集的后面
# 它的性能比match_phrase高10倍,比proximity高20倍
"match": {
"address": "java elasticsearch"
}
},
"should": {
# 借助match_phrase+slop可以感知term position的功能,為距離相近的doc貢獻分數,讓它們靠前排列
"match_phrase":{
"title":{
"query":"java elasticsearch",
"slop":50
}
}
}
}
歡迎大家點贊支持