白日夢的Elasticsearch筆記(一)基礎篇
- 2021 年 1 月 14 日
- 筆記
- elasticsearch
一、導讀
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的寫入機制 (已發布)
- 刪庫後!除了跑路還能幹什麼?(已發布)
- 全網最牛的事務兩階段提交和分散式事務串講! (已發布)
參考:
//www.elastic.co/guide/en/elasticsearch/reference/6.0