一、導讀
Hi All!我們一起學點有意思的!NoSQL!歡迎訂閱白日夢Elasticsearch專題系列文章。按計劃這個專題一共有四篇文章。所有文章公眾號首發。
所有文章公眾號首發!
所有文章公眾號首發!
所有文章公眾號首發!
所有文章公眾號首發!
Notice!!!白日夢並不能保證通過這四篇文章讓你掌握ES,但是!我會用大白話串講ES的一些概念、和花哨的玩法。起碼可以把你對Elasticsearch的陌生度降到最低,等有一天你自己業務需要使用ES時,會因為提前讀了白日夢的ES筆記而快速上手。
為寫這篇文章我還華為雲上購置了一臺2C4G的伺服器,歡迎關注白日夢,我們一起學點實用的!有趣的技術!
1.1、認識ES
關係型資料庫:
像MySQL這種資料庫就是傳統的關係型資料庫。它有個很直觀的特點:每一張資料表的列在建立表的時候就需要確定下來。比如你建立一個user表,定義了3列id、username、password。這時如果你的實體類中多了一個age的欄位,那這個實體是不能儲存進user表的。(當然後續你可以通過DDL修改新增列或者減少列。讓實體類的屬性和表中的列一一對應)。
非關係型資料庫:
非關係型資料庫也就是我們常聽說的NoSQL。常見的有:MongoDB、Redis、Elasticsearch。
且不說效能方面,單說使用方面NoSQL這種非關係型別的資料庫都支援你往它裡面儲存一個json物件,這個json有多少個欄位並不是它關係的,拿上面的例子來說,只要你給他一個物件,不管有沒有age、它都能幫你儲存進去。
關於ES更多的知識點我們在下文中展開,再說一下ES常見的使用場景和特性:
站內搜尋:
如果你的公司想做自己的站內搜尋,那ES再合適不過了。作為非關係型資料庫的ES允許你往它裡面儲存各種格式不確定的Json物件,還為你提供了全文字搜尋和分析引擎。它使您可以快速,近乎實時地(1 s)儲存,搜尋和分析大量資料。一個字:快!
日誌採集系統:
Elasticsearch是Elastic公司的核技術,並且Elastic公司還有其他諸如:Logstash、Filebeat、Kibana等技術棧。常見的公司裡面使用的日誌管理系統就可以使用ELK+Filebeat搭建起來,Filebeat收集日誌推送到Logstash做處理,然後Logstash將資料儲存入ES,最終通過Kibana展示日誌。
可擴充套件性:
Elasticsearch天生就是分散式的,既能以單機的形式執行一臺效能很差的伺服器上。它也可以形成一個成百上千節點的叢集。並且它自己會管理叢集中的節點,在ES中我們可以隨意的新增、摘除節點,叢集自己會將資料均攤在各個節點上。
1.2、安裝、啟動ES、Kibana、IK分詞器
- 安裝很簡單,所以詳細過程不會寫到文章中。
- 安裝啟動教程、ES、Kibana、IK分詞器安裝包都以百度網盤的方式分享給大家,後臺回覆:es 可領取
二、核心概念
因為這是第一篇基礎篇,對小白友好一些,所以需要先了解一些基本概念,你可以耐折性子讀一下,都不難理解的哈。
2.1、Near Realtime (NRT)
ES號稱對外提供的是近實時的搜尋服務,意思是資料從寫入ES到可以被Searchable僅僅需要1秒鐘,所以說基於ES執行的搜尋和分析可以達到秒級。
2.2、Cluster
叢集:叢集是一個或多個node的集合,它們一起儲存你存放進去的資料,使用者可以在所有的node之間進行檢索,一般的每個叢集都會有一個唯一的名稱標識,預設的名稱標識為 elasticsearch
,這個名字很重要,因為node想加入cluster時,需要這個名稱資訊。
確保別在不同的環境中使用相同的叢集名稱,進而避免node加錯叢集的情況,一顆考慮下面的叢集命名風格logging-stage
和logging-dev
和logging-pro
。
2.3、Node
單臺server就是一個node,它和cluster一樣,也存在一個預設的名稱。但是它的名稱是通過UUID生成的隨機串,當然使用者也可以定製不同的名稱,但是這個名字最好別重複。這個名稱對於管理來說很在乎要,因為需要確定,當前網路中的哪臺伺服器,對應這個叢集中的哪個節點。
node存在一個預設的設定,預設的,當每一個node在啟動時都會自動的去加入一個叫elasticsearch的節點,這就意味著,如果使用者在網路中啟動了多個node,它們會彼此發現,然後組成叢集。
在單個的cluster中,你可以擁有任意多的node。假如說你的網路上沒有其它正在執行的節點,然後你啟動一個新的節點,這個新的節點自己會組建一個叢集。
2.4、Index
Index是一類擁有相似屬性的document的集合,比如你可以為消費者的資料建立一個index,為產品建立一個index,為訂單建立一個index。
index名稱(必須是小寫的字元), 當需要對index中的文件執行索引、搜尋、更新、刪除、等操作時,都需要用到這個index。
理論上:你可以在一個叢集中建立任意數量的index。
2.5、Type
Type可以作為index中的邏輯類別。為了更細的劃分,比如使用者資料type、評論資料type、部落格資料type
在設計時盡最大努力讓擁有更多相同field的document劃分到同一個type下。
2.6、Document
document就是ES中儲存的一條資料,就像mysql中的一行記錄一樣。它可以是一條使用者的記錄、一個商品的記錄等等
2.7、一個不嚴謹的小結:
為什麼說這是不嚴謹的小結呢? 就是說下面三個對應關係只能說的從表面上看起來比較相似。但是ES中的type其實是一個邏輯上的劃分。資料在儲存是時候依然是混在一起儲存的(往下看下文中有寫),而mysql中的不同表的兩個列是絕對沒有關係的。
Elasticsearch | 關係型資料庫 |
---|---|
Document | 行 |
type | 表 |
index | 資料庫 |
2.8、Shards & Replicas
2.8.1、問題引入:
如果讓一個Index自己儲存1TB的資料,響應的速度就會下降。為了解決這個問題,ES提供了一種將使用者的Index進行subdivide的騷操作,就是將index分片,每一片都叫一個Shards,進而實現了將整體龐大的資料分佈在不同的伺服器上儲存。
2.8.2、什麼是shard?
shard分成replica shard和primary shard。顧名思義一個是主shard、一個是備份shard, 負責容錯以及承擔部分讀請求。
shard可以理解成是ES中最小的工作單元。所有shard中的資料之和,才是整個ES中儲存的資料。 可以把shard理解成是一個luncene的實現,擁有完整的建立索引,處理請求的能力。
下圖是兩個node,6個shard的組成的叢集的劃分情況:
你可以看一下上面的圖,圖中無論java應用程式訪問的是node1還是node2,其實都能獲取到資料。
2.8.3、shard的預設數量
新建立的節點會存在5個primary shard,注意!後續不然能再改動primary shard的值,如果每一個primary shard都對應一個replica shard,按理說單臺es啟動就會存在10個分片,但是現實是,同一個節點的replica shard和primary shard不能存在於一個server中,因此單臺es預設啟動後的分片數量還是5個。
2.8.4、如何拓容Cluster
首先明確一點: 一旦index建立完成了,primary shard的數量就不可能再發生變化。
因此橫向擴充就得新增replica的數量, 因為replica shard的數量後續是可以改動的。也就是說,如果後續我們將它的數量改成了2, 就意味著讓每個primary shard都擁有了兩個replica shard, 計算一下: 5+5*2=15 叢集就會擴充成15個節點。
如果想讓每一個shard都有最多的系統的資源就增加伺服器的數量,讓每一個shard獨佔一個伺服器。
2.8.5、舉個例子:
上圖中存在上下兩個node,每個node中都有一個 自己的primary shard和其它節點的replica shard,為什麼是強調自己和其它呢? 因為ES中規定,同一個節點的replica shard和primary shard不能存在於一個server中,而不同節點的primary shard可以存在於同一個server上。
當primary shard當機時,因為它對應的replicas shard在其它的server沒有受到影響,所以ES可以繼續響應使用者的讀請求。通過這種分片的機制,並且分片的地位相當,假設單個shard可以處理2000/s的請求,通過橫向擴充可以在此基礎上成倍提升系統的吞吐量,天生分散式,高可用。
此外: 每一個document肯定存在於一個primary shard和這個primary shard 對應的replica shard中, 絕對不會出現同一個document同時存在於多個primary shard中的情況。
三、入門探索:
下面的小節中你會看到我使用大量的GET / POST 等等包括什麼query。其實你不用詫異為啥整一堆這些東西而不寫點程式碼。
其實這些命令對於ES來說,就像是SQL和MySQL的關係。換句話說,其實你寫的程式碼的底層幫你執行的也是我下面說得的這些命令。所以,別怕麻煩,下面的這些知識點無論如何你都不能直接跨越過去。
3.1、叢集的健康狀況
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: 表示ES中所有的資料都是可以訪問的,但是並不是所有的replica shard都是可以使用的(我現在是預設啟動一個node,而ES又不允許同一個node的primary shard和replica shard共存,因此我當前的node中僅僅存在5個primary shard,為status為黃色)。
- red: 叢集當機,資料不可訪問。
3.2、叢集的索引資訊
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兆。
3.3、建立index
PUT /customer?pretty
ES 使用的RestfulAPI,新增使用put,這是個很親民的舉動。
3.4、新增 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,相對於全量替換中間會省去兩次網路請求
3.5、檢索
格式: GET /index/type/
GET /customer/_doc/1?pretty
響應:
{
"_index": "customer",
"_type": "_doc",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"name": "John Doe"
}
}
3.6、刪除
刪除一條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
}
3.6、更新文件
上面說了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
4.1、search
- 檢索所有索引下面的所有資料
/_search
- 搜尋指定索引下的所有資料
/index/_search
- 更多模式
/index1/index2/_search
/*1/*2/_search
/index1/index2/type1/type2/_search
/_all/type1/type2/_search
4.2、_mget api 批量查詢
mget是ES為我們提供的批量查詢的API,我們只需要制定好 index、type、id。ES會將命中的記錄批量返回給我們。
- 在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"]
}
}
]
}
4.3、_bulk api 批量增刪改
4.3.1、基本語法
{"action":{"metadata"}}\n
{"data"}\n
存在哪些型別的操作可以執行呢?
-
delete: 刪除文件。
-
create: _create 強制建立。
-
index: 表示普通的put操作,可以是建立文件也可以是全量替換文件。
-
update: 區域性替換。
上面的語法中並不是人們習慣閱讀的json格式,但是這種單行形式的json更具備高效的優勢。
ES如何處理普通的json如下:
- 將json陣列轉換為JSONArray物件,這就意味著記憶體中會出現一份一模一樣的拷貝,一份是json文字,一份是JSONArray物件。
但是如果上面的單行JSON,ES直接進行切割使用,不會在記憶體中整一個資料拷貝出來。
4.3.2、delete
delete比較好看僅僅需要一行json就ok
{ "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }
4.3.3、create
兩行json,第一行指明我們要建立的json的index,type以及id
第二行指明我們要建立的doc的資料
{ "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
{ "field1" : "value3" }
4.3.4、index
相當於是PUT,可以實現新建或者是全量替換,同樣是兩行json。
第一行表示將要新建或者是全量替換的json的index type 以及 id。
第二行是具體的資料。
{ "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
{ "field1" : "value1" }
4.3.5、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}
4.4、滾動查詢技術
如果你想一次性查詢好幾萬條資料,這麼龐大的資料量,ES效能肯定會受到影響。這時可以選擇使用滾動查詢(scroll)。一批一批的查詢,直到所有的資料被查詢完成。也就是說它會先搜尋一批資料再搜尋一批資料。
示例如下:每次傳送一次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
]
}
· ]
}
}
查詢下一批資料時,需要攜帶上一次scroll返回給我們的_scroll_id
再次滾動查詢
GET /_search/scroll
{
"scroll":"1m",
"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAACNFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAkRZSZlh2S05BYVNKZW85R19NS1Nlc1F3AAAAAAAAAI8WUmZYdktOQWFTSmVvOUdfTUtTZXNRdwAAAAAAAACQFlJmWHZLTkFhU0plbzlHX01LU2VzUXcAAAAAAAAAjhZSZlh2S05BYVNKZW85R19NS1Nlc1F3"
}
滾動查詢時,如果採用基於_doc的排序方式會獲得較高的效能。
五、下一篇目錄:
一、_search api 搜尋api
1.1、query string search
1.2、query dsl 20個查詢案例
1.3、其它輔助API
1.4、聚合分析
1.4.1、filter aggregate
1.4.2、巢狀聚合-廣度優先
1.4.3、global aggregation
1.4.4、Cardinality Aggregate 基數聚合
1.4.5、控制聚合的升降序
1.4.6、Percentiles Aggregation
二、優化相關性得分與查詢技巧
2.1、優化技巧1
2.2、優化技巧2
2.3、優化技巧3
2.4、優化技巧4
2.5、優化技巧5
2.6、優化技巧6
2.7、優化技巧7
三、下一篇目錄
推薦閱讀(公眾號首發,歡迎關注白日夢)
- MySQL的修仙之路,圖文談談如何學MySQL、如何進階!(已釋出)
- 面前突擊!33道資料庫高頻面試題,你值得擁有!(已釋出)
- 大家常說的基數是什麼?(已釋出)
- 講講什麼是慢查!如何監控?如何排查?(已釋出)
- 對NotNull欄位插入Null值有啥現象?(已釋出)
- 能談談 date、datetime、time、timestamp、year的區別嗎?(已釋出)
- 瞭解資料庫的查詢快取和BufferPool嗎?談談看!(已釋出)
- 你知道資料庫緩衝池中的LRU-List嗎?(已釋出)
- 談談資料庫緩衝池中的Free-List?(已釋出)
- 談談資料庫緩衝池中的Flush-List?(已釋出)
- 瞭解髒頁刷回磁碟的時機嗎?(已釋出)
- 用十一張圖講清楚,當你CRUD時BufferPool中發生了什麼!以及BufferPool的優化!(已釋出)
- 聽說過表空間沒?什麼是表空間?什麼是資料表?(已釋出)
- 談談MySQL的:資料區、資料段、資料頁、資料頁究竟長什麼樣?瞭解資料頁分裂嗎?談談看!(已釋出)
- 談談MySQL的行記錄是什麼?長啥樣?(已釋出)
- 瞭解MySQL的行溢位機制嗎?(已釋出)
- 說說fsync這個系統呼叫吧! (已釋出)
- 簡述undo log、truncate、以及undo log如何幫你回滾事物! (已釋出)
- 我勸!這位年輕人不講MVCC,耗子尾汁! (已釋出)
- MySQL的崩潰恢復到底是怎麼回事? (已釋出)
- MySQL的binlog有啥用?誰寫的?在哪裡?怎麼配置 (已釋出)
- MySQL的bin log的寫入機制 (已釋出)
- 刪庫後!除了跑路還能幹什麼?(已釋出)
- 全網最牛的事務兩階段提交和分散式事務串講! (已釋出)
參考: