上一篇 介紹了 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}" }
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,成功打開可以看到以下的畫面。
接著切換到 Dev Tools 就可以開始執行指令操作了。Dev Tools 的左邊是 Console 可以輸入 Request,執行後右邊會輸出結果。
Elasticsearch 採用 Query DSL (查詢表達式) 來描述查詢的條件,Qeury DSL 是以 Json 格式來撰寫。
常用指令
Cluster 狀態
Cluster 的狀態可以用 _cat
命令來查看。
health
檢查 Cluster 的健康狀態,若回傳 green 則代表健康,若顯示 yellow 或 red 則代表不健康,詳細各個狀態代表甚麼請參考上一篇 。
如果使用 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%
建立 Index
直接指定 Index 名稱即可建立 Index。
範例
建立一個名為 sport 的 Index。
若建立成功會回傳如下 :
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 工具贅述。
範例
建立一個 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
範例
取得 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