ElasticSearch 文件及操作

碼農充電站發表於2021-02-22

公號:碼農充電站pro
主頁:https://codeshellme.github.io

本節介紹 ES 文件,索引及其基本操作。

1,ES 中的文件

在 ES 中,文件(Document)是可搜尋資料的最小儲存單位,相當於關聯式資料庫中的一條記錄。

文件以 Json 資料格式儲存在 ES 中,Json 中儲存著多個鍵值對,它可以儲存不同型別的資料,比如:

  • 字串型別
  • 數字型別
  • 布林型別
  • 陣列型別
  • 日期型別
  • 二進位制型別
  • 範圍型別

Python 語言中的字典型別,就是 Json 資料格式。

文件中的資料型別可以指定,也可以由 ES 自動推斷。

每個文件中都有一個 Unique ID,用於唯一標識一個文件。Unique ID 可以由使用者指定,也可以由 ES 自動生成。

Unique ID 實際上是一個字串

比如下面的 Json 就是一個文件:

{
  "name" : "XiaoMing",
  "age" : 19,
  "gender" : "male"
}

1.1,文件後設資料

將上面那個 Json 資料儲存到 ES 後,會像下面這樣:

{
    "_index": "person", 
    "_type": "_doc", 
    "_id": "2344563",
    "_version": 1, 
    "_source": {
        "name": "XiaoMing", 
        "age": 19, 
        "gender": "male"
    }
}

其中以下劃線開頭的欄位就是後設資料:

  • _index:文件所屬的索引。
  • _type:文件的型別。ES 7.0 開始,一個索引只能有一種 _type
  • _id:文件的唯一 ID。
  • _source:文件的原始 Json 資料。
  • _version:文件更新的次數。

你可以檢視這裡,瞭解“為什麼單個Index下,不再支援多個Tyeps?”。

更多關於後設資料的資訊,可以參考這裡

1.2,文件的刪除與更新

ES 中文件的刪除操作不會馬上將其刪除,而是會將其標記到 del 檔案中,在後期合適的時候(比如 Merge 階段)會真正的刪除。

ES 中的文件是不可變更的更新操作會將舊的文件標記為刪除,同時增加一個新的欄位,並且文件的 version 加 1。

1.3,文件中的欄位數

在 ES 中,一個文件預設最多可以有 1000 個欄位,可以通過 index.mapping.total_fields.limit 進行設定。

注意在設計 ES 中的資料結構時,不要使文件的欄位數過多,這樣會使得 mapping 很大,增加叢集的負擔。

2,ES 中的索引

ES 中的文件都會儲存在某個索引(Index)中,索引是文件的容器,是一類文件的集合,相當於關係型資料庫中的表的概念。

ES 中可以建立很多不同的索引,表示不同的文件集合。

每個索引都可以定義自己的 MappingsSettings

  • Mappings:用於設定文件欄位的型別。
  • Settings:用於設定不同的資料分佈。

對於索引的一些引數設定,有些引數可以動態修改,有些引數在索引建立後不能修改,可參考這裡

ES 與傳統資料庫類比

如果將 ES 中的基本概念類比到傳統資料庫中,它們的對應關係如下:

ES 傳統資料庫
索引
文件
欄位
Mapping 表定義
DSL SQL 語句

索引相關 API

下面給出一些檢視索引相關資訊的 API:

# 檢視索引相關資訊
GET index_name

# 檢視索引的文件總數
GET index_name/_count

# 檢視指定索引的前10條文件
POST index_name/_search
{
}

#_cat indices API

# 檢視所有的索引名以 index_prefix 為字首的索引
GET /_cat/indices/index_prefix*?v&s=index

# 檢視狀態為 green 的索引
GET /_cat/indices?v&health=green

# 按照文件個數排序
GET /_cat/indices?v&s=docs.count:desc

# 檢視指定索引的指定資訊
GET /_cat/indices/index_prefix*?pri&v&h=health,index,pri,rep,docs.count,mt

# 檢視索引使用的記憶體大小
GET /_cat/indices?v&h=i,tm&s=tm:desc

3,GET 操作

GET 操作可以獲取指定文件的內容。

GET index_name/_count:獲取指定索引中的文件數。

GET index_name/_doc/id:獲取指定索引中的指定文件。

GET index_name/_doc不允許該操作。

GET index_name:獲取指定索引的 MappingsSettings

4,POST / PUT 操作

POST/PUT 操作用於建立文件。

按照 POST / PUT 方法來區分

POST index_name/_doc

  • POST index_name/_doc:不指定 ID,總是會插入新的文件,文件數加 1。
  • POST/PUT index_name/_doc/id:指定 ID
    • 當 id 存在時,會覆蓋之前的,並且 version 會加 1,文件數不增加。
    • 當 id 不存在時,會插入新的文件,文件數加 1。

PUT index_name/_create

  • PUT index_name/_create:不指定 ID,不允許該操作。
  • PUT index_name/_create/id:指定 ID
    • 當 id 存在時:報錯,不會插入新文件。
    • 當 id 不存在時:,會插入新的文件,文件數加 1。

PUT index_name/_doc

  • PUT index_name/_doc:不指定 ID,不允許該操作。
  • PUT/POST index_name/_doc/id:指定 ID
    • 當 id 存在時,會覆蓋之前的,並且 version 會加 1,文件數不增加。
    • 當 id 不存在時,會插入新的文件,文件數加 1。

PUT index_name/_doc/id?op_type=XXX

  • op_type=create
    • 當 id 存在時,報錯,不會插入新文件。
    • 當 id 不存在時,會插入新的文件,文件數加 1。
  • op_type=index
    • 當 id 存在時,會覆蓋之前的,並且 version 會加 1,文件數不增加。
    • 當 id 不存在時,會插入新的文件,文件數加 1。

按照是否指定 ID 來區分

指定 ID

  • POST/PUT index_name/_doc/id:指定 ID,稱為 Index 操作
    • 相當於 PUT index_name/_doc/id?op_type=index
    • 當 id 存在時,會覆蓋之前的,並且 version 會加 1,文件數不增加。
    • 當 id 不存在時,會插入新的文件,文件數加 1。
  • PUT index_name/_doc/id?op_type=create:指定 ID,稱為 Create 操作
    • 相當於 PUT index_name/_create/id
    • 當 id 存在時,報錯,不會插入新文件。
    • 當 id 不存在時,會插入新的文件,文件數加 1。

不指定 ID

  • POST index_name/_doc:不指定 ID,總是會插入新的文件,文件數加 1。
  • PUT index_name/_doc:不指定 ID,不允許該操作。
  • PUT index_name/_create:不指定 ID,不允許該操作。

5,Update 操作

Update 操作用於更新文件的內容。

POST index_name/_update/id/:更新指定文件的內容。更新的內容要放在 doc 欄位中,否則會報錯

  • 當 id 不存在時,報錯,不更新任何內容。
  • 當 id 存在時:
    • 如果更新的欄位與原來的相同,則不做任何操作。
    • 如果更新的欄位與原來的不同,則更新原有內容,並且 version 會加 1。

實際上 ES 中的文件是不可變更的,更新操作會將舊的文件標記為刪除,同時增加一個新的欄位,並且文件的 version 加 1。

6,Delete 操作

Delete 操作用於刪除索引或文件。

DELETE /index_name/_doc/id:刪除某個文件。

  • 當刪除的 id 存在時,會刪除該文件。
  • 當刪除的 id 不存在時,ES 會返回 not_found

DELETE /index_name:刪除整個索引,要謹慎使用

  • 當刪除的 index_name 存在時,會刪除整個索引內容。
  • 當刪除的 index_name 不存在時,ES 會返回 404 錯誤。

7,Bulk 批量操作

批量操作指的是,在一次 API 呼叫中,對不同的索引進行多次操作。

每次操作互不影響,即使某個操作出錯,也不影響其他操作。

返回的結果中包含了所有操作的執行結果。

Bulk 支援的操作有 IndexCreateUpdateDelete

Bulk 操作的格式如下:

POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test2", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

注意 Bulk 請求體的資料量不宜過大,建議在 5~15M。

8,Mget 批量讀取

Mget 一次讀取多個文件的內容,設計思想類似 Bulk 操作。

Mget 操作的格式如下:

GET _mget
{
    "docs" : [
        {"_index" : "index_name1", "_id" : "1"},
        {"_index" : "index_name2", "_id" : "2"}
    ]
}

也可以在 URI 中指定索引名稱:

GET /index_name/_mget
{
    "docs" : [
        {"_id" : "1"},
        {"_id" : "2"}
    ]
}

還可以用 _source 欄位來設定返回的內容:

GET _mget
{
    "docs" : [
        {"_index" : "index_name1", "_id" : "1"},
        {"_index" : "index_name2", "_id" : "2", "_source" : ["f1", "f2"]}
    ]
}

9,Msearch 批量查詢

Msearch 操作用於批量查詢,格式如下:

POST index_name1/_msearch
{} # 索引名稱,不寫的話就是 URI 中的索引
{"query" : {"match_all" : {}},"size":1}
{"index" : "index_name2"} # 改變了索引名稱
{"query" : {"match_all" : {}},"size":2}

URI 中也可以不寫索引名稱,此時請求體裡必須寫索引名稱:

POST _msearch
{"index" : "index_name1"} # 索引名稱
{"query" : {"match_all" : {}},"size":1}
{"index" : "index_name2"} # 索引名稱
{"query" : {"match_all" : {}},"size":2}

上文中介紹了 3 種批量操作,分別是 Bulk,Mget,Msearch。注意在使用批量操作時,資料量不宜過大,避免出現效能問題

10,ES 常見錯誤碼

當我們的請求發生錯誤的時候,ES 會返回相應的錯誤碼,常見的錯誤碼如下:

錯誤碼 含義
429 叢集過於繁忙
4XX 請求格式錯誤
500 叢集內部錯誤

11,Reindex 重建索引

有時候我們需要重建索引,比如以下情況:

  • 索引的 mappings 發生改變:比如欄位型別或者分詞器等發生更改。
  • 索引的 settings 發生改變:比如索引的主分片數發生更改。
  • 叢集內或叢集間需要做資料遷移

ES 中提供兩種重建 API:

11.1,新增子欄位

先在一個索引中插入資料:

DELETE blogs/

# 寫入文件
PUT blogs/_doc/1
{
  "content":"Hadoop is cool",
  "keyword":"hadoop"
}

# 檢視自動生成的 Mapping
GET blogs/_mapping

# 查詢文件
POST blogs/_search
{
  "query": {
    "match": {
      "content": "Hadoop"
    }
  }
}

# 可以查到資料

現在修改 mapping新增子欄位是允許的),為 content 欄位加入一個子欄位:

# 修改 Mapping,增加子欄位,使用英文分詞器
PUT blogs/_mapping
{
  "properties" : {
    "content" : {      # content 欄位
      "type" : "text",
      "fields" : {     # 加入一個子欄位
        "english" : {  # 子欄位名稱
          "type" : "text",      # 子欄位型別
          "analyzer":"english"  # 子欄位分詞器
        }
      }
    }
  }
}

# 檢視新的 Mapping
GET blogs/_mapping

修改 mapping 之後再查詢文件:

# 使用 english 子欄位查詢 Mapping 變更前寫入的文件
# 查不到文件
POST blogs/_search
{
  "query": {
    "match": {
      "content.english": "Hadoop"
    }
  }
}

# 注意:不使用 english 子欄位是可以查詢到之前的文件的
POST blogs/_search
{
  "query": {
    "match": {
      "content": "Hadoop"
    }
  }
}

結果發現,使用 english 子欄位是查不到之前的文件的。這時候就需要重建索引

11.2,Update by query

下面使用 Update by query 對索引進行重建:

# Update所有文件
POST blogs/_update_by_query
{

}

重建索引之後,不管是使用 english 子欄位還是不使用,都可以查出文件。

Update by query 操作還可以設定一些條件:

request-body 示例:

POST tech_blogs/_update_by_query?pipeline=blog_pipeline
{
    "query": {      # 將 query 的查詢結果進行重建
        "bool": {
            "must_not": {
                "exists": {"field": "views"}
            }
        }
    }
}

11.3,修改欄位型別

在原有 mapping 上,修改欄位型別是不允許的:

# 會發生錯誤
PUT blogs/_mapping
{
  "properties" : {
    "content" : {
      "type" : "text",
      "fields" : {
        "english" : {
          "type" : "text",
          "analyzer" : "english"
        }
      }
    },
    "keyword" : {  # 修改 keyword 欄位的型別
      "type" : "keyword"
    }
  }
}

這時候只能建立一個新的索引,設定正確的欄位型別,然後再將原有索引中的資料,重建到新索引中。

建立一個新的索引 blogs_new

# 建立新的索引並且設定新的Mapping
PUT blogs_new/
{
  "mappings": {
    "properties" : {
      "content" : {
        "type" : "text",
        "fields" : {
          "english" : {
            "type" : "text",
            "analyzer" : "english"
          }
        }
      },
      "keyword" : {
        "type" : "keyword"
      }
    }    
  }
}

11.4,Reindex

下面使用 Reindex 將原來索引中的資料,匯入到新的索引中:

# Reindx API
POST _reindex
{
  "source": { # 指定原有索引
    "index": "blogs"
  },
  "dest": {   # 指定目標索引
    "index": "blogs_new"
  }
}

Reindex API 中的 source 欄位和 dest 欄位還有很多引數可以設定,具體可參考其官方文件

另外 Reindex 請求的 URI 中也可以設定引數,可以參考這裡

12,ES 的併發控制

同一個資源在多併發處理的時候,會發生衝突的問題。

傳統資料庫(比如 MySQL)會採用的方式,在更新資料的時候對資料進行加鎖,來防止衝突。

而 ES 並沒有採用鎖,而是將併發問題交給了使用者處理。

在 ES 中可以採用兩種方式:

  • 內部版本控制(ES 自帶的 version):在 URI 中使用 if_seq_noif_primary_term
  • 外部版本控制(由使用者指定 version):在 URI 中使用 versionversion_type=external

示例,首先插入資料:

DELETE products
PUT products/_doc/1
{
  "title":"iphone",
  "count":100
}

# 上面的插入操作會返回 4 個欄位:
#{
#  "_id" : "1",
#  "_version" : 1,
#  "_seq_no" : 0,
#  "_primary_term" : 1
#}

12.1,內部版本控制方式

使用內部版本控制的方式:

PUT products/_doc/1?if_seq_no=0&if_primary_term=1
{
  "title":"iphone",
  "count":100
}

# 上面的更新操作返回下面內容:
#{
#  "_id" : "1",
#  "_version" : 2,       # 加 1
#  "_seq_no" : 1,        # 加 1
#  "_primary_term" : 1   # 不變
#}

如果再次執行這句更新操作,則會出錯,出錯之後由使用者決定如何處理這就達到了解決衝突的目的

# 再執行則會出錯,因為 seq_no=0 且 primary_term=1 的資料已經不存在了
PUT products/_doc/1?if_seq_no=0&if_primary_term=1

12.2,外部版本控制方式

先看下資料庫中的資料:

GET products/_doc/1

# 返回:
{
  "_index" : "products",
  "_type" : "_doc",
  "_id" : "1",            # id
  "_version" : 2,         # version
  "_seq_no" : 1,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "title" : "iphone",
    "count" : 100
  }
}

使用外部版本控制的方式:

# 如果 URI 中的 version 值與 ES 中的 version 值相等,則出錯
# 下面這句操作會出錯,出錯之後,由使用者決定如何處理
PUT products/_doc/1?version=2&version_type=external
{
  "title":"iphone",
  "count":1000
}

# 如果 URI 中的 version 值與 ES 中的 version 值不相等,則成功
# 下面這句操作會成功
PUT products/_doc/1?version=3&version_type=external
{
  "title":"iphone",
  "count":1000
}

13,使用 Ingest 節點對資料預處理

Ingest 節點用於對資料預處理,它是在 ES 5.0 後引入的一種節點型別,可以達到一定的 Logstash 的功能。

預設情況下,所有的節點都是 Ingest 節點。

Ingest 節點通過新增一些 processors 來完成特定的處理,Pipeline 可以看做是一組 processors 的順序執行。

Ingest 節點的處理階段如下圖所示:

在這裡插入圖片描述

13.0,Ingest 節點與 Logstash 對比

在這裡插入圖片描述

13.1,內建的 Processors

ES 中內建了很多現成的 Processors 供我們使用:

  • Append:向一個陣列型別的欄位加入更多的值。
  • Split:將字串拆分成陣列。
  • Set:設定一個欄位。
  • Uppercase:大寫轉換。
  • Lowercase:小寫轉換。
  • Remove:移除一個已存在的欄位。如果欄位不存在,將丟擲異常。
  • Rename:為一個欄位重新命名。
  • Convert:轉換一個欄位的資料型別。比如將字串型別轉換成整數型別。
  • Date:日期格式轉換。
  • JSON:將 json 字串轉換成 JSON 型別。
  • Date-index-name:將通過該處理器的文件,分配到指定時間格式的索引中。
  • Fail:當出現異常的時候,將指定的資訊返回給使用者。
  • Foreach:用於處理陣列型別的資料。
  • Pipeline:引用另一個 Pipeline。
  • Trim:刪除字元換的前置和後置空格。
  • Sort:對陣列中的元素排序。
  • Url-decode:對字串進行 URL 解碼。
  • User-agent:用於解析 User-Agent 資訊。
  • Html-strip:用於移除 HTML 標籤。
  • Script:用 Painless 語言編寫指令碼,以支援更復雜的功能。
    • Painless 語言是專門為 ES 設計的,在 ES 5.x 引入,具有高效能和安全性。
    • ES 6.0 開始,ES 只支援 Painless 指令碼,不再支援其它語言指令碼(比如 JavaScript,Python 等)。
    • Painless 基於 Java 語言,並支援所有的 Java 資料型別。

13.2,測試 Processors

ES 中提供了一個 simulate 介面,用於測試 Processors。

示例:

POST _ingest/pipeline/_simulate
{
  "pipeline": {        # 定義 pipeline
    "description": "to split blog tags", # 描述
    "processors": [    # 一系列的 processors
      {
        "split": {     # 一個 split processor
          "field": "tags",
          "separator": ","  # 用逗號分隔
         }
       },
       {
        "set":{        # 可以設定多個 processor
          "field": "views",
          "value": 0
        }
      }
     ]
   },
  "docs": [            # 測試的文件
    {                  # 第 1 個文件
      "_index": "index",
      "_id": "id",
      "_source": {
        "title": "Introducing big data......",
        "tags": "hadoop,elasticsearch,spark",
        "content": "You konw, for big data"
       }
    },
    {                 # 第 2 個文件
      "_index": "index",
      "_id": "idxx",
      "_source": {
        "title": "Introducing cloud computering",
        "tags": "openstack,k8s",
        "content": "You konw, for cloud"
      }
    }
  ]
}

13.3,新增一個 Pipeline

當 Processors 測試通過後,可以向 ES 中新增(設定)一個 Pipeline,語法:

# blog_pipeline 為 pipeline 名稱
PUT _ingest/pipeline/blog_pipeline
{
  "description": "a blog pipeline",
  "processors": [
      {
        "split": {  # 第 1個 Processor
          "field": "tags",
          "separator": ","
        }
      },
      {
        "set":{     # 第 2個 Processor
          "field": "views",
          "value": 0
        }
      }
   ]
}

13.4,檢視 Pipeline

# 檢視 Pipleline
GET _ingest/pipeline/blog_pipeline

# 刪除 Pipleline
DELETE _ingest/pipeline/blog_pipeline

13.5,測試 Pipeline

# blog_pipeline 是 Pipeline 名稱
POST _ingest/pipeline/blog_pipeline/_simulate
{
  "docs": [
    {     # 一個文件
      "_source": {
        "title": "Introducing cloud computering",
        "tags": "openstack,k8s",
        "content": "You konw, for cloud"
      }
    }
  ]
}

13.6,使用 Pipeline

使用 Pipeline 插入文件時,文件會先經過 Pipeline 的處理,然後再插入到 ES 中。

# URI 中指定了 Pipeline 的名字
PUT tech_blogs/_doc/2?pipeline=blog_pipeline
{
  "title": "Introducing cloud computering",
  "tags": "openstack,k8s",
  "content": "You konw, for cloud"
}

最終插入的文件是這樣的:

{
  "title": "Introducing cloud computering",
  "tags": ["openstack", "k8s"],
  "content": "You konw, for cloud",
  "views": 0
}

另外 update-by-query(重建索引)的 URI 中也可以設定 pipeline 引數來使用一個 Pipeline

14,總結

上文介紹到的所有操作,可以參考 ES 的官方文件

(本節完。)


推薦閱讀:

ElasticSearch 查詢

ElasticSearch 分詞器

ElasticSearch 搜尋引擎概念簡介

Kibana,Logstash 和 Cerebro 的安裝執行

ElasticSearch 安裝與執行


歡迎關注作者公眾號,獲取更多技術乾貨。

碼農充電站pro

相關文章