極力推薦: 官網地址: https://www.elastic.co/guide/en/elasticsearch/reference/6.0css
肺腑之言,學ES先學原生的語法,SpringData封裝的是太好用了,可是沒玩過原生的語法,可能不知道Spring提供的API在幹什麼html
核心概念:#
Near Realtime (NRT)#
在ES中進行搜索是近實時的,意思是數據從寫入ES到能夠被searchable僅僅須要1秒鐘,所以說基於ES執行的搜索和分析能夠達到秒級java
Cluster#
集羣 , 集羣是一個或多個node的集合,他們一塊兒保存你存放進去的數據,用戶能夠在全部的node之間進行檢索,通常的每一個集羣都會有一個惟一的名稱標識,默認的名稱標識爲 elasticsearch
, 這個名字很重要,由於node想加入cluster時,須要這個名稱信息node
確保別在不一樣的環境中使用相同的集羣名稱,進而避免node加錯集羣的狀況,一顆考慮下面的集羣命名風格logging-stage
和logging-dev
和logging-pro
mysql
Node#
單臺server就是一個node,他和 cluster同樣,也存在一個默認的名稱,可是它的名稱是經過UUID生成的隨機串,固然用戶也能夠定製不一樣的名稱,可是這個名字最好別重複,這個名稱對於管理來講很在意要,由於須要肯定,當前網絡中的哪臺服務器,對應這個集羣中的哪一個節點nginx
node存在一個默認的設置,默認的,當每個node在啓動時都會自動的去加入一個叫elasticsearch的節點,這就意味着,若是用戶在網絡中啓動了多個node,他們會彼此發現,而後組成集羣算法
在單個的cluster中,你能夠擁有任意多的node,假如說你的網絡上沒有有其餘正在運行的節點,而後你啓動一個新的節點,這個新的節點本身會組件一個集羣sql
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":