0%

Elasticsearch (二) - 快速搭建與 Document 的建立、更新和刪除

上一篇 介紹了 Elasticsearch 的一些基本概念,這一篇就要來介紹如何操作 Elasticsearch。

本篇會介紹從搭建 Elasticsearch 到建立、更新和刪除 Document,查詢的部份因為內容較多將在 下一篇 來做介紹。

建立 Elasticsearch + Kibana

Kibana 是 Elasticsearch 的視覺化工具,本篇操作會以 Kibana 為主。如果想用其他 API 的工具,例如 Postman,後面介紹會舉一個例子示範。

首先我們使用 Docker-Compose 來快速搭建出一個 Elasticsearch + Kibana 的 Server。如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
version: '3.7'
services:
elasticsearch:
image: elasticsearch:6.5.4
container_name: elasticsearch
environment:
- discovery.type=single-node
ports:
- 9200:9200
- 9300:9300
networks:
- esnet
kibana:
image: kibana:6.5.4
container_name: kibana
ports:
- 5601:5601
networks:
- esnet
depends_on:
- elasticsearch
networks:
esnet:
driver: bridge
name: elasticsearch_esnet

若 Elasticsearch 啟動成功可以在輸出找到這一行,可以看到 message 的值為 started。

1
2
3
4
{"type": "server", "timestamp": "2020-07-29T05:51:37,414Z", "level": "INFO", 
"component": "o.e.n.Node", "cluster.name": "docker-cluster",
"node.name": "2f019cbcdb2b", "message": "started",
"cluster.uuid": "4PjAfq-HT32ZnH-FDgtnsA", "node.id": "0aqSpwcDSnixl4NfMVRpYA" }

另外在輸出還可以找到 Gateway 這一行,Getway 是用來讓資料持久化的組件,以避免資料在節點故障時遺失。而啟動時 Gateway 會檢查硬碟內是否有資料被保存過,如果有可以用來恢復資料。

1
2
3
4
{"type": "server", "timestamp": "2020-07-29T05:51:37,505Z", "level": "INFO",
"component": "o.e.g.GatewayService", "cluster.name": "docker-cluster",
"node.name": "2f019cbcdb2b", "message": "recovered [0] indices into cluster_state",
"cluster.uuid": "4PjAfq-HT32ZnH-FDgtnsA", "node.id": "0aqSpwcDSnixl4NfMVRpYA" }

而 Kibana 會自動去尋找 Elasticsearch 並和他進行連接,所以只要讓 Kibana 在 Elasticsearch 啟動後再啟動即可,不需要再特別對 Kibana 設定。

Port 設定

啟動 Elasticsearh 的 Container 時我們有指定了兩組 Port,分別是 9300 和 9200。

  • 9300 預設用於節點之間的通訊,稱為 Transport。
1
2
3
4
{"type": "server", "timestamp": "2020-07-29T05:51:37,147Z", "level": "INFO",
"component": "o.e.t.TransportService", "cluster.name": "docker-cluster",
"node.name": "2f019cbcdb2b", "message": "publish_address {172.17.0.3:9300},
bound_addresses {0.0.0.0:9300}" }
  • 9200 預設用於 Http 的通訊。
1
2
3
4
5
{"type": "server", "timestamp": "2020-07-29T05:51:37,412Z", "level": "INFO",
"component": "o.e.h.AbstractHttpServerTransport", "cluster.name": "docker-cluster",
"node.name": "2f019cbcdb2b", "message": "publish_address {172.17.0.3:9200},
bound_addresses {0.0.0.0:9200}", "cluster.uuid": "4PjAfq-HT32ZnH-FDgtnsA",
"node.id": "0aqSpwcDSnixl4NfMVRpYA" }

連接 Elasticsearch

以上啟動完成後就可以在瀏覽器輸入 http://localhost:9200 來連接 Elasticsearch。如果 Elasticsearch 正常運作的話,會回傳一個 Json 作為 Response。如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name" : "aheFnoR",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "q8glcZZRRe-I-ESRz-9ZCg",
"version" : {
"number" : "6.5.4",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "d2ef93d",
"build_date" : "2018-12-17T21:17:40.758843Z",
"build_snapshot" : false,
"lucene_version" : "7.5.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}

連接 Kibana

在瀏覽器輸入 http://localhost:5601 來連接 Kibana,成功打開可以看到以下的畫面。
Kibana

接著切換到 Dev Tools 就可以開始執行指令操作了。Dev Tools 的左邊是 Console 可以輸入 Request,執行後右邊會輸出結果。
Kibana Dev Tools

Elasticsearch 採用 Query DSL (查詢表達式) 來描述查詢的條件,Qeury DSL 是以 Json 格式來撰寫。

常用指令

Cluster 狀態

Cluster 的狀態可以用 _cat 命令來查看。

health

檢查 Cluster 的健康狀態,若回傳 green 則代表健康,若顯示 yellow 或 red 則代表不健康,詳細各個狀態代表甚麼請參考上一篇

1
GET _cat/health

如果使用 Postman 的話,Request Method 就依照範例給的對應即可。Uri 就以 Elasticsearch 的 ip + port + 範例給的路徑即可。如下 :

1
http://localhost:9200/_cat/health

回傳結果如下 :

1
1596805350 13:02:30 docker-cluster green 1 1 1 1 0 0 0 0 - 100.0%

health

建立 Index

直接指定 Index 名稱即可建立 Index。

1
PUT <IndexName>

範例

建立一個名為 sport 的 Index。

1
PUT sport

若建立成功會回傳如下 :

1
2
3
4
5
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "sport"
}

建立 Document

在 Index 後加上 Type,再加上 Document ID,並且加入要新增到 Document 的內容。這裡要注意的是前一篇有提到相同 Index 下的 Field 如果名稱相同必須要是相同的 Data Type。

1
2
3
4
POST /<Index>/<Type>/<Doc ID>/_create
{
"<FieldName>": <Value>
}

如果使用 Postman,則 { "<FieldName>": <Value> } 就放在 Body,並且格式要使用 Json。這一段就是上方所提到的 DSL 查詢表達式,下方所有的操作都是一樣的方法,就不再針對 Postman 或其他 API 工具贅述。

create document

範例

建立一個 Document 在 basketball 這個 Type 下,且這個 Type 在 sport 這個 Index 下。而這個 Document 的 ID 是 1。

1
2
3
4
5
6
POST /sport/basketball/1/_create
{
"team": "Lakers",
"location": "Los Angelas",
"assets": 100
}

建立成功會回傳下面這些訊息,可以看到 _index、_type、_id 跟上面指定的一樣。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "sport",
"_type" : "basketball",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}

建立好後我們先用簡單的查詢指令來看一下剛剛建好的 Document。

1
POST /sport/basketball/_search

可以看到輸出的結果會有這一段剛剛建好的 Document,更進一步的查詢下面會再介紹。

1
2
3
4
5
6
7
8
9
10
11
12
13
"hits": [
{
"_index": "sport",
"_type": "basketball",
"_id": "1",
"_score": 1.0,
"_source": {
"team": "Lakers",
"location": "Los Angelas",
"assets": 100
}
}
]

Mapping

建立好 Document 後,你可能會想為什麼不用先定義 Schema 就可以直接寫資料進去呢 ?

事實上,非關聯式資料庫不需要事先定義哪個欄位要放什麼,在 Elasticsearch 這個定義叫做 Mapping。所以當你直接建立 Document 時,Elasticsearch 就會自動幫你建立 Mapping。

要查看和設定 Mapping 可以使用 _mapping 這個指令。

取得 Index Mapping

1
GET /<Index>/_mapping

範例
取得 Sport 的 Mapping。

1
GET /sport/_mapping

可以看到回傳結果裡列出了每一個 Field 的 DataType。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"sport" : {
"mappings" : {
"basketball" : {
"properties" : {
"assets" : {
"type" : "long"
},
"location" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"team" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}

這裡有一個要特別注意的地方是,可以看到 team 的 field type 是 text,也就是字串,這個是沒問題的。但是下面又還有一個 fields – keyword,而他的 type 是 keyword。這是 Elasticsearch 自動建立的一個 Field,叫做 team.keyword

這個 Field 的存在是因為 Elasticsearch 會對 text 做分析並拆解,而 Keyword 會保留原始的內容,例如完整的句子。當要查詢時如果是查 keyword 這個 Field,一定要完全符合內容。因為 keyword 這個 type 就是定義不拆解內容。

自訂義 Mapping

除了讓 Elasticsearch 自己建立 Mapping,也可以自己定義。自己定義要在建立 Document 之前定義,如同關聯式資料庫要先定義 Table Schema。

1
2
3
4
5
6
7
8
9
10
11
12
POST /<Index>/_mapping/<Type>
{
"properties": {
"<Field>": {
"type": "<data type>"
},
"<Field>": {
"type": "<data type>"
},
...
}
}

要特別注意當 Mapping 已經定義好之後,不論是自動定義還是自己定義的,都不能去修改。唯一能做的是加入新的 Field。

範例
這裡我們示範加入一個新的 Field,加入的寫法和要自定義是一樣的,所以可以想成都是要新增 Field 就好。

1
2
3
4
5
6
7
8
POST /sport/_mapping/basketball
{
"properties": {
"champion": {
"type": "long"
}
}
}

新增完 Field 後,再次取得 Mapping 來看。可以看到多了一個剛剛加入的 champion。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"sport" : {
"mappings" : {
"basketball" : {
"properties" : {
"assets" : {
"type" : "long"
},
"champion" : {
"type" : "long"
},
"location" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"team" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}

更新 Document

更新可以使用 _update 指令,但是要搭配 doc 將更新的 Field 和值包起來。更新的時候 Type 後面要加上 Document 的 ID 系統才會知道要改哪個 Document。

1
2
3
4
5
6
7
8
POST /<Index>/<Type>/<Doc ID>/_update
{
"doc": {
"<Field>": <Value>,
"<Field>": <Value>,
...
}
}

範例

前面我們新增了一個 Field,現在我們就利用更新的方式來補上這個 Field 的值。

1
2
3
4
5
6
POST sport/basketball/1/_update
{
"doc": {
"champion": 16
}
}

更新成功會回傳 result 為 updated。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "sport",
"_type" : "basketball",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}

再次取得 Document 來看,可以發現 champion 已經有值了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"hits" : [
{
"_index" : "sport",
"_type" : "basketball",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"team" : "Lakers",
"location" : "Los Angelas",
"assets" : 100,
"champion" : 16
}
}
]

刪除 Document

刪除 Document 只要指定 Index、Type 和 Document ID 即可刪除。

1
DELETE <Index>/<Type>/<Doc ID>

範例

我們目前只有建立一個 Document,就刪除這個 Document。

1
DELETE sport/basketball/1

刪除成功 result 會回傳 deleted。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index" : "sport",
"_type" : "basketball",
"_id" : "1",
"_version" : 3,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}

批次處理

Elasticsearch 提供以批次的方式來大量處理資料,可以使用 _bulk 指令來進行批次處理。

Create

Create 用於建立 Document,若 Document 已存在則會回傳錯誤。

1
2
3
4
5
6
POST <Index>/<Type>/_bulk
{ "create" : { "_id": <Doc ID> } }
{ "<Field>":<Value>, "<Field>":<Value>, ...}
{ "create" : { "_id": <Doc ID> } }
{ "<Field>":<Value>, "<Field>":<Value>, ...}
...

如果每次要處理的 Index 或是 Type 不一樣的話,也可以個別指定。如下 :

1
2
3
4
5
6
POST /_bulk
{ "create" : { "_index": "<Index>", "_type": "<Type>", "_id": <Doc ID> } }
{ "<Field>":<Value>, "<Field>":<Value>, ...}
{ "create" : { "_index": "<Index>", "_type": "<Type>", "_id": <Doc ID> } }
{ "<Field>":<Value>, "<Field>":<Value>, ...}
...

範例

1
2
3
4
5
6
7
POST sport/basketball/_bulk
{ "create" : { "_id": 1 } }
{ "team":"Celtics", "location":"Boston", "assets": 100,"champion": 17}
{ "create" : { "_id": 2 } }
{ "team": "Lakers", "location":"Los Angelas","assets": 150,"champion": 16}
{ "create" : { "_id": 3 } }
{ "team": "Bulls", "location":"Chicago", "assets": 120, "champion": 6}

Index

Index 用於建立或更新 Document,如果 Document 不存在則建立,存在則更新。

1
2
3
4
5
6
POST <Index>/<Type>/_bulk
{ "index" : { "_id": <Doc ID> } }
{ "<Field>":<Value>, "<Field>":<Value>, ...}
{ "index" : { "_id": <Doc ID> } }
{ "<Field>":<Value>, "<Field>":<Value>, ...}
...

範例
這個範例我們更新 ID 為 3 的 Docuemt,並且新增一個 Document。

1
2
3
4
5
POST sport/basketball/_bulk
{ "index" : { "_id": 3 } }
{ "team": "Bulls", "location":"Chicago", "assets": 130, "champion": 6}
{ "index" : { "_id": 4 } }
{ "team": "Spurs", "location":"San Antonio", "assets": 160, "champion": 5}

可以看到下面的結果,一個是 updated、另一個是 created。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"took" : 79,
"errors" : false,
"items" : [
{
"index" : {
"_index" : "sport",
"_type" : "basketball",
"_id" : "3",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 2,
"status" : 200
}
},
{
"index" : {
"_index" : "sport",
"_type" : "basketball",
"_id" : "4",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 2,
"status" : 201
}
}
]
}

Update

Update 用於更新 Document,如果 Document 不存在會回傳錯誤。

1
2
3
4
5
6
POST <Index>/<Type>/_bulk
{ "update" : { "_id": <Doc ID> } }
{ "doc":{ "<Field>": <Value>, "<Field>": <Value>, ...}}
{ "update" : { "_id": <Doc ID> } }
{ "doc":{ "<Field>": <Value>, "<Field>": <Value>, ...}}
...

範例

1
2
3
4
5
POST sport/basketball/_bulk
{ "update" : { "_id": 3 } }
{ "doc":{ "assets": 180}}
{ "update" : { "_id": 4 } }
{ "doc":{ "assets": 100}}

Delete

Delete 用於刪除 Document,直接指定 Document ID 即可,如果 Document 不存在會回傳錯誤。

1
2
3
4
POST <Index>/<Type>/_bulk
{ "delete" : { "_id": <Doc ID> } }
{ "delete" : { "_id": <Doc ID> } }
...

範例

1
2
3
POST sport/basketball/_bulk
{ "delete" : { "_id": 3 } }
{ "delete" : { "_id": 4 } }

批次合併處理

上述介紹的這些批次指令可以合在一起執行。

1
2
3
4
5
6
7
8
POST <Index>/<Type>/_bulk
{ "create" : { "_id": <Doc ID> } }
{ "<Field>":<Value>, "<Field>":<Value>, ...}
{ "index" : { "_id": <Doc ID> } }
{ "<Field>":<Value>, "<Field>":<Value>, ...}
{ "update" : { "_id": <Doc ID> } }
{ "doc":{ "<Field>": <Value>, "<Field>": <Value>, ...}}
{ "delete" : { "_id": <Doc ID> } }

範例

1
2
3
4
5
6
7
8
POST sport/basketball/_bulk
{ "create" : { "_id": 3 } }
{ "team": "Bulls", "location":"Chicago", "assets": 150, "champion": 6}
{ "index" : { "_id": 4 } }
{ "team": "Spurs", "location":"San Antonio", "assets": 160, "champion": 5}
{ "update" : { "_id": 3 } }
{ "doc":{ "assets": 180}}
{ "delete" : { "_id": 4 } }

結果如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
{
"took" : 24,
"errors" : false,
"items" : [
{
"create" : {
"_index" : "sport",
"_type" : "basketball",
"_id" : "3",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 6,
"_primary_term" : 2,
"status" : 201
}
},
{
"index" : {
"_index" : "sport",
"_type" : "basketball",
"_id" : "4",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 4,
"_primary_term" : 2,
"status" : 201
}
},
{
"update" : {
"_index" : "sport",
"_type" : "basketball",
"_id" : "3",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 7,
"_primary_term" : 2,
"status" : 200
}
},
{
"delete" : {
"_index" : "sport",
"_type" : "basketball",
"_id" : "4",
"_version" : 2,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 5,
"_primary_term" : 2,
"status" : 200
}
}
]
}

Summary

本篇介紹了如何快速搭建 Elasticsearch 和 Kibana,也介紹了如何建立、更新和刪除 Document。

下一篇 我們將介紹如何查詢。

參考

[1] 使用 Docker 建立 ElasticSearch (1) 建立 ES
[2] Elasticsearch Basic Operation
[3] Removal of mapping types
[4] ElasticSearch 中的索引與類型的前生今世
[5] Elasticsearch.Net
[6] Elasticsearch.Net and NEST
[7] Elasticsearch: 權威指南
[8] Docker-Compose 建立 Elasticsearch 與 Kibana 服務
[9] ES Mapping、字段類型 Field type 詳解
[10] ElasticSearch Missing