觸類旁通Elasticsearch之吊打同行系列:原理篇
點(diǎn)擊上方藍(lán)色字體,選擇“設(shè)為星標(biāo)”
回復(fù)”資源“獲取更多資源

目錄
一、邏輯設(shè)計(jì)
文檔
類型
索引
二、物理設(shè)計(jì)
節(jié)點(diǎn)
主分片與副本分片
分布式索引和搜索
三、索引數(shù)據(jù)
四、搜索數(shù)據(jù)
在哪里搜索
回復(fù)的內(nèi)容
如何搜索
通過ID獲取文檔
ES被設(shè)計(jì)為處理海量數(shù)據(jù)的高性能搜索場景。海量數(shù)據(jù)具體說至少應(yīng)該是數(shù)億文檔,而高性能具體說就是從數(shù)億文檔中任意搜索需要的信息,應(yīng)該在秒級(jí)返回結(jié)果。既然ES的一切都是為了性能而設(shè)計(jì),從邏輯設(shè)計(jì)和物理設(shè)計(jì)兩個(gè)角度考察ES的數(shù)據(jù)組織,對(duì)于理解ES的工作原理會(huì)有幫助。
邏輯設(shè)計(jì):用于索引和搜索的基本單位是文檔,可以將其認(rèn)為是關(guān)系數(shù)據(jù)庫里的一行記錄。文檔以類型分組,類型包含若干文檔,類似表中包含若干行。最終,一個(gè)或多個(gè)類型存儲(chǔ)于同一索引中,索引是更大的容器,類似于SQL世界中的數(shù)據(jù)庫。ES6中類型的概念已經(jīng)過時(shí),并且將在7中徹底棄用。因此在我的環(huán)境中,ES索引和文檔就對(duì)應(yīng)數(shù)據(jù)庫的表和記錄。
物理設(shè)計(jì):物理設(shè)計(jì)的配置方式?jīng)Q定了集群的性能、可擴(kuò)展性和可用性。ES將每個(gè)索引劃分為片,缺省為5片,每份分片可以在集群中不同的服務(wù)器間遷移。通常,應(yīng)用程序無須關(guān)心這些,因?yàn)闊o論ES是單臺(tái)還是多臺(tái)服務(wù)器,應(yīng)用和ES的交互基本保持不變。
圖1展示了這兩個(gè)方面:
圖1 ES邏輯設(shè)計(jì)與物理設(shè)計(jì)
一、邏輯設(shè)計(jì)
圖2所示為一個(gè)ES索引的邏輯結(jié)構(gòu):
圖2 ES數(shù)據(jù)的邏輯設(shè)計(jì)
在ES6中,一個(gè)索引中只能有一個(gè)類型,缺省名為_doc。索引-類型-文檔ID 的組合唯一確定了一篇文檔,文檔ID可以是任意字符串。
當(dāng)進(jìn)行搜索的時(shí)候,可以查找特定的索引中的文檔,也可以跨多個(gè)索引進(jìn)行搜索,類似于單表或多表查詢。但和關(guān)系數(shù)據(jù)庫不同的是,ES并不支持關(guān)系數(shù)據(jù)庫中表之間的join,或者嵌套子查詢。
1. 文檔
ES是面向文檔的,索引和搜索的最小單位是文檔。ES中文檔有幾個(gè)重要的屬性。
它是自包含的。一篇文檔中同時(shí)包含字段和字段的取值。關(guān)系庫的表結(jié)構(gòu)是元數(shù)據(jù),與真正數(shù)據(jù)的存儲(chǔ)和管理方式是不同的。但ES中文檔數(shù)據(jù)本身就包含了字段名和字段值。
它可以是層次的。文檔中可以包含其它文檔。一個(gè)字段可以是簡單的,如一個(gè)字符串,也可以包含其它字段和取值。
它是無模式的。文檔不依賴于預(yù)先定義的模式,不同文檔的字段可以不同。
一篇文檔通常是數(shù)據(jù)的JSON表示。和ES溝通最為廣泛的方式是HTTP協(xié)議上的JSON。下面是個(gè)簡單文檔的例子:
{
"name": "Elasticsearch Denver",
"organizer": "Lee",
"location": "Denver, Colorado, USA"
}
下面是一個(gè)層次型文檔的例子:
{
"name": "Elasticsearch Denver",
"organizer": "Lee",
"location": {
"name": "Denver, Colorado, USA",
"geolocation": "39.7392, -104.9847"
}
}
一個(gè)簡單的文檔也可以包含一組數(shù)值,例如:
{
"name": "Elasticsearch Denver",
"organizer": "Lee",
"members": ["Lee", "Mike"]
}
ES中的文檔是無模式的,也就是說并非所有的文檔都需要擁有相同的字段,它們不是受限于同一個(gè)模式。盡管可以隨意添加和忽略字段,但是每個(gè)字段的類型很重要。ES保存字段和類型之間的映射以及其它設(shè)置,類似于表結(jié)構(gòu)。
2. 類型
ES6中類型的概念已經(jīng)過時(shí)。在ES6之前的版本中,類型是文檔的容器,類似于表格是行的容器。每個(gè)類型中字段的定義稱為映射(mapping),每種字段通過不同的方式進(jìn)行處理。
既然ES是無模式的,為什么每個(gè)文檔屬于一種類型,而且每個(gè)類型包含一個(gè)看上去很像模式的映射呢?我們說“無模式”是因?yàn)槲臋n不受模式的限制。它們并不需要擁有映射中所定義的所有字段,也能提出新的字段。這是如何運(yùn)作的?首先,映射包含某個(gè)類型中當(dāng)前索引的所有文檔的所有字段。但不是所有的文檔必須要有所有的字段。同樣,如果一篇新索引的文檔擁有一個(gè)映射中尚不存在的字段,ES會(huì)自動(dòng)地將新字段加入映射。為了添加這個(gè)字段,ES需要確定它是什么類型,于是ES會(huì)根據(jù)字段值進(jìn)行猜測。例如,如果值是7,ES會(huì)假設(shè)字段是長整型。
這種對(duì)新字段的自動(dòng)檢測也有缺點(diǎn),因?yàn)镋S可能猜得不對(duì)。例如,在索引了值7之后,可能想再索引hello world,這時(shí)由于它是text而不是long,索引就會(huì)失敗,對(duì)于線上環(huán)境,最安全的方式是在索引數(shù)據(jù)之前,就定義好所需的映射。從這個(gè)角度看很像數(shù)據(jù)庫,在加入數(shù)據(jù)前先建表。所以在實(shí)際應(yīng)用中,常見的使用方式還是先仔細(xì)定義好映射,再裝載數(shù)據(jù)。
映射只是將文檔進(jìn)行邏輯劃分。從物理角度看,文檔寫入磁盤時(shí)不考慮它們所屬的類型。
3. 索引
索引是文檔的容器,一個(gè)ES索引非常像關(guān)系數(shù)據(jù)庫中的表,是獨(dú)立的大量文檔的集合。每個(gè)索引存儲(chǔ)在磁盤上的同組文件中;索引存儲(chǔ)了所有字段的映射和數(shù)據(jù),還有一些設(shè)置。例如,每個(gè)索引有一個(gè)稱為refresh_interval的設(shè)置,定義了新文檔對(duì)于搜索可見的時(shí)間間隔。從性能的角度看,刷新操作的代價(jià)是非常昂貴的。ES的索引數(shù)據(jù)是寫入到磁盤上的。但這個(gè)過程是分階段實(shí)現(xiàn)的,因?yàn)镮O的操作比較費(fèi)時(shí)。
先寫到內(nèi)存中,此時(shí)不可搜索。
默認(rèn)經(jīng)過 1s 之后會(huì)被寫入 lucene 的底層文件 segment 中 ,此時(shí)可以搜索到。
refresh 之后才會(huì)寫入磁盤。
ES被稱為準(zhǔn)實(shí)時(shí)的,指的就是這種刷新過程。
二、物理設(shè)計(jì)
默認(rèn)情況下,每個(gè)索引由5個(gè)分片組成,而每個(gè)分片又有一個(gè)副本,一共10個(gè)分片。如圖3所示。
圖3 一個(gè)有3個(gè)節(jié)點(diǎn)的集群,索引被劃分為5個(gè)主分片,每個(gè)主分片有一個(gè)副本分片
技術(shù)上而言,一個(gè)分片是一個(gè)的文件,Lucene用這些文件存儲(chǔ)索引數(shù)據(jù)。分片也是ES將數(shù)據(jù)從一個(gè)節(jié)點(diǎn)遷移到另一個(gè)節(jié)點(diǎn)的最小單位。
1. 節(jié)點(diǎn)
一個(gè)節(jié)點(diǎn)是一個(gè)ES實(shí)例,多個(gè)節(jié)點(diǎn)可以加入同一集群。在多節(jié)點(diǎn)的集群上,同樣的數(shù)據(jù)可以在多臺(tái)服務(wù)器上傳播。如果每分片至少有一個(gè)副本,那么任何一個(gè)節(jié)點(diǎn)都可以宕機(jī),而ES依然可以進(jìn)行服務(wù),返回所有數(shù)據(jù)。對(duì)于應(yīng)用程序,集群中有1個(gè)還是多個(gè)節(jié)點(diǎn)是透明的。默認(rèn)情況下,可以連接集群中的任一節(jié)點(diǎn)并訪問完整的數(shù)據(jù)集。
默認(rèn)情況下,當(dāng)索引一篇文檔時(shí),系統(tǒng)首先根據(jù)文檔ID的散列值選擇一個(gè)主分片,并將文檔發(fā)送到該主分片。這個(gè)主分片可能位于另一個(gè)節(jié)點(diǎn),如圖4中節(jié)點(diǎn)2上的主分片,不過對(duì)于應(yīng)用程序這一點(diǎn)是透明的。
圖4 文檔被索引到隨機(jī)的主分片和它們的副本分片。
搜索在完整的分片集合上運(yùn)行,無論它們的狀態(tài)是主分片還是副本分片。
然后文檔被發(fā)送到該主分片的所有副本分片進(jìn)行索引(如圖4的左邊)。這使得副本分片和主分片之間保持?jǐn)?shù)據(jù)的同步。數(shù)據(jù)同步使得副本分片可以服務(wù)于搜索請求,并在原主分片無法訪問時(shí)自動(dòng)升級(jí)為主分片。
當(dāng)搜索一個(gè)索引時(shí),ES需要在該索引的完整集合中進(jìn)行查找(見圖4的右邊)。這些分片可以是分片,也可以是副本分片。ES在索引的主分片和副本分片中進(jìn)行搜索請求的負(fù)載均衡,使得副本分片對(duì)于搜索性能和容錯(cuò)都有所幫助。
2. 主分片與副本分片
分片是ES最小的處理單元,一個(gè)分片是一個(gè)Lucene的索引:一個(gè)包含倒排索引的文件目錄。如圖5所示,get-together索引的首個(gè)主分片可能包含何種信息。該分片稱為get-together0,它是一個(gè)Lucene索引、一個(gè)倒排索引。它默認(rèn)存儲(chǔ)原始文檔的內(nèi)容,再加上一些額外的信息,如詞條字典和詞頻。
圖5 Lucene索引中的詞條字典和詞頻
詞條字典將每個(gè)詞條和包含該詞條的文檔映射起來。搜索的時(shí)候,ES沒必要為了某個(gè)詞條掃描所有文檔,而是根據(jù)這個(gè)字典快速識(shí)別匹配的文檔。
詞頻使得ES可以快速地獲取謀篇文檔中某個(gè)詞條出現(xiàn)的次數(shù)。這對(duì)于計(jì)算結(jié)果的相關(guān)性得分非常重要。更高得分的文檔出現(xiàn)在結(jié)果列表的更前面。默認(rèn)的排序算法是TF-IDF。
可以在任何時(shí)候改變每個(gè)分片的副本分片的數(shù)量,因?yàn)楦北痉制偸强梢员粍?chuàng)建和移除。但主分片的數(shù)量必須在創(chuàng)建索引之前確定,索引創(chuàng)建后主分片的數(shù)量不能修改。過少的分片將限制可擴(kuò)展性,但過多的分片影響性能。默認(rèn)設(shè)置的5份是個(gè)不錯(cuò)的開始。
3. 分布式索引和搜索
索引的過程如圖6所示。接受索引請求的ES節(jié)點(diǎn)首先選擇文檔索引到哪個(gè)分片。默認(rèn)的,文檔在分片中均勻分布:對(duì)于每篇文檔,分片是通過其ID字符串的散列決定的。每個(gè)分片擁有相同的散列范圍,接收新文檔的機(jī)會(huì)均等。一旦目標(biāo)分片確定,接受請求的節(jié)點(diǎn)將文檔轉(zhuǎn)發(fā)到該分片所在節(jié)點(diǎn)。隨后,索引操作在所有目標(biāo)分片的副本分片中進(jìn)行。在所有可用副本分片完成文檔的索引后,索引命令就會(huì)返回成功。
圖6 索引操作被轉(zhuǎn)發(fā)到相應(yīng)的分片,然后轉(zhuǎn)發(fā)到它的副本分片
在搜索的時(shí)候,接受請求的節(jié)點(diǎn)將請求轉(zhuǎn)發(fā)到一組包含所有數(shù)據(jù)的分片。ES使用round-robin的輪詢機(jī)制選擇可用的分片(主分片或副本分片),并將搜索請求轉(zhuǎn)發(fā)過去,其假設(shè)集群中的所有節(jié)點(diǎn)是同樣快的。如圖7所示,ES然后從這些分片收集結(jié)果,將其聚集到單一的結(jié)果返回給應(yīng)用程序。
圖7 轉(zhuǎn)發(fā)搜索請求到包含完整數(shù)據(jù)集合的主分片/副本分片,然后聚集結(jié)果并將其發(fā)送回客戶端
三、索引數(shù)據(jù)
可以使用curl的PUT方法索引一個(gè)文檔,如:
curl -XPUT '172.16.1.127:9200/get-together/_doc/1?pretty' -H 'Content-Type: application/json' -d '
{
"name": "Elasticsearch Denver",
"organizer": "Lee"
}'
這句DSL語句的功能是向索引get-together中新增一個(gè)ID為1的文檔,類似于SQL的insert命令:
insert into get-together (id, name, organizer) values (1, 'Elasticsearch Denver', 'Lee');
輸出如下:
{
"_index" : "get-together",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
回復(fù)中包含索引、類型和文檔ID。如果索引和類型不存在,則會(huì)自動(dòng)創(chuàng)建。文檔ID也可以由ES自動(dòng)生成。這里還獲得了文檔的版本,它從1開始并隨著每次的更新而增加。ES使用這個(gè)版本號(hào)實(shí)現(xiàn)并發(fā)更新時(shí)的樂觀鎖功能,防止類似關(guān)系數(shù)據(jù)庫中的第二類更新丟失問題。
這個(gè)curl命令之所以可以奏效,是因?yàn)镋S自動(dòng)創(chuàng)建了get-together和_doc類型,并為_doc類型創(chuàng)建了一個(gè)新的映射。映射包含字符串字段的定義。默認(rèn)情況下ES處理所有這些,無需任何事先配置,就可以開始索引。
使用下面的命令查詢索引的映射:
curl '172.16.1.127:9200/get-together/_mapping?pretty'
返回如下??梢钥吹剑珽S自動(dòng)將name和organizer字段識(shí)別為text類型:
{
"get-together" : {
"mappings" : {
"_doc" : {
"properties" : {
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"organizer" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}
映射類型包含與文檔相關(guān)的字段或?qū)傩粤斜?。ES缺省將字符串?dāng)?shù)據(jù)映射為text和keyword。因此,可以對(duì)name和organizer字段執(zhí)行全文搜索,同時(shí)使用name.keyword或organizer.keyword執(zhí)行原文匹配和聚合。
可以手動(dòng)創(chuàng)建索引:
curl -XPUT '172.16.1.127:9200/new-index?pretty'
類似于創(chuàng)建一個(gè)名為new-index的表,但這里只指定了索引名稱,返回如下:
如果只想建立字符串類型的倒排索引搜索字段,而不映射keyword字段,只需要在創(chuàng)建索引時(shí)顯式指定mapping:
curl -XPUT '172.16.1.127:9200/myindex?pretty' -H 'Content-Type: application/json' -d '
{
"mappings": {
"_doc": {
"properties": {
"name": {
"type": "text"
},
"organizer": {
"type": "text"
}
}
}
}
}'
curl '172.16.1.127:9200/myindex/_mapping?pretty'
{
"myindex" : {
"mappings" : {
"_doc" : {
"properties" : {
"name" : {
"type" : "text"
},
"organizer" : {
"type" : "text"
}
}
}
}
}
}
四、搜索數(shù)據(jù)
下面的命令搜索get-together索引中包含“elasticsearch”關(guān)鍵詞的文檔,但只獲取最相關(guān)文檔的name和location_event.name字段。功能類似于SQL語句:
select name, location_event from get-together
where column1 like '%elasticsearch%'
or column2 like '%elasticsearch%'
...
or columnn like '%elasticsearch%'
order by _score
limit 1;
下面兩種寫法是等價(jià)的,但后者的可讀性更好。這個(gè)例子中的搜索條件沒有指定任何字段,意為在所有字段中搜索。
curl "172.16.1.127:9200/get-together/_search?\
q=elasticsearch\
&_source=name,location_event.name\
&size=1\
&pretty"
curl "172.16.1.127:9200/get-together/_search?pretty" -H 'Content-Type: application/json' -d'
{
"_source": [
"name",
"location_event.name"
],
"query": {
"query_string": {
"query": "elasticsearch"
}
},
"size": 1
}'
結(jié)果返回:
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 10,
"max_score" : 1.4880564,
"hits" : [
{
"_index" : "get-together",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.4880564,
"_source" : {
"name" : "Elasticsearch Denver"
}
}
]
}
}
1. 在哪里搜索
可以指定ES在特定索引中進(jìn)行查詢,但也可以在同一個(gè)索引的多個(gè)字段中搜索、在多個(gè)索引或在所有索引中搜索。
# 在多個(gè)索引中搜索
curl "172.16.1.127:9200/get-together,myindex/_search?q=elasticsearch&pretty"
# ignore_unavailable=true會(huì)忽略不存在的索引,而不是返回錯(cuò)誤
curl "172.16.1.127:9200/get-together,myindex/_search?q=name:elasticsearch&pretty&ignore_unavailable=true"
# 為了在所有索引中搜索,省略索引名稱
curl "172.16.1.127:9200/_search?q=name:elasticsearch&pretty"
這種關(guān)于“在哪里搜索”的靈活性,允許在多個(gè)索引中組織數(shù)據(jù)。例如,日志事件經(jīng)常以基于時(shí)間的索引組織,如“l(fā)ogs-20190101”、“l(fā)ogs-20190102”等??梢灾凰阉髯钚碌乃饕部梢栽诙鄠€(gè)索引或全量數(shù)據(jù)里搜索。
2. 回復(fù)的內(nèi)容
(1)時(shí)間
除了和搜索條件匹配的文檔,搜索回復(fù)還包含其它有價(jià)值的信息,用于檢驗(yàn)搜索的性能或結(jié)果的相關(guān)性。ES的JSON應(yīng)答包含了時(shí)間、分片、命中統(tǒng)計(jì)數(shù)據(jù)、文檔等。
took字段表示ES處理請求所花的時(shí)間,單位是毫秒。timed_out字段表示搜索請求是否超時(shí),默認(rèn)情況下,搜索永遠(yuǎn)不會(huì)超時(shí),但是可以通過timeout參數(shù)設(shè)定超時(shí)時(shí)間。例如下面的搜索在3秒后超時(shí):
curl "172.16.1.127:9200/get-together/_search?q=elasticsearch&pretty&timeout=3s"
如果搜索超時(shí),timed_out的值就是true,而且只能獲得超時(shí)前所獲得的結(jié)果。
(2)分片
回復(fù)的下一部分是搜索相關(guān)的分片信息:
"_shards" : {
"total" : 2,
"successful" : 2,
"skipped" : 0,
"failed" : 0
}
get-together索引有兩個(gè)分片,所有分片都有返回,所以成功(successful)的值是2,而失?。╢ailed)的值是0。圖8展示了一個(gè)擁有3個(gè)節(jié)點(diǎn)的集群,每個(gè)節(jié)點(diǎn)只有一份分片且沒有副本分片。如果某個(gè)節(jié)點(diǎn)宕機(jī)了,就會(huì)丟失某些數(shù)據(jù)。在這種情況下,ES提供正常分片中的結(jié)果,并在failed字段中報(bào)告不可搜索的分片數(shù)量。
圖8 仍然可用的分片將返回部分結(jié)果
(3)命中統(tǒng)計(jì)數(shù)據(jù)
回復(fù)的最后一項(xiàng)組成元素是hits,這項(xiàng)相當(dāng)長因?yàn)樗似ヅ湮臋n的數(shù)組。數(shù)組之前包含了幾項(xiàng)統(tǒng)計(jì)數(shù)據(jù):
"total" : 10,
"max_score" : 1.4880564
total表示匹配文檔的總數(shù),max_score是這些匹配文檔的最高得分。文檔得分,是該文檔和給定搜索條件的相關(guān)性衡量,得分默認(rèn)是通過TF-IDF算法進(jìn)行計(jì)算的。
匹配文檔的總數(shù)和回復(fù)中的文檔數(shù)量可能并不相同。ES默認(rèn)限制結(jié)果數(shù)為10,可使用size參數(shù)修改返回的結(jié)果數(shù)量。查看total字段的值,可以獲取匹配搜索條件的精確文檔數(shù)量。
(4)結(jié)果文檔
"hits" : [
{
"_index" : "get-together",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.4880564,
"_source" : {
"name" : "Elasticsearch Denver"
}
}
]
結(jié)果中包括每個(gè)匹配文檔所屬的索引、類型、它的ID、得分,以及搜索查詢中所指定的字段的值。查詢中使用了_source=name,location_event.name。如果結(jié)果中某個(gè)指定字段的值為空,缺省沒有該字段的定義,就像結(jié)果中沒有l(wèi)ocation_event.name字段。這點(diǎn)和數(shù)據(jù)庫不同,數(shù)據(jù)庫是有schema的,字段值和表定義分開處理,即使某字段沒有值,結(jié)果中該字段也會(huì)有個(gè)NULL值。如果不指定需要哪些字段,會(huì)返回“_source”中的所有字段。_source是一個(gè)特殊的字段,ES默認(rèn)在其中存儲(chǔ)原始的JSON文檔。
3. 如何搜索
(1)設(shè)置查詢的字符串選項(xiàng)
query_string提供了除字符串之外的更多選項(xiàng)。例如,如果搜索“Elasticsearch san Francisco”,ES默認(rèn)查詢所有字段。如果想在名稱和標(biāo)題中查詢,需要指定:
"fields": ["name", "title"]
ES默認(rèn)返回匹配了任一指定關(guān)鍵詞的文檔(默認(rèn)的操作符是OR)。如果希望匹配所有的關(guān)鍵詞,需要指定:
"default_operator": "AND"
修改后的查詢?nèi)缦拢?/p>
curl "172.16.1.127:9200/get-together/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"query_string": {
"query": "elasticsearch",
"fields": ["name", "title"],
"default_operator": "AND"
}
}
}'
查詢字符串是指定搜索條件的強(qiáng)大工具。ES分析字符串并理解所查找的詞條和其它選項(xiàng),如字段和操作符,然后執(zhí)行查詢。這項(xiàng)功能是從Lucene繼承而來。
(2)選擇合適的查詢類型
除query_string外,ES還有很多其它類型的查詢。例如,如果在name字段中只查找“elasticsearch”一個(gè)詞,term查詢更直接:
curl "172.16.1.127:9200/get-together/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"term": {
"name": "elasticsearch"
}
}
}'
(3)使用過濾器
如果不需要通過結(jié)果得分返回結(jié)果,可以使用過濾器查詢替代。過濾器查詢只關(guān)心一條結(jié)果是否匹配搜索條件,因此過濾器查詢更快,而且更容易緩存。例如:
curl "172.16.1.127:9200/get-together/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": {
"term": {
"name": "elasticsearch"
}
}
}
}
}'
返回的結(jié)果和同樣詞條的查詢相同,但結(jié)果沒有根據(jù)得分排序,因?yàn)樗械慕Y(jié)果得分都是0。
(4)應(yīng)用聚合
除了查詢和過濾,還可以通過聚合進(jìn)行各種統(tǒng)計(jì)。例如實(shí)現(xiàn)SQL的簡單聚合:
select count(*), organizer from get-together group by organizer;
ES查詢命令如下:
curl 172.16.1.127:9200/get-together/_doc/_search?pretty -H 'Content-Type: application/json' -d '
{
"aggregations": {
"organizers": {
"terms": {
"field": "organizer"
}
}
}
}'
當(dāng)在get-together索引上執(zhí)行此聚合查詢時(shí),報(bào)以下錯(cuò)誤:
"Fielddata is disabled on text fields by default. Set fielddata=true on [organizer] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead."
ES聚合使用一個(gè)叫Doc Values的數(shù)據(jù)結(jié)構(gòu)。Doc Values使聚合更快、更高效且內(nèi)存友好。Doc Values的存在是因?yàn)榈古潘饕粚?duì)某些操作是高效的。倒排索引的優(yōu)勢在于查找包含某個(gè)條目的文檔,而反過來確定哪些條目在單個(gè)文檔里并不高效。
Doc Values結(jié)構(gòu)類似如下:
Doc Terms
Doc_1 | brown, dog, fox, jumped, lazy, over, quick, the Doc_2 | brown, dogs, foxes, in, lazy, leap, over, quick, summer Doc_3 | dog, dogs, fox, jumped, over, quick, the
Doc values在索引時(shí)生成,伴隨倒排索引創(chuàng)建。像倒排索引一樣基于per-segment,且是不可變,被序列化存儲(chǔ)到磁盤。通過序列化持久化數(shù)據(jù)結(jié)構(gòu)到磁盤,可以用操作系統(tǒng)的文件緩存來代替JVM heap。但是當(dāng)工作空間需要的內(nèi)存很大時(shí),Doc Values會(huì)被置換出內(nèi)存,這樣會(huì)導(dǎo)致訪問速度降低,但是如果放在JVM heap,將直接導(dǎo)致內(nèi)存溢出錯(cuò)誤。
Doc Values默認(rèn)對(duì)除了分詞的所有字段起作用。因?yàn)榉衷~字段產(chǎn)生太多tokens且Doc Values對(duì)其并不是很有效。Doc Values默認(rèn)開啟,如果不執(zhí)行基于一個(gè)確定的子段聚合、排序或執(zhí)行腳本(Script ),可以選擇關(guān)閉Doc Values,這可以節(jié)省磁盤空間,提高索引數(shù)據(jù)的速度。
text字段不支持doc_values,text使用fielddata,一種在查詢時(shí)期生成在緩存里的數(shù)據(jù)結(jié)構(gòu)。當(dāng)字段在首次sort、aggregations或in a script時(shí)創(chuàng)建,讀取磁盤上所有segment的倒排索引,反轉(zhuǎn) term<->doc 的關(guān)系,加載到j(luò)vm heap,它將在segment的整個(gè)生命周期內(nèi)一直存在。fielddata很耗內(nèi)存,默認(rèn)禁用fielddata。text字段是先分詞再索引的,因此,應(yīng)該使用不分詞的keyword用來聚合。
organizer字段的mapping如下:
"organizer" : {
"type" : "text"
}
正如錯(cuò)誤提示中所指出的,要解決這個(gè)問題,可選擇兩種方式:一是設(shè)置fielddata=true,二是增加keyword字段。第一種方法可以即時(shí)生效,第二種方法需要重新索引數(shù)據(jù)才能生效。所以建議在創(chuàng)建index時(shí),仔細(xì)定義mapping,以免以后修改結(jié)構(gòu)產(chǎn)生問題。
第一種方式:
# 設(shè)置fielddata=true
curl -XPOST "172.16.1.127:9200/get-together/_mapping/_doc?pretty" -H 'Content-Type: application/json' -d'
{
"properties": {
"organizer": {
"type": "text",
"fielddata": "true"
}
}
}'
# 執(zhí)行聚合查詢
curl 172.16.1.127:9200/get-together/_doc/_search?pretty -H 'Content-Type: application/json' -d '
{
"aggregations": {
"organizers": {
"terms": {
"field": "organizer"
}
}
}
}'
第二種方式:
# 修改organizer字段的映射
curl -XPOST "172.16.1.127:9200/get-together/_mapping/_doc?pretty" -H 'Content-Type: application/json' -d'
{
"properties": {
"organizer": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}'
# 重新索引數(shù)據(jù)
# 在organizer.keyword字段上執(zhí)行聚合
curl 172.16.1.127:9200/get-together/_doc/_search?pretty -H 'Content-Type: application/json' -d '
{
"aggregations": {
"organizers": {
"terms": {
"field": "organizer.keyword"
}
}
}
}'
返回的聚合結(jié)果如下:
"aggregations" : {
"organizers" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "Lee",
"doc_count" : 2
},
{
"key" : "Andy",
"doc_count" : 1
},
{
"key" : "Daniel",
"doc_count" : 1
},
{
"key" : "Mik",
"doc_count" : 1
},
{
"key" : "Tyler",
"doc_count" : 1
}
]
}
}
關(guān)于Doc Values和FieldData的更多說明,參見“Es官方文檔整理-3.Doc Values和FieldData”。
4. 通過ID獲取文檔
為了獲取一個(gè)具體的文檔,必須要知道它所屬的索引、類型和ID。然后就可以發(fā)送HTTP GET請求到這篇文檔的URI:
curl '172.16.1.127:9200/get-together/_doc/1?pretty'
返回:
{
"_index" : "get-together",
"_type" : "_doc",
"_id" : "doesnt-exist",
"found" : false
}
通過ID獲得文檔要比搜索更快,所消耗的資源成本也更低。這也是實(shí)時(shí)完成的:只要索引操作完成了,新的文檔就可以通過GET API獲取。相比之下,搜索是近實(shí)時(shí)的,因?yàn)樗鼈冃枰却J(rèn)情況下每秒進(jìn)行一次的刷新操作。這個(gè)思想和DB也類似,通過主鍵查詢通常是查詢數(shù)據(jù)最快的途徑。


版權(quán)聲明:
本文為大數(shù)據(jù)技術(shù)與架構(gòu)整理,原作者獨(dú)家授權(quán)。未經(jīng)原作者允許轉(zhuǎn)載追究侵權(quán)責(zé)任。編輯|冷眼丶微信公眾號(hào)|import_bigdata文章不錯(cuò)?點(diǎn)個(gè)【在看】吧!??


