1. 程式人生 > >Elasticsearch從入門到放棄:文件CRUD要牢記

Elasticsearch從入門到放棄:文件CRUD要牢記

在Elasticsearch中,文件(document)是所有可搜尋資料的最小單位。它被序列化成JSON儲存在Elasticsearch中。每個文件都會有一個唯一ID,這個ID你可以自己指定或者交給Elasticsearch自動生成。

如果延續我們之前不恰當的對比RDMS的話,我認為文件可以類比成關係型資料庫中的表。

元資料

前面我們提到,每個文件都有一個唯一ID來標識,獲取文件時,“_id”欄位記錄的就是文件的唯一ID,它是元資料之一。當然,文件還有一些其他的元資料,下面我們來一一介紹

  • _index:文件所屬的索引名
  • _type:文件所屬的type
  • _id:文件的唯一ID

有了這三個,我們就可以唯一確定一個document了,當然,7.0版本以後我們已經不需要_type了。接下來我們再來看看其他的一些元資料

  • _source:文件的原始JSON資料
  • _field_names:該欄位用於索引文件中值不為null的欄位名,主要用於exists請求查詢指定欄位是否為空
  • _ignore:這個欄位用於索引和儲存文件中每個由於異常(開啟了ignore_malformed)而被忽略的欄位的名稱
  • _meta:該欄位用於儲存一些自定義的元資料資訊
  • _routing:用來指定資料落在哪個分片上,預設值是Id
  • _version:文件的版本資訊
  • _score:相關性打分

建立文件

建立文件有以下4種方法:

  • PUT /<index>/_doc/<_id>
  • POST /<index>/_doc/
  • PUT /<index>/_create/<_id>
  • POST /<index>/_create/<_id>

這四種方法的區別是,如果不指定id,則Elasticsearch會自動生成一個id。如果使用_create的方法,則必須保證文件不存在,而使用_doc方法的話,既可以建立新的文件,也可以更新已存在的文件。

在建立文件時,還可以選擇一些引數。

請求引數

  • if_seq_no:當文件的序列號是指定值時才更新
  • if_primary_term:當文件的primary term是指定值時才更新
  • op_type:如果設定為create則指定id的文件必須不存在,否則操作失敗。有效值為index或create,預設為index
  • op_type:指定預處理的管道id
  • refresh:如果設定為true,則立即重新整理受影響的分片。如果是wait_for,則會等到重新整理分片後,此次操作才對搜尋可見。如果是false,則不會重新整理分片。預設值為false
  • routing:指定路由到的主分片
  • timeout:指定響應時間,預設是30秒
  • master_timeout:連線主節點的響應時長,預設是30秒
  • version:顯式的指定版本號
  • version_type:指定版本號型別:internal、 external、external_gte、force
  • wait_for_active_shards:處理操作之前,必須保持活躍的分片副本數量,可以設定為all或者任意正整數。預設是1,即只需要主分片活躍。

響應包體

  • **_shards**:提供分片的資訊
  • **_shards.total**:建立了文件的總分片數量
  • **_shards.successful**:成功建立文件分片的數量
  • **_shards.failed**:建立文件失敗的分片數量
  • **_index**:文件所屬索引
  • **_type**:文件所屬type,目前只支援_doc
  • **_id**:文件的id
  • **_version**:文件的版本號
  • **_seq_no**:文件的序列號
  • **_primary_term**:文件的主要術語
  • result:索引的結果,created或者updated

我們在建立文件時,如果指定的索引不存在,則ES會自動為我們建立索引。這一操作是可以通過設定中的action.auto_create_index欄位來控制的,預設是true。你可以修改這個欄位,實現指定某些索引可以自動建立或者所有索引都不能自動建立的目的。

更新文件

瞭解瞭如何建立文件之後,我們再來看看應該如何更新一個已經存在的文件。其實在建立文件時我們就提到過,使用PUT /<index>/_doc/<id>的方法就可以更新一個已存在的文件。除此之外,我們還有另一種更新文件的方法:

POST /<index>/_update/<_id>

這兩種更新有所不同。_doc方法是先刪除原有的文件,再建立新的。而_update方法則是增量更新,它的更新過程是先檢索到文件,然後執行指定指令碼,最後重新索引。

還有一個區別就是_update方法支援使用指令碼更新,預設的語言是painless,你可以通過引數lang來進行設定。在請求引數方面,_update相較於_doc多了以下幾個引數:

  • lang:指定指令碼語言
  • retry_on_conflict:發生衝突時重試次數,預設是0
  • **_source**:設定為false,則不返回任何檢索欄位
  • **_source_excludes**:指定要從檢索結果排除的source欄位
  • **_source_includes**:指定要返回的檢索source欄位

下面的一個例子是用指令碼來更新文件

curl -X POST "localhost:9200/test/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
    "script" : {
        "source": "ctx._source.counter += params.count",
        "lang": "painless",
        "params" : {
            "count" : 4
        }
    }
}
'

Upsert

curl -X POST "localhost:9200/test/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
    "script" : {
        "source": "ctx._source.counter += params.count",
        "lang": "painless",
        "params" : {
            "count" : 4
        }
    },
    "upsert" : {
        "counter" : 1
    }
}
'

當指定的文件不存在時,可以使用upsert引數,建立一個新的文件,而當指定的文件存在時,該請求會執行script中的指令碼。如果不想使用指令碼,而只想新增/更新文件的話,可以使用doc_as_upsert。

curl -X POST "localhost:9200/test/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
    "doc" : {
        "name" : "new_name"
    },
    "doc_as_upsert" : true
}
'

update by query

這個API是用於批量更新檢索出的文件的,具體可以通過一個例子來了解。

curl -X POST "localhost:9200/twitter/_update_by_query?pretty" -H 'Content-Type: application/json' -d'
{
  "script": {
    "source": "ctx._source.likes++",
    "lang": "painless"
  },
  "query": {
    "term": {
      "user": "kimchy"
    }
  }
}
'

獲取文件

ES獲取文件用的是GET API,請求的格式是:

GET /<index>/_doc/<_id>

它會返回文件的資料和一些元資料,如果你只想要文件的內容而不需要元資料時,可以使用

GET /<index>/_source/<_id>

請求引數

獲取文件的有幾個請求引數之前已經提到過,這裡不再贅述,它們分別是:

  • refresh
  • routing
  • **_source**
  • **_source_excludes**
  • **_source_includes**
  • version
  • version_type

而還有一些之前沒提到過的引數,我們來具體看一下

  • preference:用來 指定執行請求的node或shard,如果設定為_local,則會優先在本地的分片執行
  • realtime:如果設定為true,則請求是實時的而不是近實時。預設是true
  • stored_fields:返回指定的欄位中,store為true的欄位

mget

mget是批量獲取的方法之一,請求的格式有兩種:

  • GET /_mget
  • GET /<index>/_mget

第一種是在請求體中寫index。第二種是把index放到url中,不過這種方式可能會觸發ES的安全檢查。

mget的請求引數和get相同,只是需要在請求體中指定doc的相關檢索條件

request

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

response

{
  "docs" : [
    {
      "_index" : "jackey",
      "_type" : "_doc",
      "_id" : "1",
      "_version" : 5,
      "_seq_no" : 6,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "user" : "ja",
        "tool" : "ES",
        "message" : "qwer"
      }
    },
    {
      "_index" : "jackey",
      "_type" : "_doc",
      "_id" : "2",
      "_version" : 1,
      "_seq_no" : 2,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "user" : "zhe",
        "post_date" : "2019-11-15T14:12:12",
        "message" : "learning Elasticsearch"
      }
    }
  ]
}

刪除文件

CURD操作只剩下最後一個D了,下面我們就一起來看看ES中如何刪除一個文件。

刪除指定id使用的請求是

DELETE /<index>/_doc/<_id>

在併發量比較大的情況下,我們在刪除時通常會指定版本,以確定刪除的文件是我們真正想要刪除的文件。刪除請求的引數我們在之前也都介紹過,想要具體瞭解的同學可以直接檢視官方文件。

delete by query

類似於update,delete也有一個delete by query的API。

POST /<index>/_delete_by_query

它也是要先按照條件來查詢匹配的文件,然後刪除這些文件。在執行查詢之前,Elasticsearch會先為指定索引做一個快照,如果在執行刪除過程中,要索引發生改變,則會導致操作衝突,同時返回刪除失敗。

如果刪除的文件比較多,也可以使這個請求非同步執行,只需要設定wait_for_completion=false即可。

這個API的refresh與delete API的refresh引數有所不同,delete中的refresh引數是設定操作是否立即可見,即只重新整理一個分片,而這個API中的refresh引數則是需要重新整理受影響的所有分片。

Bulk API

最後,我們再來介紹一種特殊的API,批量操作的API。它支援兩種寫法,可以將索引名寫到url中,也可以寫到請求體中。

  • POST /_bulk

  • POST /<index>/_bulk

在這個請求中,你可以任意使用之前的CRUD請求的組合。

curl -X POST "localhost:9200/_bulk?pretty" -H 'Content-Type: application/json' -d'
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
'

請求體中使用的語法是newline delimited JSON(NDJSON)。具體怎麼用呢?其實我們在上面的例子中已經有所展現了,對於index或create這樣的請求,如果請求本身是有包體的,那麼用換行符來表示下面的內容與子請求分隔,即為包體的開始。

例如上面例子中的index請求,它的包體就是{ "field1" : "value1" },所以它會在index請求的下一行出現。

對於批量執行操作來說,單條操作失敗並不會影響其他操作,而最終每條操作的結果也都會返回。

上面的例子執行完之後,我們得到的結果應該是

{
   "took": 30,
   "errors": false,
   "items": [
      {
         "index": {
            "_index": "test",
            "_type": "_doc",
            "_id": "1",
            "_version": 1,
            "result": "created",
            "_shards": {
               "total": 2,
               "successful": 1,
               "failed": 0
            },
            "status": 201,
            "_seq_no" : 0,
            "_primary_term": 1
         }
      },
      {
         "delete": {
            "_index": "test",
            "_type": "_doc",
            "_id": "2",
            "_version": 1,
            "result": "not_found",
            "_shards": {
               "total": 2,
               "successful": 1,
               "failed": 0
            },
            "status": 404,
            "_seq_no" : 1,
            "_primary_term" : 2
         }
      },
      {
         "create": {
            "_index": "test",
            "_type": "_doc",
            "_id": "3",
            "_version": 1,
            "result": "created",
            "_shards": {
               "total": 2,
               "successful": 1,
               "failed": 0
            },
            "status": 201,
            "_seq_no" : 2,
            "_primary_term" : 3
         }
      },
      {
         "update": {
            "_index": "test",
            "_type": "_doc",
            "_id": "1",
            "_version": 2,
            "result": "updated",
            "_shards": {
                "total": 2,
                "successful": 1,
                "failed": 0
            },
            "status": 200,
            "_seq_no" : 3,
            "_primary_term" : 4
         }
      }
   ]
}

批量操作的執行過程相比多次單個操作而言,在效能上會有一定的提升。但同時也會有一定的風險,所以我們在使用的時候要非常的謹慎。

總結

本文我們先介紹了文件的基本概念和文件的元資料。接著又介紹了文件的CRUD操作和Bulk API。相信看完文章你對Elasticsearch的文件也會有一定的瞭解。那最後就請你啟動你的Elasticsearch,然後親自動手試一試這些操作,看看各種請求的引數究竟有什麼作用。相信親手實驗過一遍之後你會對這些API有更深的印象