Elasticsearch數(shù)據(jù)的存儲(chǔ)格式
Elastcisearch 是分布式的 文檔 存儲(chǔ)。它能存儲(chǔ)和檢索復(fù)雜的數(shù)據(jù)結(jié)構(gòu)—?序列化成為JSON文檔—?以 實(shí)時(shí) 的方式。 換句話說(shuō),一旦一個(gè)文檔被存儲(chǔ)在 Elasticsearch 中,它就是可以被集群中的任意節(jié)點(diǎn)檢索到
在 Elasticsearch 中, 每個(gè)字段的所有數(shù)據(jù) 都是 默認(rèn)被索引的 。 即每個(gè)字段都有為了快速檢索設(shè)置的專用倒排索引。而且,不像其他多數(shù)的數(shù)據(jù)庫(kù),它能在 同一個(gè)查詢中 使用所有這些倒排索引,并以驚人的速度返回結(jié)果
Elasticsearch 文檔
在 Elasticsearch 中,術(shù)語(yǔ) 文檔 有著特定的含義。它是指最頂層或者根對(duì)象, 這個(gè)根對(duì)象被序列化成 JSON 并存儲(chǔ)到 Elasticsearch 中,指定了唯一 ID
這個(gè)JSON中的字段的名字可以是任何合法的字符串,但 不可以 包含英文句號(hào)(.),ES中的每一個(gè)文檔就是一個(gè)JSON對(duì)象,其中包含了我們定義的數(shù)據(jù)字段,其中可以每個(gè)字段可以存儲(chǔ)任意類型的值。
元數(shù)據(jù)
一個(gè)文檔不光是包括文檔本身的數(shù)據(jù),同時(shí)也包含了元數(shù)據(jù)(元數(shù)據(jù)就是有關(guān)文檔本身的信息)
三個(gè)元數(shù)據(jù)必須的元素如下:
_index
//文檔在哪存放
_type
//文檔表示的對(duì)象類別
_id
//文檔唯一標(biāo)識(shí)
_index
這個(gè)表示文檔存儲(chǔ)對(duì)應(yīng)的索引,一個(gè)索引對(duì)應(yīng)多個(gè)分片,文檔則存儲(chǔ)在索引對(duì)應(yīng)的分片中,通過(guò)分片計(jì)算公式可以計(jì)算出文檔應(yīng)該存入那個(gè)分片中,公式為:
shard = hash(routing) % number_of_primary_shards
//就是索引的ID取hash然后對(duì)主分片數(shù)量取余
實(shí)際上,在 Elasticsearch 中,我們的數(shù)據(jù)是被存儲(chǔ)和索引在 分片 中,而一個(gè)索引僅僅是邏輯上的命名空間, 這個(gè)命名空間由一個(gè)或者多個(gè)分片組合在一起。 然而,這是一個(gè)內(nèi)部細(xì)節(jié),我們的應(yīng)用程序根本不應(yīng)該關(guān)心分片,對(duì)于應(yīng)用程序而言,只需知道文檔位于一個(gè) 索引 內(nèi)。 Elasticsearch 會(huì)處理所有的細(xì)節(jié)
_type
數(shù)據(jù)可能在索引中只是松散的組合在一起,但是通常明確定義一些數(shù)據(jù)中的子分區(qū)是很有用的。 例如,所有的產(chǎn)品都放在一個(gè)索引中,但是你有許多不同的產(chǎn)品類別,比如 "electronics" 、 "kitchen" 和 "lawn-care"。
這些文檔共享一種相同的(或非常相似)的模式:他們有一個(gè)標(biāo)題、描述、產(chǎn)品代碼和價(jià)格。他們只是正好屬于“產(chǎn)品”下的一些子類。
Elasticsearch 公開(kāi)了一個(gè)稱為 types (類型)的特性,它允許您在索引中對(duì)數(shù)據(jù)進(jìn)行邏輯分區(qū)。不同 types 的文檔可能有不同的字段,但最好能夠非常相似。 詳情參考 類型和映射 中更多的討論關(guān)于 types 的一些應(yīng)用和限制。
一個(gè) _type 命名可以是大寫或者小寫,但是不能以下劃線或者句號(hào)開(kāi)頭,不應(yīng)該包含逗號(hào), 并且長(zhǎng)度限制為256個(gè)字符.
_id
ID 是一個(gè)字符串,當(dāng)它和 _index 以及 _type 組合就可以唯一確定 Elasticsearch 中的一個(gè)文檔。 當(dāng)你創(chuàng)建一個(gè)新的文檔,要么提供自己的 _id ,要么讓 Elasticsearch 幫你生成,如果你是通過(guò)ELK將數(shù)據(jù)存入ES時(shí),在logstash中指定document_id字段為你文檔數(shù)據(jù)中的ID字段就可以自行指定ID了
文檔索引
通過(guò)使用 index API ,文檔可以被 索引 —— 存儲(chǔ)和使文檔可被搜索。 但是首先,我們要確定文檔的位置。正如我們剛剛討論的,一個(gè)文檔的 _index 、 _type 和 _id 唯一標(biāo)識(shí)一個(gè)文檔。 我們可以提供自定義的 _id 值,或者讓 index API 自動(dòng)生成
我們可以直接通過(guò)命令:
PUT /{INDEX}/{TYPE}/{ID}
{
//SOURCE
}
指定索引名、類型、id插入一個(gè)文檔
插入成功后的響應(yīng)是如下的格式
{
"_index": {INDEX}
"_type": {TYPE}
"_id": {ID}
"_version": 1
"created" : true
}
其中有一個(gè)_version,這也是元數(shù)據(jù)中的一個(gè)元素,標(biāo)識(shí)著這個(gè)文檔的版本,每次對(duì)這個(gè)文檔做修改時(shí),包括刪除,都會(huì)遞增,這樣可以標(biāo)識(shí)文檔的修改,保證文檔的修改部分不會(huì)影響未修改部分
如果你未指定ID,則ES會(huì)自行隨機(jī)生成一個(gè)ID,生成規(guī)則如下:
自動(dòng)生成的 ID 是 URL-safe、 基于 Base64 編碼且長(zhǎng)度為20個(gè)字符的 GUID 字符串。 這些 GUID 字符串由可修改的 FlakeID 模式生成,這種模式允許多個(gè)節(jié)點(diǎn)并行生成唯一 ID ,且互相之間的沖突概率幾乎為零。
ES文檔的查詢(數(shù)據(jù)的查詢)
我們可以通過(guò)一下語(yǔ)句進(jìn)行數(shù)據(jù)的查詢
GET /{INDEX}/{TYPE}/{ID}?pretty
的到的響應(yīng)如下:
{
"_index": {INDEX}
"_type": {TYPE}
"_id": {ID}
"_version": 1
"_source":{//元數(shù)據(jù)字段
//我們存入ES文檔時(shí)的原數(shù)據(jù)
}
}
在請(qǐng)求的查詢串參數(shù)中加上 pretty 參數(shù),正如前面的例子中看到的,這將會(huì)調(diào)用 Elasticsearch 的 pretty-print 功能,該功能 使得 JSON 響應(yīng)體更加可讀。但是, _source 字段不能被格式化打印出來(lái)。相反,我們得到的 _source 字段中的 JSON 串,剛好是和我們傳給它的一樣
GET 請(qǐng)求的響應(yīng)體包括 {"found": true} ,這證實(shí)了文檔已經(jīng)被找到。 如果我們請(qǐng)求一個(gè)不存在的文檔,我們?nèi)耘f會(huì)得到一個(gè) JSON 響應(yīng)體,但是 found 將會(huì)是 false 。 此外, HTTP 響應(yīng)碼將會(huì)是 404 Not Found ,而不是 200 OK
我們可以通過(guò)傳遞 -i 參數(shù)給 curl 命令,該參數(shù)能夠顯示響應(yīng)的頭部
curl -i XGET http://ip:port/{INDEX}/{TYPE}/{ID}?pretty
得到的響應(yīng)體如下:
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=UTF-8
Content-Length: 83
{
"_index" : "website",
"_type" : "blog",
"_id" : "124",
"found" : false
}
默認(rèn)情況下, GET 請(qǐng)求會(huì)返回整個(gè)文檔,這個(gè)文檔正如存儲(chǔ)在 _source 字段中的一樣。但是也許你只對(duì)其中的 title 字段感興趣。單個(gè)字段能用 _source 參數(shù)請(qǐng)求得到,多個(gè)字段也能使用逗號(hào)分隔的列表來(lái)指定
GET /{INDEX}/{TYPE}/{ID}?_source=title,text
這樣返回的文檔只會(huì)返回你指定的字段,如tittle和text
檢查文檔是否存在
如果只想檢查一個(gè)文檔是否存在--根本不想關(guān)心內(nèi)容—?那么用 HEAD 方法來(lái)代替 GET 方法。 HEAD 請(qǐng)求沒(méi)有返回體,只返回一個(gè) HTTP 請(qǐng)求報(bào)頭
curl -i -XHEAD http://localhost:9200/{INDEX}/{TYPE}/{ID}
如果ES中存在該文檔則返回
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
Content-Length: 0
如果ES中不存在該文檔則返回
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=UTF-8
Content-Length: 0
文檔的更新操作
ES的文檔一旦建立則不可更改,雖然ES有uopdate這種API,看似是對(duì)ES中的文檔進(jìn)行了更新操作,但是實(shí)際上ES中進(jìn)行了先對(duì)舊文檔的JSON數(shù)據(jù)提取出來(lái),然后進(jìn)行修改,然后將舊文檔刪除,在相同的索引相同的位置存入一個(gè)新文檔
唯一的區(qū)別在于, update API 僅僅通過(guò)一個(gè)客戶端請(qǐng)求來(lái)實(shí)現(xiàn)這些步驟,而不需要單獨(dú)的 get 和 POST 請(qǐng)求對(duì)index進(jìn)行操作
創(chuàng)建一個(gè)新文檔
ES中的文檔的唯一標(biāo)識(shí)是由幾個(gè)元數(shù)據(jù)的元素組成的,由_index,_type,_id等三個(gè)元素標(biāo)識(shí)了一個(gè)文檔的唯一性,如果我們需要指定id插入一個(gè)文檔的話,如果是通過(guò)API自動(dòng)插入的話,API會(huì)先檢索一下ES中是否存在這個(gè)id對(duì)應(yīng)的文檔,如果存在則進(jìn)行覆蓋,如果不存在則插入新的,但是如果通過(guò)命令進(jìn)行插入的話,我們可以通過(guò)以下命令進(jìn)行校驗(yàn)
PUT /{INDEX}/{TYPE}/{ID}?op_type=create
{ ... }
//或者
PUT /{INDEX}/{TYPE}/{ID}/_create
{ ... }
通過(guò)這樣的方式進(jìn)行插入會(huì)對(duì)ID進(jìn)行index,type和id組成的唯一標(biāo)識(shí)進(jìn)行校驗(yàn),如果存在的話,則會(huì)返回一個(gè)409的報(bào)錯(cuò)信息,如下所示:
{
"error": {
"root_cause": [
{
"type": "document_already_exists_exception",
"reason": "[{TYPE}][{ID}]: document already exists",
"shard": "0",
"index": "website"
}
],
"type": "document_already_exists_exception",
"reason": "[{TYPE}][{ID}]: document already exists",
"shard": "0",
"index": "website"
},
"status": 409
}
如果ES中不存在這個(gè)文檔,則會(huì)進(jìn)行向其中插入新文檔,返回201,也就是成功的狀態(tài)碼和http響應(yīng)
文檔刪除
刪除文檔的語(yǔ)法和我們所知道的規(guī)則相同,只是使用 DELETE 方法:
DELETE /{INDEX}/{TYPE}/{ID}
如果ES中存在改文檔,則會(huì)給該文檔打上一個(gè)刪除標(biāo)記,然后返回成功的狀態(tài)碼,如果不存在該文檔,則會(huì)返回一個(gè)404,結(jié)果如下:
{//200
"found" : true,
"_index" : "{INDEX}",
"_type" : "{TYPE}",
"_id" : "{ID}",
"_version" : 3
}
//404
{
"found" : false,
"_index" : "website",
"_type" : "blog",
"_id" : "123",
"_version" : 4
}
無(wú)論該文檔在ES中是否存在,都會(huì)對(duì)version進(jìn)行一個(gè)自增操作。
而已經(jīng)打上刪除標(biāo)記的刪除文檔不會(huì)立即將文檔從磁盤中刪除,只是將文檔標(biāo)記為已刪除狀態(tài)。隨著你不斷的索引更多的數(shù)據(jù),Elasticsearch 將會(huì)在后臺(tái)清理標(biāo)記為已刪除的文檔
ES處理沖突
當(dāng)我們對(duì)數(shù)據(jù)進(jìn)行操作時(shí),如果出現(xiàn)了同時(shí)兩個(gè)人對(duì)數(shù)據(jù)進(jìn)行操作的話,那么只有最近的一次操作會(huì)起作用,而對(duì)于其他的操作則會(huì)被覆蓋,這樣就出現(xiàn)了數(shù)據(jù)處理沖突,我們可以通過(guò)并發(fā)控制來(lái)控制這些操作,處理沖突
悲觀控制并發(fā)
這種方法廣泛運(yùn)用于關(guān)系型數(shù)據(jù)庫(kù)中,這種方法控制的前提是假定所有資源都有可能被并發(fā)訪問(wèn),都有可能會(huì)發(fā)生沖突,所以在,訪問(wèn)之前就給他鎖住,阻塞其余訪問(wèn)這個(gè)資源的線程以防止沖突,確保只有加鎖的對(duì)象能夠?qū)Y源進(jìn)行訪問(wèn)(synchronized就是一個(gè)典型的悲觀鎖)
樂(lè)觀控制并發(fā)
ES中假定沖突是不會(huì)發(fā)生的并不會(huì)阻塞正在嘗試的操作,但是如果源數(shù)據(jù)在讀寫中被修改,則會(huì)更新失敗。之后會(huì)嘗試去解決這個(gè)操作失敗的問(wèn)題,也就是解決沖突。 例如,可以重試更新、使用新的數(shù)據(jù)、或者將相關(guān)情況報(bào)告給用戶。
Elasticsearch 是分布式的。當(dāng)文檔創(chuàng)建、更新或刪除時(shí), 新版本的文檔必須復(fù)制到集群中的其他節(jié)點(diǎn)。Elasticsearch 也是異步和并發(fā)的,這意味著這些復(fù)制請(qǐng)求被并行發(fā)送,并且到達(dá)目的地時(shí)也許 順序是亂的 。 Elasticsearch 需要一種方法確保文檔的舊版本不會(huì)覆蓋新的版本。
當(dāng)我們之前討論 index , GET 和 delete 請(qǐng)求時(shí),我們指出每個(gè)文檔都有一個(gè) _version (版本)號(hào),當(dāng)文檔被修改時(shí)版本號(hào)遞增。 Elasticsearch 使用這個(gè) _version 號(hào)來(lái)確保變更以正確順序得到執(zhí)行。如果舊版本的文檔在新版本之后到達(dá),它可以被簡(jiǎn)單的忽略。
我們可以利用 _version 號(hào)來(lái)確保 應(yīng)用中相互沖突的變更不會(huì)導(dǎo)致數(shù)據(jù)丟失。我們通過(guò)指定想要修改文檔的 version 號(hào)來(lái)達(dá)到這個(gè)目的。 如果該版本不是當(dāng)前版本號(hào),我們的請(qǐng)求將會(huì)失敗。
更新和沖突
在本節(jié)的介紹中,我們說(shuō)明 檢索 和 重建索引 步驟的間隔越小,變更沖突的機(jī)會(huì)越小。 但是它并不能完全消除沖突的可能性。 還是有可能在 update 設(shè)法重新索引之前,來(lái)自另一進(jìn)程的請(qǐng)求修改了文檔。
為了避免數(shù)據(jù)丟失, update API 在 檢索 步驟時(shí)檢索得到文檔當(dāng)前的 _version 號(hào),并傳遞版本號(hào)到 重建索引 步驟的 index 請(qǐng)求。 如果另一個(gè)進(jìn)程修改了處于檢索和重新索引步驟之間的文檔,那么 _version 號(hào)將不匹配,更新請(qǐng)求將會(huì)失敗。
對(duì)于部分更新的很多使用場(chǎng)景,文檔已經(jīng)被改變也沒(méi)有關(guān)系。 例如,如果兩個(gè)進(jìn)程都對(duì)頁(yè)面訪問(wèn)量計(jì)數(shù)器進(jìn)行遞增操作,它們發(fā)生的先后順序其實(shí)不太重要; 如果沖突發(fā)生了,我們唯一需要做的就是嘗試再次更新。
這可以通過(guò)設(shè)置參數(shù) retry_on_conflict 來(lái)自動(dòng)完成, 這個(gè)參數(shù)規(guī)定了失敗之前 update 應(yīng)該重試的次數(shù),它的默認(rèn)值為 0 。
在增量操作無(wú)關(guān)順序的場(chǎng)景,例如遞增計(jì)數(shù)器等這個(gè)方法十分有效,但是在其他情況下變更的順序 是 非常重要的。 類似 index API , update API 默認(rèn)采用 最終寫入生效 的方案,但它也接受一個(gè) version 參數(shù)來(lái)允許你使用 optimistic concurrency control 指定想要更新文檔的版本。
取回多個(gè)文檔
Elasticsearch 的速度已經(jīng)很快了,但甚至能更快。 將多個(gè)請(qǐng)求合并成一個(gè),避免單獨(dú)處理每個(gè)請(qǐng)求花費(fèi)的網(wǎng)絡(luò)延時(shí)和開(kāi)銷。 如果你需要從 Elasticsearch 檢索很多文檔,那么使用 multi-get 或者 mget API 來(lái)將這些檢索請(qǐng)求放在一個(gè)請(qǐng)求中,將比逐個(gè)文檔請(qǐng)求更快地檢索到全部文檔。
例如:
GET /_mget
{
"docs" : [
{
"_index" : "index",
"_type" : "type",
"_id" : id
},
{
"_index" : "index",
"_type" : "type",
"_id" : id
"_source": "字段名"
}
]
}
可以指定字段名進(jìn)行查詢
代價(jià)比較小的批量操作
和mget一樣,我們同樣有一次性提交大量操作的API(bulk),bulk API 允許在單個(gè)步驟中進(jìn)行多次 create 、 index 、 update 或 delete 請(qǐng)求。 如果你需要索引一個(gè)數(shù)據(jù)流比如日志事件,它可以排隊(duì)和索引數(shù)百或數(shù)千批次。
bulk 與其他的請(qǐng)求體格式稍有不同,如下所示
{ action: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n
這種格式類似一個(gè)有效的單行 JSON 文檔 流 ,它通過(guò)換行符(\n)連接到一起。注意兩個(gè)要點(diǎn):
每行一定要以換行符(\n)結(jié)尾, 包括最后一行 。這些換行符被用作一個(gè)標(biāo)記,可以有效分隔行。
這些行不能包含未轉(zhuǎn)義的換行符,因?yàn)樗麄儗?huì)對(duì)解析造成干擾。這意味著這個(gè) JSON 不 能使用 pretty 參數(shù)打印。
ction/metadata 行指定 哪一個(gè)文檔 做 什么操作 。
action 必須是以下選項(xiàng)之一:
create: 如果文檔不存在,那么就創(chuàng)建它。詳情請(qǐng)見(jiàn) 創(chuàng)建新文檔。
index: 創(chuàng)建一個(gè)新文檔或者替換一個(gè)現(xiàn)有的文檔。詳情請(qǐng)見(jiàn) 索引文檔 和 更新整個(gè)文檔。
update: 部分更新一個(gè)文檔。詳情請(qǐng)見(jiàn) 文檔的部分更新。
delete: 刪除一個(gè)文檔。詳情請(qǐng)見(jiàn) 刪除文檔
metadata 應(yīng)該指定被索引、創(chuàng)建、更新或者刪除的文檔的 _index 、 _type 和 _id 。
request body 行由文檔的 _source 本身組成—?文檔包含的字段和值。它是 index 和 create 操作所必需的,這是有道理的:你必須提供文檔以索引。
它也是 update 操作所必需的,并且應(yīng)該包含你傳遞給 update API 的相同請(qǐng)求體: doc 、 upsert 、 script 等等。 刪除操作不需要 request body 行。
如下列操作:
POST /_bulk
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }}//1
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}//2
{ "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }}//3
{ "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} } //4
{ "doc" : {"title" : "My updated blog post"} }
上述操作,1號(hào)操作,delete操作后面不需要跟詳細(xì)的操作語(yǔ)句,因?yàn)閐elete本身就是直接進(jìn)行文檔刪除的。
2號(hào)操作執(zhí)行的是create操作,后續(xù)接的title語(yǔ)句就是操作建立的詳細(xì)字段,如果這個(gè)這個(gè)指定條件的文檔已經(jīng)存在了,則會(huì)返回 一個(gè)文檔已經(jīng)存在的錯(cuò)誤信息
3號(hào)操作是建立或者覆蓋一個(gè)文檔,如果沒(méi)有直接指定ID的話,那么就是新建一個(gè)文檔,會(huì)自動(dòng)隨機(jī)生成一個(gè)新的ID
4號(hào)操作執(zhí)行的是更新操作,后續(xù)接的doc就是操作的字段。
bulk操作d的每個(gè)子請(qǐng)求都是獨(dú)立執(zhí)行,因此某個(gè)子請(qǐng)求的失敗不會(huì)對(duì)其他子請(qǐng)求的成功與否造成影響。 如果其中任何子請(qǐng)求失敗,最頂層的 error 標(biāo)志被設(shè)置為 true ,并且在相應(yīng)的請(qǐng)求報(bào)告出錯(cuò)誤明細(xì)
這也意味著 bulk 請(qǐng)求不是原子的: 不能用它來(lái)實(shí)現(xiàn)事務(wù)控制。每個(gè)請(qǐng)求是單獨(dú)處理的,因此一個(gè)請(qǐng)求的成功或失敗不會(huì)影響其他的請(qǐng)求
如果你是使用bulk對(duì)相同的索引(_index)和類型(_type)下的文檔進(jìn)行操作的話,則不需要重復(fù)指定這兩個(gè)元數(shù)據(jù),只需要指定ID就行了。
批量請(qǐng)求的大小
整個(gè)批量請(qǐng)求都需要由接收到請(qǐng)求的節(jié)點(diǎn)加載到內(nèi)存中,因此該請(qǐng)求越大,其他請(qǐng)求所能獲得的內(nèi)存就越少。 批量請(qǐng)求的大小有一個(gè)最佳值,大于這個(gè)值,性能將不再提升,甚至?xí)陆怠?但是最佳值不是一個(gè)固定的值。它完全取決于硬件、文檔的大小和復(fù)雜度、索引和搜索的負(fù)載的整體情況。
幸運(yùn)的是,很容易找到這個(gè) 最佳點(diǎn) :通過(guò)批量索引典型文檔,并不斷增加批量大小進(jìn)行嘗試。 當(dāng)性能開(kāi)始下降,那么你的批量大小就太大了。一個(gè)好的辦法是開(kāi)始時(shí)將 1,000 到 5,000 個(gè)文檔作為一個(gè)批次, 如果你的文檔非常大,那么就減少批量的文檔個(gè)數(shù)。
密切關(guān)注你的批量請(qǐng)求的物理大小往往非常有用,一千個(gè) 1KB 的文檔是完全不同于一千個(gè) 1MB 文檔所占的物理大小。 一個(gè)好的批量大小在開(kāi)始處理后所占用的物理大小約為 5-15 MB。