白日夢的Elasticsearch系列筆記(一)基礎篇-- 快手上手ES

賜我白日夢發表於2021-01-14


一、導讀

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分詞器

  1. 安裝很簡單,所以詳細過程不會寫到文章中。
  2. 安裝啟動教程、ES、Kibana、IK分詞器安裝包都以百度網盤的方式分享給大家,後臺回覆:es 可領取

二、核心概念

因為這是第一篇基礎篇,對小白友好一些,所以需要先了解一些基本概念,你可以耐折性子讀一下,都不難理解的哈。

2.1、Near Realtime (NRT)

ES號稱對外提供的是近實時的搜尋服務,意思是資料從寫入ES到可以被Searchable僅僅需要1秒鐘,所以說基於ES執行的搜尋和分析可以達到秒級。


2.2、Cluster

叢集:叢集是一個或多個node的集合,它們一起儲存你存放進去的資料,使用者可以在所有的node之間進行檢索,一般的每個叢集都會有一個唯一的名稱標識,預設的名稱標識為 elasticsearch ,這個名字很重要,因為node想加入cluster時,需要這個名稱資訊。

確保別在不同的環境中使用相同的叢集名稱,進而避免node加錯叢集的情況,一顆考慮下面的叢集命名風格logging-stagelogging-devlogging-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、舉個例子:

shard和replica入門圖

上圖中存在上下兩個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
三、下一篇目錄


推薦閱讀(公眾號首發,歡迎關注白日夢)

  1. MySQL的修仙之路,圖文談談如何學MySQL、如何進階!(已釋出)
  2. 面前突擊!33道資料庫高頻面試題,你值得擁有!(已釋出)
  3. 大家常說的基數是什麼?(已釋出)
  4. 講講什麼是慢查!如何監控?如何排查?(已釋出)
  5. 對NotNull欄位插入Null值有啥現象?(已釋出)
  6. 能談談 date、datetime、time、timestamp、year的區別嗎?(已釋出)
  7. 瞭解資料庫的查詢快取和BufferPool嗎?談談看!(已釋出)
  8. 你知道資料庫緩衝池中的LRU-List嗎?(已釋出)
  9. 談談資料庫緩衝池中的Free-List?(已釋出)
  10. 談談資料庫緩衝池中的Flush-List?(已釋出)
  11. 瞭解髒頁刷回磁碟的時機嗎?(已釋出)
  12. 用十一張圖講清楚,當你CRUD時BufferPool中發生了什麼!以及BufferPool的優化!(已釋出)
  13. 聽說過表空間沒?什麼是表空間?什麼是資料表?(已釋出)
  14. 談談MySQL的:資料區、資料段、資料頁、資料頁究竟長什麼樣?瞭解資料頁分裂嗎?談談看!(已釋出)
  15. 談談MySQL的行記錄是什麼?長啥樣?(已釋出)
  16. 瞭解MySQL的行溢位機制嗎?(已釋出)
  17. 說說fsync這個系統呼叫吧! (已釋出)
  18. 簡述undo log、truncate、以及undo log如何幫你回滾事物! (已釋出)
  19. 我勸!這位年輕人不講MVCC,耗子尾汁! (已釋出)
  20. MySQL的崩潰恢復到底是怎麼回事? (已釋出)
  21. MySQL的binlog有啥用?誰寫的?在哪裡?怎麼配置 (已釋出)
  22. MySQL的bin log的寫入機制 (已釋出)
  23. 刪庫後!除了跑路還能幹什麼?(已釋出)
  24. 全網最牛的事務兩階段提交和分散式事務串講! (已釋出)

參考:

https://www.elastic.co/guide/en/elasticsearch/reference/6.0

相關文章