ElasticSearch索引、文檔基本操作
一、ElasticSearc 數(shù)據(jù)結(jié)構(gòu)
ES 用于索引和搜索的基本單位是文檔,可以將其認(rèn)為是 MySQL 里的一行。文檔以類型來分組,類型中包含若干文檔,類似 MySQL 表中包含若干行。最終,一個或多個類型存在于同一索引中,索引類似于 MySQL 中的數(shù)據(jù)庫。
注意上面這段話中出現(xiàn)的兩個“索引”,在學(xué)習(xí) ES 的過程中,有三種“索引”,分別是不同的含義:
1、索引(名詞):ES 中存儲數(shù)據(jù)的一個結(jié)構(gòu),類似于 MySQL 中的數(shù)據(jù)庫。
2、索引(動詞):保存一個文檔到索引(名詞)的過程,類似于 SQL 語句中的 INSERT 關(guān)鍵詞,如果該文檔已經(jīng)存在,就相當(dāng)于 UPDATE 關(guān)鍵字。
3、索引(倒排索引):ES 使用一個叫做“倒排索引”的數(shù)據(jù)結(jié)構(gòu),提高數(shù)據(jù)檢索速度,類似于 MySQL 中 B+?樹結(jié)構(gòu)的索引。
我們可以畫一個簡單的對比圖來類比 ElasticSearch 和傳統(tǒng)關(guān)系型數(shù)據(jù)庫 MySQL,從而幫助我們更好地理解 ES 的這些概念,見下圖1。

索引、類型、文檔、字段在 ES 中的結(jié)構(gòu)如下圖2所示。

從以上描述,我們可以知道,“索引+類型+文檔ID”這個組合就可以“唯一”確定 ES 中的某篇文檔,下面我們詳細(xì)講述一下什么是文檔、類型和索引。
1、索引(Index)
索引是文檔的容器,是一類文檔的集合,等同于 MySQL 中的數(shù)據(jù)庫。
索引命名規(guī)范:索引受文件系統(tǒng)的限制。僅可能為小寫字母,不能下劃線開頭。
舉例說明:比如電商系統(tǒng),有一個 customer_info 索引,存儲商城所有客戶的信息。
2、類型(Type)
類型用于區(qū)分同一個索引下不同的數(shù)據(jù)類型,等同于 MySQL 中的表。
類型命名規(guī)范:類型名稱可以包括除了null的任何字符,不能以下劃線開頭。7.0版本之后不再支持類型,默認(rèn)為_doc。
舉例說明:customer_info 索引中有兩個類型,分別是 common_customer_info 和 vip_customer_info,分別表示普通客戶信息和vip客戶信息。
我們需要注意一下 ES 關(guān)于類型(Type)的改革:
在 5.X 版本中,一個索引下可以創(chuàng)建多個類型。
在 6.X 版本中,一個索引下只能存在一個類型。
在 7.X 版本中,一個 Index 中只有一個默認(rèn)的 Type,這個 Type 的名字為_doc。即在 7.x 版本的 ES 中,庫表合一,Index 既可以被認(rèn)為對應(yīng) MySQL 的 Database,也可以認(rèn)為對應(yīng) Table。也可以這樣理解:ES 實例對應(yīng) MySQL 實例中的一個 Database,Index 對應(yīng) MySQL 中的 Table,Document 對應(yīng) MySQL 中表的一行記錄。
在 8.x 版本中,徹底廢除 Type。
為什么不建議使用類型呢?或者說,為什么 ES 后來也逐漸廢棄了類型呢?
這是因為 ES 是基于 Lucene 實現(xiàn)的搜索引擎,Lucene 的全文檢索功能之所以快,是因為“倒排索引”的存在。而這種“倒排索引”是基于索引(index)生成的,并非類型(type),多個類型(type)反而會減慢搜索的速度。
為了保持 Elasticsearch “一切為了搜索” 的宗旨,適當(dāng)?shù)淖鲂└淖儯◤U除類型)也是無可厚非的,也是值得的。
3、文檔(Document)
ES 中的單條記錄稱為文檔,等同于 MySQL 中的行。
舉例說明:common_customer_info 中每一行就是一個普通客戶的信息,包括客戶的 name、age、home、birthday、hobbies 等信息。
4、字段(field)
文檔由字段組成,ElasticSearch 中的字段等同于 MySQL 中的列。
字段命名規(guī)范:字段不能使用空格,對象類型可以使用點號“.”,舉例,info 字段值是一個對象 name,name 對象包括兩個屬性 firstname 和 lastname。
"info.name.firstname":?"zhou"
"info.name.lastname":?"xuejiao"
相當(dāng)于:
"info":?{
??"name":?{
????"firstname":?"zhou",
????"lastname":?"xuejiao"
??}
}
舉例說明:3 示例中的 name、age、home、birthday、hobbies 就是字段。
5、映射(mapping)
在 ES 中,對于字段的定義稱為映射,比如 name 字段映射為字符串類型。
二、索引、文檔基本操作
1、RESTful 架構(gòu)
RESTful 架構(gòu)有以下幾個特點:
a.服務(wù)端的每一個資源都有一個 URI(統(tǒng)一資源標(biāo)識符)與之對應(yīng)。
b.客戶端通過 HTTP 協(xié)議與服務(wù)端進(jìn)行通信。
c.客戶端通過四個 HTTP 動詞(GET、POST、PUT、DELETE),對服務(wù)器端資源進(jìn)行操作,GET 用來獲取資源,POST 用來新建資源(也可以用于更新資源),PUT 用來更新資源,DELETE用來刪除資源。
2、ES 的 RESTful 風(fēng)格操作

3、索引、文檔基本操作案例
操作 ES 的索引、文檔有多種方式,我們可以使用 curl 工具,或者使用 Kibana/Head 插件,來操作 ES 中的索引、文檔,這兩種方式我們都來了解
curl 是一個利用 URL 語法在 Linux 命令行下工作的文件傳輸工具。
(1)索引操作
a.創(chuàng)建索引
1、使用 curl 命令創(chuàng)建索引。
//?請求
curl?-XPUT?'http://192.168.56.10:9200/common_customer_info?pretty'?-H?'Content-Type:?application/json'?-d?'
{
??"settings":{
????"number_of_shards":5,?//?指定索引分片數(shù)量
????"number_of_replicas"?:?1,?//?指定索引副本分片數(shù)量
????"refresh_interval":"30s"?//?指定索引刷新時間
??}
}'
//?響應(yīng)
{
??"acknowledged"?:?true,
??"shards_acknowledged"?:?true,
??"index"?:?"common_customer_info"
}

2、使用 Kibana 創(chuàng)建索引。
//?請求
PUT?/common_customer_info
{
??"settings":{
????"number_of_shards":5,
????"number_of_replicas"?:?1,
????"refresh_interval":"30s"?
??}
}?
//?響應(yīng)
{
??"acknowledged"?:?true,
??"shards_acknowledged"?:?true,
??"index"?:?"common_customer_info"
}

b.查看索引
b.1 查看指定索引
1、使用 curl 命令查看指定索引。
//?請求,查看?common_customer_info?這個索引的信息
//?注意:如果是 GET 請求,curl 命令可以省略?-XGET
curl?'http://192.168.56.10:9200/common_customer_info'
//?響應(yīng)
{
??"common_customer_info"?:?{
????"aliases"?:?{?},?//?別名
????"mappings"?:?{?},?//?映射,即索引中有哪些字段,以及字段的類型
????"settings"?:?{?//?索引配置
??????"index"?:?{
????????"refresh_interval"?:?"30s",//?索引刷新時間
????????"number_of_shards"?:?"5",//?分片數(shù)
????????"provided_name"?:?"common_customer_info",//?索引名稱
????????"creation_date"?:?"1634191993054",//?索引創(chuàng)建時間
????????"number_of_replicas"?:?"1",//?副本分片數(shù)
????????"uuid"?:?"rHtVcwjMSn-4vyvWv_Rr-A",//?索引id
????????"version"?:?{//?索引版本號
??????????"created"?:?"7080099"
????????}
??????}
????}
??}
}

2、使用 Kibana 查看指定索引。
//?請求
GET?/common_customer_info
//?響應(yīng)
{
??"common_customer_info"?:?{
????"aliases"?:?{?},
????"mappings"?:?{?},
????"settings"?:?{
??????"index"?:?{
????????"refresh_interval"?:?"30s",
????????"number_of_shards"?:?"5",
????????"provided_name"?:?"common_customer_info",
????????"creation_date"?:?"1634191993054",
????????"number_of_replicas"?:?"1",
????????"uuid"?:?"rHtVcwjMSn-4vyvWv_Rr-A",
????????"version"?:?{
??????????"created"?:?"7080099"
????????}
??????}
????}
??}
}

b.2 查看所有索引
1、使用 curl 命令查看所有索引。
//?請求
curl?http://192.168.56.10:9200/_cat/indices\?v
//?響應(yīng)
health?status?index??????????????????????????uuid???????????????????pri?rep?docs.count?docs.deleted?store.size?pri.store.size
green??open???.kibana-event-log-7.8.0-000001?LjLds26VQV2kafEdLV57Zg???1???0??????????5????????????0?????25.7kb?????????25.7kb
green??open???.apm-custom-link???????????????n_FOUb-jQaqQJXeYTzQ85A???1???0??????????0????????????0???????208b???????????208b
green??open???.kibana_task_manager_1?????????HaE-DEJmT9WoC4RbT-oCCg???1???0??????????5????????????0?????45.4kb?????????45.4kb
green??open???kibana_sample_data_ecommerce???K8HCVTKoSj-002bU6TxjBA???1???0???????4675????????????0??????4.2mb??????????4.2mb
green??open???.apm-agent-configuration???????lBbpATyqRk-mQOVzSHK7Tw???1???0??????????0????????????0???????208b???????????208b
yellow?open???common_customer_info???????????rHtVcwjMSn-4vyvWv_Rr-A???5???1??????????0????????????0????????1kb????????????1kb
green??open???.kibana_1??????????????????????DLYiBmlgR7G4CzOE8nNvIA???1???0????????216????????????5??????1.1mb??????????1.1mb

2、使用 Kibana 查看所有索引。
//?請求
GET?/_cat/indices\?v
//?響應(yīng)和?1?一樣

b.3 根據(jù)條件查看索引
1、使用 curl 命令,根據(jù)條件查看索引。
//?請求,查看所有以字母“c”開頭的索引
curl?http://192.168.56.10:9200/_cat/indices/c\*\?v
//?響應(yīng)
health?status?index????????????????uuid???????????????????pri?rep?docs.count?docs.deleted?store.size?pri.store.size
yellow?open???common_customer_info?rHtVcwjMSn-4vyvWv_Rr-A???5???1??????????0????????????0????????1kb????????????1kb

c.刪除索引
1、使用 curl 命令刪除索引。
//?請求,刪除?common_customer_info?這個索引
curl?-XDELETE?'http://192.168.56.10:9200/common_customer_info'
//響應(yīng)
{
??"acknowledged":true
}

2、使用 Kibana 刪除索引。
//?請求
DELETE?/common_customer_info
//?響應(yīng)
{
??"acknowledged"?:?true
}

(2)文檔操作
a.創(chuàng)建文檔(指定文檔id)
1、使用 curl 命令,創(chuàng)建文檔(指定文檔id)。
//?請求,1 表示新建文檔的 id。
curl?-XPUT?"http://192.168.56.10:9200/common_customer_info/_doc/1"?-H?'Content-Type:?application/json'?-d'
{
????"name":"張三",
????"age":27,
????"home":"北京市朝陽區(qū)xx街道xx號",
????"birthday":"1996-05-11",
????"hobbies":[
????????"跑步",
????????"爬山",
????????"擼貓"
????]
}'
//?響應(yīng)
{
??"_index"?:?"common_customer_info",//?文檔所在的索引。
??"_type"?:?"_doc",//?文檔所在的類型,我們使用的類型是 ES 7.x 版本默認(rèn)的?_doc。
??"_id"?:?"1",//?文檔id。
??"_version"?: 1,//?文檔的版本信息,每對文檔更新一次,版本號就加1。ES 通過使用 version 來保證對文檔的變更能以正確的順序執(zhí)行,,避免亂序造成的數(shù)據(jù)丟失。
??"result"?:?"created",//?執(zhí)行結(jié)果,created 文檔創(chuàng)建成功。
??"_shards"?:?{//?表示分片信息。
????"total"?: 2,//?一共有2個分片,由下一句可以知道主分片數(shù)量為1,所以副本分片數(shù)量為 2-1=1 個。
????"successful"?:?1,//?數(shù)據(jù)寫入主分片,"successful"?: 1 表示主分片有1個,并寫入成功。
????"failed"?:?0//?寫入失敗0個。
??},
??"_seq_no"?:?0,//?嚴(yán)格遞增的順序號,是一個整數(shù),保證后寫入的文檔的?_seq_no 大于先寫入的文檔的_seq_no。
??"_primary_term"?: 1//?和?_seq_no 一樣,也是一個整數(shù),每當(dāng)主分片發(fā)生重分配時,比如重啟、主分片選舉,_primary_term 會遞增1。
}
ElasticSearch 使用 _version、_seq_no、_primary_term 這三個字段來做版本控制。

2、使用 Kibana,創(chuàng)建文檔(指定文檔id)。
//?請求
PUT?/common_customer_info/_doc/1
{
????"name":"張三",
????"age":27,
????"home":"北京市朝陽區(qū)xx街道xx號",
????"birthday":"1996-05-11",
????"hobbies":[
????????"跑步",
????????"爬山",
????????"擼貓"
????]
}
//?響應(yīng)和?1?一樣

b.創(chuàng)建文檔(不指定文檔id,使用 ES 自動生成的文檔id)
創(chuàng)建文檔時,也可以不指定 id,此時 ES 會自動生成一個文檔id,如果不指定 id,則需要使用 POST 請求,而不能使用 PUT 請求。
1、使用 curl 命令,創(chuàng)建文檔(不指定文檔id,使用 ES 自動生成的文檔id)。
//?請求
curl?-XPOST?"http://192.168.56.10:9200/common_customer_info/_doc"?-H?'Content-Type:?application/json'?-d'
{
????"name":"張三",
????"age":27,
????"home":"北京市朝陽區(qū)xx街道xx號",
????"birthday":"1996-05-11",
????"hobbies":[
????????"跑步",
????????"爬山",
????????"擼貓"
????]
}'
//?響應(yīng)
{
?"_index":?"common_customer_info",
?"_type":?"_doc",
?"_id":?"HyrDfXwBWK9BMtd1hplW",
?"_version":?1,
?"result":?"created",
?"_shards":?{
??"total":?2,
??"successful":?1,
??"failed":?0
?},
?"_seq_no":?0,
?"_primary_term":?1
}

2、使用 Kibana,創(chuàng)建文檔(不指定文檔id,使用 ES 自動生成的文檔id)。
POST?/common_customer_info/_doc
{
????"name":"張三",
????"age":27,
????"home":"北京市朝陽區(qū)xx街道xx號",
????"birthday":"1996-05-11",
????"hobbies":[
????????"跑步",
????????"爬山",
????????"擼貓"
????]
}
//?響應(yīng)和 1 差不多,只有文檔id不一樣,因為 ES 生成的文檔id是隨機的。

c.查詢文檔
c.1 查詢當(dāng)前索引當(dāng)前類型中的所有文檔
1、使用 curl 命令,查詢當(dāng)前索引當(dāng)前類型中的所有文檔。
//?請求
curl?-XPOST?http://192.168.56.10:9200/common_customer_info/_doc/_search
//?響應(yīng)
{
??"took"?:?7,
??"timed_out"?:?false,
??"_shards"?:?{
????"total"?:?1,
????"successful"?:?1,
????"skipped"?:?0,
????"failed"?:?0
??},
??"hits"?:?{
????"total"?:?{
??????"value"?:?1,
??????"relation"?:?"eq"
????},
????"max_score"?:?1.0,
????"hits"?:?[
??????{
????????"_index"?:?"common_customer_info",
????????"_type"?:?"_doc",
????????"_id"?:?"aCrGfXwBWK9BMtd1jJl3",
????????"_score"?:?1.0,
????????"_source"?:?{
??????????"name"?:?"張三",
??????????"age"?:?27,
??????????"home"?:?"北京市朝陽區(qū)xx街道xx號",
??????????"birthday"?:?"1996-05-11",
??????????"hobbies"?:?[
????????????"跑步",
????????????"爬山",
????????????"擼貓"
??????????]
????????}
??????}
????]
??}
}

2、通過 Kibana,查詢當(dāng)前索引當(dāng)前類型中的所有文檔。
//?請求
POST?/common_customer_info/_doc/_search
//?響應(yīng)和?1?一樣

c.2 通過文檔id查詢文檔
1、使用 curl 命令,通過文檔 id 查詢文檔。
//?請求,查詢?文檔id=aCrGfXwBWK9BMtd1jJl3?的這條文檔
curl?http://192.168.56.10:9200/common_customer_info/_doc/aCrGfXwBWK9BMtd1jJl3
//?響應(yīng)
{
??"_index"?:?"common_customer_info",
??"_type"?:?"_doc",
??"_id"?:?"aCrGfXwBWK9BMtd1jJl3",
??"_version"?:?1,
??"_seq_no"?:?0,
??"_primary_term"?:?1,
??"found"?:?true,
??"_source"?:?{
????"name"?:?"張三",
????"age"?:?27,
????"home"?:?"北京市朝陽區(qū)xx街道xx號",
????"birthday"?:?"1996-05-11",
????"hobbies"?:?[
??????"跑步",
??????"爬山",
??????"擼貓"
????]
??}
}

2、通過 Kibana,通過文檔id查詢文檔。
//?請求
GET?/common_customer_info/_doc/aCrGfXwBWK9BMtd1jJl3
//?響應(yīng)和?1?一樣

d.更新文檔
d.1 根據(jù)文檔id修改部分字段
在修改字段時,有一個坑大家需要注意,我就經(jīng)常掉坑里(尷尬)。
我們想要修改上面那個文檔,將 name 字段由"張三"修改成"李四",我想當(dāng)然地寫出如下請求命令:
curl?-XPOST?"http://192.168.56.10:9200/common_customer_info/_doc/aCrGfXwBWK9BMtd1jJl3"?-H?'Content-Type:?application/json'?-d'
{
????"name":"李四"
}'
執(zhí)行這條命令,響應(yīng)結(jié)果如下:
{
?"_index":?"common_customer_info",
?"_type":?"_doc",
?"_id":?"aCrGfXwBWK9BMtd1jJl3",
?"_version":?2,
?"result":?"updated",
?"_shards":?{
??"total":?2,
??"successful":?1,
??"failed":?0
?},
?"_seq_no":?1,
?"_primary_term":?2
}
我們再去看一下這個文檔,發(fā)現(xiàn)name字段值修改成功了,但是整個文檔只剩下 name 一個字段了,其他字段全都沒了。這是因為使用這種方式,更新的文檔 {"name": "李四"} 會覆蓋掉原來的文檔。
如果不想讓除了修改字段之外的其他字段丟失,請求體中必須指明 doc,將要修改的字段放到 doc 中。
//?curl?命令
curl?-XPOST?"http://192.168.56.10:9200/common_customer_info/_doc/aCrGfXwBWK9BMtd1jJl3"?-H?'Content-Type:?application/json'?-d'
{??
??"doc":{
????"name":"李四"
??}
}'
//?Kibana
POST?/common_customer_info/_doc/aCrGfXwBWK9BMtd1jJl3/_update
{
??"doc":{
????"name":"李四"
??}
}
d.2 根據(jù)其他條件修改
我們想根據(jù)哪個字段(除了文檔id)修改文檔,就把這個字段放到 script 腳本中。
在 script 腳本中,lang 表示腳本語言,painless 是 es 內(nèi)置的一種腳本語言。source 表示具體執(zhí)行的腳本,ctx 是一個上下文對象,通過 ctx 可以訪問到 _source、_title 等。
//?請求,將?name?=?李四?的?home?修改為?吉林省長春市xx街道xx號
curl?-XPOST?"http://192.168.56.10:9200/common_customer_info/_doc/_update_by_query"?-H?'Content-Type:?application/json'?-d'
{
??"query":{
????"match":{
??????"name":"李四"
????}
??},
??"script":{
????"source":"ctx._source.home?=?\"吉林省長春市xx街道xx號\""
??}
}'
f.刪除文檔
f.1 根據(jù)文檔id刪除文檔
//?刪除文檔?id=aCrGfXwBWK9BMtd1jJl3?這條文檔
curl?-XDELETE?"http://192.168.56.10:9200/common_customer_info/_doc/aCrGfXwBWK9BMtd1jJl3"
f.2 根據(jù)其他字段(除了文檔id)刪除文檔
//?刪除?name=李四?的所有文檔
curl?-XPOST?"http://192.168.56.10:9200/common_customer_info/_doc/_delete_by_query"?-H?'Content-Type:?application/json'?-d'{
??"query":{
????"match":{
??????"name":"李四"
????}
??}
}'
