白日夢的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分詞器

  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. 全網最牛的事務兩階段提交和分散式事務串講! (已發布)

參考:

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