架構(gòu)原理
本書(shū)作為 Elastic Stack 指南,關(guān)注于 Elasticsearch 在日志和數(shù)據(jù)分析場(chǎng)景的應(yīng)用,并不打算對(duì)底層的 Lucene 原理或者 Java 編程做詳細(xì)的介紹,但是 Elasticsearch 層面上的一些架構(gòu)設(shè)計(jì),對(duì)我們做性能調(diào)優(yōu),故障處理,具有非常重要的影響。
所以,作為 ES 部分的起始章節(jié),先從數(shù)據(jù)流向和分布的層面,介紹一下 ES 的工作原理,以及相關(guān)的可控項(xiàng)。各位讀者可以跳過(guò)這節(jié)先行閱讀后面的運(yùn)維操作部分,但作為性能調(diào)優(yōu)的基礎(chǔ)知識(shí),依然建議大家抽時(shí)間返回來(lái)了解。
帶著問(wèn)題學(xué)習(xí)
- 寫(xiě)入的數(shù)據(jù)是如何變成elasticsearch里可以被檢索和聚合的索引內(nèi)容的?
- lucene如何實(shí)現(xiàn)準(zhǔn)實(shí)時(shí)索引?
- 什么是segment?
- 什么是commit?
- segment的數(shù)據(jù)來(lái)自哪里?
- segment在寫(xiě)入磁盤(pán)前就可以被檢索,是因?yàn)槔昧耸裁矗?/li>
- elasticsearch中的refresh操作是什么?配置項(xiàng)是哪個(gè)?設(shè)置的命令是什么?
- refresh只是寫(xiě)到了文件系統(tǒng)緩存,那么實(shí)際寫(xiě)入磁盤(pán)是由什么控制呢?,如果這期間發(fā)生錯(cuò)誤和故障,數(shù)據(jù)會(huì)不會(huì)丟失?
- 什么是translog日志?什么時(shí)候會(huì)被清空?什么是flush操作?配置項(xiàng)是什么?怎么配置?
10.什么是段合并?為什么要段合并?段合并線程配置項(xiàng)?段合并策略?怎么forcemerge(optimize)? - routing的規(guī)則是什么樣的?replica讀寫(xiě)過(guò)程?wait_for_active_shards參數(shù)timeout參數(shù) ?
- reroute 接口?
- 兩種 自動(dòng)發(fā)現(xiàn)方式?
目錄
segment、buffer和translog對(duì)實(shí)時(shí)性的影響
既然介紹數(shù)據(jù)流向,首先第一步就是:寫(xiě)入的數(shù)據(jù)是如何變成 Elasticsearch 里可以被檢索和聚合的索引內(nèi)容的?
以單文件的靜態(tài)層面看,每個(gè)全文索引都是一個(gè)詞元的倒排索引,具體涉及到全文索引的通用知識(shí),這里不單獨(dú)介紹,有興趣的讀者可以閱讀《Lucene in Action》等書(shū)籍詳細(xì)了解。
動(dòng)態(tài)更新的 Lucene 索引
以在線動(dòng)態(tài)服務(wù)的層面看,要做到實(shí)時(shí)更新條件下數(shù)據(jù)的可用和可靠,就需要在倒排索引的基礎(chǔ)上,再做一系列更高級(jí)的處理。
其實(shí)總結(jié)一下 Lucene 的處理辦法,很簡(jiǎn)單,就是一句話:新收到的數(shù)據(jù)寫(xiě)到新的索引文件里。
Lucene 把每次生成的倒排索引,叫做一個(gè)段(segment)。然后另外使用一個(gè) commit 文件,記錄索引內(nèi)所有的 segment。而生成 segment 的數(shù)據(jù)來(lái)源,則是內(nèi)存中的 buffer。也就是說(shuō),動(dòng)態(tài)更新過(guò)程如下:
-
當(dāng)前索引有 3 個(gè) segment 可用。索引狀態(tài)如圖 2-1;
A Lucene index with a commit point and three segments圖 2-1
-
新接收的數(shù)據(jù)進(jìn)入內(nèi)存 buffer。索引狀態(tài)如圖 2-2;
A Lucene index with new documents in the in-memory buffer, ready to commit圖 2-2
-
內(nèi)存 buffer 刷到磁盤(pán),生成一個(gè)新的 segment,commit 文件同步更新。索引狀態(tài)如圖 2-3。
After a commit, a new segment is added to the index and the buffer is cleared圖 2-3
利用磁盤(pán)緩存實(shí)現(xiàn)的準(zhǔn)實(shí)時(shí)檢索
既然涉及到磁盤(pán),那么一個(gè)不可避免的問(wèn)題就來(lái)了:磁盤(pán)太慢了!對(duì)我們要求實(shí)時(shí)性很高的服務(wù)來(lái)說(shuō),這種處理還不夠。所以,在第 3 步的處理中,還有一個(gè)中間狀態(tài):
-
內(nèi)存 buffer 生成一個(gè)新的 segment,刷到文件系統(tǒng)緩存中,Lucene 即可檢索這個(gè)新 segment。索引狀態(tài)如圖 2-4。
The buffer contents have been written to a segment, which is searchable, but is not yet commited圖 2-4
文件系統(tǒng)緩存真正同步到磁盤(pán)上,commit 文件更新。達(dá)到圖 2-3 中的狀態(tài)。
這一步刷到文件系統(tǒng)緩存的步驟,在 Elasticsearch 中,是默認(rèn)設(shè)置為 1 秒間隔的,對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō),幾乎就相當(dāng)于是實(shí)時(shí)可搜索了。Elasticsearch 也提供了單獨(dú)的 /_refresh 接口,用戶如果對(duì) 1 秒間隔還不滿意的,可以主動(dòng)調(diào)用該接口來(lái)保證搜索可見(jiàn)。
注:5.0 中還提供了一個(gè)新的請(qǐng)求參數(shù):?refresh=wait_for,可以在寫(xiě)入數(shù)據(jù)后不強(qiáng)制刷新但一直等到刷新才返回。
不過(guò)對(duì)于 Elastic Stack 的日志場(chǎng)景來(lái)說(shuō),恰恰相反,我們并不需要如此高的實(shí)時(shí)性,而是需要更快的寫(xiě)入性能。所以,一般來(lái)說(shuō),我們反而會(huì)通過(guò) /_settings 接口或者定制 template 的方式,加大 refresh_interval 參數(shù):
# curl -XPOST http://127.0.0.1:9200/logstash-2015.06.21/_settings -d'{ "refresh_interval": "10s" }'
- 1
- 2
- 3
如果是導(dǎo)入歷史數(shù)據(jù)的場(chǎng)合,那甚至可以先完全關(guān)閉掉:
# curl -XPUT http://127.0.0.1:9200/logstash-2015.05.01 -d'{ "settings" : { "refresh_interval": "-1" }}'
- 1
- 2
- 3
- 4
- 5
- 6
在導(dǎo)入完成以后,修改回來(lái)或者手動(dòng)調(diào)用一次即可:
# curl -XPOST http://127.0.0.1:9200/logstash-2015.05.01/_refresh
- 1
【聲明:轉(zhuǎn)載請(qǐng)注明出處
獨(dú)立:http://wangnan.tech
簡(jiǎn)書(shū):http://www.itdecent.cn/u/244399b1d776
CSDN:http://blog.csdn.net/wangnan9279】
translog 提供的磁盤(pán)同步控制
既然 refresh 只是寫(xiě)到文件系統(tǒng)緩存,那么第 4 步寫(xiě)到實(shí)際磁盤(pán)又是有什么來(lái)控制的?如果這期間發(fā)生主機(jī)錯(cuò)誤、硬件故障等異常情況,數(shù)據(jù)會(huì)不會(huì)丟失?
這里,其實(shí)有另一個(gè)機(jī)制來(lái)控制。Elasticsearch 在把數(shù)據(jù)寫(xiě)入到內(nèi)存 buffer 的同時(shí),其實(shí)還另外記錄了一個(gè) translog 日志。也就是說(shuō),第 2 步并不是圖 2-2 的狀態(tài),而是像圖 2-5 這樣:

圖 2-5
在第 3 和第 4 步,refresh 發(fā)生的時(shí)候,translog 日志文件依然保持原樣,如圖 2-6:

圖 2-6
也就是說(shuō),如果在這期間發(fā)生異常,Elasticsearch 會(huì)從 commit 位置開(kāi)始,恢復(fù)整個(gè) translog 文件中的記錄,保證數(shù)據(jù)一致性。
等到真正把 segment 刷到磁盤(pán),且 commit 文件進(jìn)行更新的時(shí)候, translog 文件才清空。這一步,叫做 flush。同樣,Elasticsearch 也提供了 /_flush 接口。
對(duì)于 flush 操作,Elasticsearch 默認(rèn)設(shè)置為:每 30 分鐘主動(dòng)進(jìn)行一次 flush,或者當(dāng) translog 文件大小大于 512MB (老版本是 200MB)時(shí),主動(dòng)進(jìn)行一次 flush。這兩個(gè)行為,可以分別通過(guò) index.translog.flush_threshold_period 和 index.translog.flush_threshold_size 參數(shù)修改。
如果對(duì)這兩種控制方式都不滿意,Elasticsearch 還可以通過(guò) index.translog.flush_threshold_ops 參數(shù),控制每收到多少條數(shù)據(jù)后 flush 一次。
translog 的一致性
索引數(shù)據(jù)的一致性通過(guò) translog 保證。那么 translog 文件自己呢?
默認(rèn)情況下,Elasticsearch 每 5 秒,或每次請(qǐng)求操作結(jié)束前,會(huì)強(qiáng)制刷新 translog 日志到磁盤(pán)上。
后者是 Elasticsearch 2.0 新加入的特性。為了保證不丟數(shù)據(jù),每次 index、bulk、delete、update 完成的時(shí)候,一定觸發(fā)刷新 translog 到磁盤(pán)上,才給請(qǐng)求返回 200 OK。這個(gè)改變?cè)谔岣邤?shù)據(jù)安全性的同時(shí)當(dāng)然也降低了一點(diǎn)性能。
如果你不在意這點(diǎn)可能性,還是希望性能優(yōu)先,可以在 index template 里設(shè)置如下參數(shù):
{ "index.translog.durability": "async"}
- 1
- 2
- 3
Elasticsearch 分布式索引
大家可能注意到了,前面一段內(nèi)容,一直寫(xiě)的是”Lucene 索引”。這個(gè)區(qū)別在于,Elasticsearch 為了完成分布式系統(tǒng),對(duì)一些名詞概念作了變動(dòng)。索引成為了整個(gè)集群級(jí)別的命名,而在單個(gè)主機(jī)上的Lucene 索引,則被命名為分片(shard)。至于數(shù)據(jù)是怎么識(shí)別到自己應(yīng)該在哪個(gè)分片,請(qǐng)閱讀稍后有關(guān) routing 的章節(jié)。
segment merge對(duì)寫(xiě)入性能的影響
通過(guò)上節(jié)內(nèi)容,我們知道了數(shù)據(jù)怎么進(jìn)入 ES 并且如何才能讓數(shù)據(jù)更快的被檢索使用。其中用一句話概括了 Lucene 的設(shè)計(jì)思路就是”開(kāi)新文件”。從另一個(gè)方面看,開(kāi)新文件也會(huì)給服務(wù)器帶來(lái)負(fù)載壓力。因?yàn)槟J(rèn)每 1 秒,都會(huì)有一個(gè)新文件產(chǎn)生,每個(gè)文件都需要有文件句柄,內(nèi)存,CPU 使用等各種資源。一天有 86400 秒,設(shè)想一下,每次請(qǐng)求要掃描一遍 86400 個(gè)文件,這個(gè)響應(yīng)性能絕對(duì)好不了!
為了解決這個(gè)問(wèn)題,ES 會(huì)不斷在后臺(tái)運(yùn)行任務(wù),主動(dòng)將這些零散的 segment 做數(shù)據(jù)歸并,盡量讓索引內(nèi)只保有少量的,每個(gè)都比較大的,segment 文件。這個(gè)過(guò)程是有獨(dú)立的線程來(lái)進(jìn)行的,并不影響新 segment 的產(chǎn)生。歸并過(guò)程中,索引狀態(tài)如圖 2-7,尚未完成的較大的 segment 是被排除在檢索可見(jiàn)范圍之外的:

圖 2-7
當(dāng)歸并完成,較大的這個(gè) segment 刷到磁盤(pán)后,commit 文件做出相應(yīng)變更,刪除之前幾個(gè)小 segment,改成新的大 segment。等檢索請(qǐng)求都從小 segment 轉(zhuǎn)到大 segment 上以后,刪除沒(méi)用的小 segment。這時(shí)候,索引里 segment 數(shù)量就下降了,狀態(tài)如圖 2-8 所示:

圖 2-8
歸并線程配置
segment 歸并的過(guò)程,需要先讀取 segment,歸并計(jì)算,再寫(xiě)一遍 segment,最后還要保證刷到磁盤(pán)??梢哉f(shuō),這是一個(gè)非常消耗磁盤(pán) IO 和 CPU 的任務(wù)。所以,ES 提供了對(duì)歸并線程的限速機(jī)制,確保這個(gè)任務(wù)不會(huì)過(guò)分影響到其他任務(wù)。
在 5.0 之前,歸并線程的限速配置 indices.store.throttle.max_bytes_per_sec 是 20MB。對(duì)于寫(xiě)入量較大,磁盤(pán)轉(zhuǎn)速較高,甚至使用 SSD 盤(pán)的服務(wù)器來(lái)說(shuō),這個(gè)限速是明顯過(guò)低的。對(duì)于 Elastic Stack 應(yīng)用,社區(qū)廣泛的建議是可以適當(dāng)調(diào)大到 100MB或者更高。
# curl -XPUT http://127.0.0.1:9200/_cluster/settings -d'{ "persistent" : { "indices.store.throttle.max_bytes_per_sec" : "100mb" }}'
- 1
- 2
- 3
- 4
- 5
- 6
5.0 開(kāi)始,ES 對(duì)此作了大幅度改進(jìn),使用了 Lucene 的 CMS(ConcurrentMergeScheduler) 的 auto throttle 機(jī)制,正常情況下已經(jīng)不再需要手動(dòng)配置 indices.store.throttle.max_bytes_per_sec 了。官方文檔中都已經(jīng)刪除了相關(guān)介紹,不過(guò)從源碼中還是可以看到,這個(gè)值目前的默認(rèn)設(shè)置是 10240 MB。
歸并線程的數(shù)目,ES 也是有所控制的。默認(rèn)數(shù)目的計(jì)算公式是: Math.min(3, Runtime.getRuntime().availableProcessors() / 2)。即服務(wù)器 CPU 核數(shù)的一半大于 3 時(shí),啟動(dòng) 3 個(gè)歸并線程;否則啟動(dòng)跟 CPU 核數(shù)的一半相等的線程數(shù)。相信一般做 Elastic Stack 的服務(wù)器 CPU 合數(shù)都會(huì)在 6 個(gè)以上。所以一般來(lái)說(shuō)就是 3 個(gè)歸并線程。如果你確定自己磁盤(pán)性能跟不上,可以降低 index.merge.scheduler.max_thread_count 配置,免得 IO 情況更加惡化。
歸并策略
歸并線程是按照一定的運(yùn)行策略來(lái)挑選 segment 進(jìn)行歸并的。主要有以下幾條:
- index.merge.policy.floor_segment
默認(rèn) 2MB,小于這個(gè)大小的 segment,優(yōu)先被歸并。 - index.merge.policy.max_merge_at_once
默認(rèn)一次最多歸并 10 個(gè) segment - index.merge.policy.max_merge_at_once_explicit
默認(rèn) forcemerge 時(shí)一次最多歸并 30 個(gè) segment。 - index.merge.policy.max_merged_segment
默認(rèn) 5 GB,大于這個(gè)大小的 segment,不用參與歸并。forcemerge 除外。
根據(jù)這段策略,其實(shí)我們也可以從另一個(gè)角度考慮如何減少 segment 歸并的消耗以及提高響應(yīng)的辦法:加大 flush 間隔,盡量讓每次新生成的 segment 本身大小就比較大。
forcemerge 接口
既然默認(rèn)的最大 segment 大小是 5GB。那么一個(gè)比較龐大的數(shù)據(jù)索引,就必然會(huì)有為數(shù)不少的 segment 永遠(yuǎn)存在,這對(duì)文件句柄,內(nèi)存等資源都是極大的浪費(fèi)。但是由于歸并任務(wù)太消耗資源,所以一般不太選擇加大 index.merge.policy.max_merged_segment 配置,而是在負(fù)載較低的時(shí)間段,通過(guò) forcemerge 接口,強(qiáng)制歸并 segment。
# curl -XPOST http://127.0.0.1:9200/logstash-2015-06.10/_forcemerge?max_num_segments=1
- 1
由于 forcemerge 線程對(duì)資源的消耗比普通的歸并線程大得多,所以,絕對(duì)不建議對(duì)還在寫(xiě)入數(shù)據(jù)的熱索引執(zhí)行這個(gè)操作。這個(gè)問(wèn)題對(duì)于 Elastic Stack 來(lái)說(shuō)非常好辦,一般索引都是按天分割的。更合適的任務(wù)定義方式,請(qǐng)閱讀本書(shū)稍后的 curator 章節(jié)。
routing和replica的讀寫(xiě)過(guò)程
之前兩節(jié),完整介紹了在單個(gè) Lucene 索引,即 ES 分片內(nèi)的數(shù)據(jù)寫(xiě)入流程。現(xiàn)在徹底回到 ES 的分布式層面上來(lái),當(dāng)一個(gè) ES 節(jié)點(diǎn)收到一條數(shù)據(jù)的寫(xiě)入請(qǐng)求時(shí),它是如何確認(rèn)這個(gè)數(shù)據(jù)應(yīng)該存儲(chǔ)在哪個(gè)節(jié)點(diǎn)的哪個(gè)分片上的?
路由計(jì)算
作為一個(gè)沒(méi)有額外依賴的簡(jiǎn)單的分布式方案,ES 在這個(gè)問(wèn)題上同樣選擇了一個(gè)非常簡(jiǎn)潔的處理方式,對(duì)任一條數(shù)據(jù)計(jì)算其對(duì)應(yīng)分片的方式如下:
shard = hash(routing) % number_of_primary_shards
每個(gè)數(shù)據(jù)都有一個(gè) routing 參數(shù),默認(rèn)情況下,就使用其 _id 值。將其 _id 值計(jì)算哈希后,對(duì)索引的主分片數(shù)取余,就是數(shù)據(jù)實(shí)際應(yīng)該存儲(chǔ)到的分片 ID。
由于取余這個(gè)計(jì)算,完全依賴于分母,所以導(dǎo)致 ES 索引有一個(gè)限制,索引的主分片數(shù),不可以隨意修改。因?yàn)橐坏┲鞣制瑪?shù)不一樣,所以數(shù)據(jù)的存儲(chǔ)位置計(jì)算結(jié)果都會(huì)發(fā)生改變,索引數(shù)據(jù)就完全不可讀了。
副本一致性
作為分布式系統(tǒng),數(shù)據(jù)副本可算是一個(gè)標(biāo)配。ES 數(shù)據(jù)寫(xiě)入流程,自然也涉及到副本。在有副本配置的情況下,數(shù)據(jù)從發(fā)向 ES 節(jié)點(diǎn),到接到 ES 節(jié)點(diǎn)響應(yīng)返回,流向如下(附圖 2-9):
- 客戶端請(qǐng)求發(fā)送給 Node 1 節(jié)點(diǎn),注意圖中 Node 1 是 Master 節(jié)點(diǎn),實(shí)際完全可以不是。
- Node 1 用數(shù)據(jù)的
_id取余計(jì)算得到應(yīng)該講數(shù)據(jù)存儲(chǔ)到 shard 0 上。通過(guò) cluster state 信息發(fā)現(xiàn) shard 0 的主分片已經(jīng)分配到了 Node 3 上。Node 1 轉(zhuǎn)發(fā)請(qǐng)求數(shù)據(jù)給 Node 3。 - Node 3 完成請(qǐng)求數(shù)據(jù)的索引過(guò)程,存入主分片 0。然后并行轉(zhuǎn)發(fā)數(shù)據(jù)給分配有 shard 0 的副本分片的 Node 1 和 Node 2。當(dāng)收到任一節(jié)點(diǎn)匯報(bào)副本分片數(shù)據(jù)寫(xiě)入成功,Node 3 即返回給初始的接收節(jié)點(diǎn) Node 1,宣布數(shù)據(jù)寫(xiě)入成功。Node 1 返回成功響應(yīng)給客戶端。

圖 2-9
這個(gè)過(guò)程中,有幾個(gè)參數(shù)可以用來(lái)控制或變更其行為:
- wait_for_active_shards
上面示例中,2 個(gè)副本分片只要有 1 個(gè)成功,就可以返回給客戶端了。這點(diǎn)也是有配置項(xiàng)的。其默認(rèn)值的計(jì)算來(lái)源如下:
int( (primary + number_of_replicas) / 2 ) + 1
根據(jù)需要,也可以將參數(shù)設(shè)置為 one,表示僅寫(xiě)完主分片就返回,等同于 async;還可以設(shè)置為 all,表示等所有副本分片都寫(xiě)完才能返回。
- timeout
如果集群出現(xiàn)異常,有些分片當(dāng)前不可用,ES 默認(rèn)會(huì)等待 1 分鐘看分片能否恢復(fù)??梢允褂??timeout=30s參數(shù)來(lái)縮短這個(gè)等待時(shí)間。
副本配置和分片配置不一樣,是可以隨時(shí)調(diào)整的。有些較大的索引,甚至可以在做 forcemerge 前,先把副本全部取消掉,等 optimize 完后,再重新開(kāi)啟副本,節(jié)約單個(gè) segment 的重復(fù)歸并消耗。
# curl -XPUT http://127.0.0.1:9200/logstash-mweibo-2015.05.02/_settings -d '{ "index": { "number_of_replicas" : 0 }}'
- 1
- 2
- 3
shard 的 allocate 控制
某個(gè) shard 分配在哪個(gè)節(jié)點(diǎn)上,一般來(lái)說(shuō),是由 ES 自動(dòng)決定的。以下幾種情況會(huì)觸發(fā)分配動(dòng)作:
- 新索引生成
- 索引的刪除
- 新增副本分片
- 節(jié)點(diǎn)增減引發(fā)的數(shù)據(jù)均衡
ES 提供了一系列參數(shù)詳細(xì)控制這部分邏輯:
- cluster.routing.allocation.enable
該參數(shù)用來(lái)控制允許分配哪種分片。默認(rèn)是all??蛇x項(xiàng)還包括primaries和new_primaries。none則徹底拒絕分片。該參數(shù)的作用,本書(shū)稍后集群升級(jí)章節(jié)會(huì)有說(shuō)明。 - cluster.routing.allocation.allow_rebalance
該參數(shù)用來(lái)控制什么時(shí)候允許數(shù)據(jù)均衡。默認(rèn)是indices_all_active,即要求所有分片都正常啟動(dòng)成功以后,才可以進(jìn)行數(shù)據(jù)均衡操作,否則的話,在集群重啟階段,會(huì)浪費(fèi)太多流量了。 - cluster.routing.allocation.cluster_concurrent_rebalance
該參數(shù)用來(lái)控制集群內(nèi)同時(shí)運(yùn)行的數(shù)據(jù)均衡任務(wù)個(gè)數(shù)。默認(rèn)是 2 個(gè)。如果有節(jié)點(diǎn)增減,且集群負(fù)載壓力不高的時(shí)候,可以適當(dāng)加大。 - cluster.routing.allocation.node_initial_primaries_recoveries
該參數(shù)用來(lái)控制節(jié)點(diǎn)重啟時(shí),允許同時(shí)恢復(fù)幾個(gè)主分片。默認(rèn)是 4 個(gè)。如果節(jié)點(diǎn)是多磁盤(pán),且 IO 壓力不大,可以適當(dāng)加大。 - cluster.routing.allocation.node_concurrent_recoveries
該參數(shù)用來(lái)控制節(jié)點(diǎn)除了主分片重啟恢復(fù)以外其他情況下,允許同時(shí)運(yùn)行的數(shù)據(jù)恢復(fù)任務(wù)。默認(rèn)是 2 個(gè)。所以,節(jié)點(diǎn)重啟時(shí),可以看到主分片迅速恢復(fù)完成,副本分片的恢復(fù)卻很慢。除了副本分片本身數(shù)據(jù)要通過(guò)網(wǎng)絡(luò)復(fù)制以外,并發(fā)線程本身也減少了一半。當(dāng)然,這種設(shè)置也是有道理的——主分片一定是本地恢復(fù),副本分片卻需要走網(wǎng)絡(luò),帶寬是有限的。從 ES 1.6 開(kāi)始,冷索引的副本分片可以本地恢復(fù),這個(gè)參數(shù)也就是可以適當(dāng)加大了。 - indices.recovery.concurrent_streams
該參數(shù)用來(lái)控制節(jié)點(diǎn)從網(wǎng)絡(luò)復(fù)制恢復(fù)副本分片時(shí)的數(shù)據(jù)流個(gè)數(shù)。默認(rèn)是 3 個(gè)??梢耘浜仙弦粭l配置一起加大。 - indices.recovery.max_bytes_per_sec
該參數(shù)用來(lái)控制節(jié)點(diǎn)恢復(fù)時(shí)的速率。默認(rèn)是 40MB。顯然是比較小的,建議加大。
此外,ES 還有一些其他的分片分配控制策略。比如以 tag 和 rack_id 作為區(qū)分等。一般來(lái)說(shuō),Elastic Stack 場(chǎng)景中使用不多。運(yùn)維人員可能比較常見(jiàn)的策略有兩種:
- 磁盤(pán)限額
為了保護(hù)節(jié)點(diǎn)數(shù)據(jù)安全,ES 會(huì)定時(shí)(cluster.info.update.interval,默認(rèn) 30 秒)檢查一下各節(jié)點(diǎn)的數(shù)據(jù)目錄磁盤(pán)使用情況。在達(dá)到cluster.routing.allocation.disk.watermark.low(默認(rèn) 85%)的時(shí)候,新索引分片就不會(huì)再分配到這個(gè)節(jié)點(diǎn)上了。在達(dá)到cluster.routing.allocation.disk.watermark.high(默認(rèn) 90%)的時(shí)候,就會(huì)觸發(fā)該節(jié)點(diǎn)現(xiàn)存分片的數(shù)據(jù)均衡,把數(shù)據(jù)挪到其他節(jié)點(diǎn)上去。這兩個(gè)值不但可以寫(xiě)百分比,還可以寫(xiě)具體的字節(jié)數(shù)。有些公司可能出于成本考慮,對(duì)磁盤(pán)使用率有一定的要求,需要適當(dāng)抬高這個(gè)配置:
# curl -XPUT localhost:9200/_cluster/settings -d '{ "transient" : { "cluster.routing.allocation.disk.watermark.low" : "85%", "cluster.routing.allocation.disk.watermark.high" : "10gb", "cluster.info.update.interval" : "1m" }}'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 熱索引分片不均
默認(rèn)情況下,ES 集群的數(shù)據(jù)均衡策略是以各節(jié)點(diǎn)的分片總數(shù)(indices_all_active)作為基準(zhǔn)的。這對(duì)于搜索服務(wù)來(lái)說(shuō)無(wú)疑是均衡搜索壓力提高性能的好辦法。但是對(duì)于 Elastic Stack 場(chǎng)景,一般壓力集中在新索引的數(shù)據(jù)寫(xiě)入方面。正常運(yùn)行的時(shí)候,也沒(méi)有問(wèn)題。但是當(dāng)集群擴(kuò)容時(shí),新加入集群的節(jié)點(diǎn),分片總數(shù)遠(yuǎn)遠(yuǎn)低于其他節(jié)點(diǎn)。這時(shí)候如果有新索引創(chuàng)建,ES 的默認(rèn)策略會(huì)導(dǎo)致新索引的所有主分片幾乎全分配在這臺(tái)新節(jié)點(diǎn)上。整個(gè)集群的寫(xiě)入壓力,壓在一個(gè)節(jié)點(diǎn)上,結(jié)果很可能是這個(gè)節(jié)點(diǎn)直接被壓死,集群出現(xiàn)異常。
所以,對(duì)于 Elastic Stack 場(chǎng)景,強(qiáng)烈建議大家預(yù)先計(jì)算好索引的分片數(shù)后,配置好單節(jié)點(diǎn)分片的限額。比如,一個(gè) 5 節(jié)點(diǎn)的集群,索引主分片 10 個(gè),副本 1 份。則平均下來(lái)每個(gè)節(jié)點(diǎn)應(yīng)該有 4 個(gè)分片,那么就配置:
# curl -s -XPUT http://127.0.0.1:9200/logstash-2015.05.08/_settings -d '{ "index": { "routing.allocation.total_shards_per_node" : "5" }}'
- 1
- 2
- 3
注意,這里配置的是 5 而不是 4。因?yàn)槲覀冃枰A(yù)防有機(jī)器故障,分片發(fā)生遷移的情況。如果寫(xiě)的是 4,那么分片遷移會(huì)失敗。
此外,另一種方式則更加玄妙,Elasticsearch 中有一系列參數(shù),相互影響,最終聯(lián)合決定分片分配:
- cluster.routing.allocation.balance.shard
節(jié)點(diǎn)上分配分片的權(quán)重,默認(rèn)為 0.45。數(shù)值越大越傾向于在節(jié)點(diǎn)層面均衡分片。 - cluster.routing.allocation.balance.index
每個(gè)索引往單個(gè)節(jié)點(diǎn)上分配分片的權(quán)重,默認(rèn)為 0.55。數(shù)值越大越傾向于在索引層面均衡分片。 - cluster.routing.allocation.balance.threshold
大于閾值則觸發(fā)均衡操作。默認(rèn)為1。
Elasticsearch 中的計(jì)算方法是:
(indexBalance * (node.numShards(index) – avgShardsPerNode(index)) + shardBalance * (node.numShards() – avgShardsPerNode)) <=> weightthreshold
所以,也可以采取加大 cluster.routing.allocation.balance.index,甚至設(shè)置 cluster.routing.allocation.balance.shard 為 0 來(lái)盡量采用索引內(nèi)的節(jié)點(diǎn)均衡。
reroute 接口
上面說(shuō)的各種配置,都是從策略層面,控制分片分配的選擇。在必要的時(shí)候,還可以通過(guò) ES 的 reroute 接口,手動(dòng)完成對(duì)分片的分配選擇的控制。
reroute 接口支持五種指令:allocate_replica, allocate_stale_primary, allocate_empty_primary,move 和 cancel。常用的一般是 allocate 和 move:
-
allocate_*指令
因?yàn)樨?fù)載過(guò)高等原因,有時(shí)候個(gè)別分片可能長(zhǎng)期處于 UNASSIGNED 狀態(tài),我們就可以手動(dòng)分配分片到指定節(jié)點(diǎn)上。默認(rèn)情況下只允許手動(dòng)分配副本分片(即使用 allocate_replica),所以如果要分配主分片,需要單獨(dú)加一個(gè) accept_data_loss選項(xiàng):
# curl -XPOST 127.0.0.1:9200/_cluster/reroute -d '{ "commands" : [ { "allocate_stale_primary" : { "index" : "logstash-2015.05.27", "shard" : 61, "node" : "10.19.0.77", "accept_data_loss" : true } } ]}'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
注意,allocate_stale_primary 表示準(zhǔn)備分配到的節(jié)點(diǎn)上可能有老版本的歷史數(shù)據(jù),運(yùn)行時(shí)請(qǐng)?zhí)崆按_認(rèn)一下是哪個(gè)節(jié)點(diǎn)上保留有這個(gè)分片的實(shí)際目錄,且目錄大小最大。然后手動(dòng)分配到這個(gè)節(jié)點(diǎn)上。以此減少數(shù)據(jù)丟失。
- move 指令
因?yàn)樨?fù)載過(guò)高,磁盤(pán)利用率過(guò)高,服務(wù)器下線,更換磁盤(pán)等原因,可以會(huì)需要從節(jié)點(diǎn)上移走部分分片:
curl -XPOST 127.0.0.1:9200/_cluster/reroute -d '{ "commands" : [ { "move" : { "index" : "logstash-2015.05.22", "shard" : 0, "from_node" : "10.19.0.81", "to_node" : "10.19.0.104" } } ]}'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
分配失敗原因
如果是自己手工 reroute 失敗,Elasticsearch 返回的響應(yīng)中會(huì)帶上失敗的原因。不過(guò)格式非常難看,一堆 YES,NO。從 5.0 版本開(kāi)始,Elasticsearch 新增了一個(gè) allocation explain 接口,專門(mén)用來(lái)解釋指定分片的具體失敗理由:
curl -XGET 'http://localhost:9200/_cluster/allocation/explain' -d'{ "index": "logstash-2016.10.31", "shard": 0, "primary": false }'
- 1
- 2
- 3
- 4
- 5
- 6
得到的響應(yīng)如下:
{ "shard" : { "index" : "myindex", "index_uuid" : "KnW0-zELRs6PK84l0r38ZA", "id" : 0, "primary" : false }, "assigned" : false, "shard_state_fetch_pending": false, "unassigned_info" : { "reason" : "INDEX_CREATED", "at" : "2016-03-22T20:04:23.620Z" }, "allocation_delay_ms" : 0, "remaining_delay_ms" : 0, "nodes" : { "V-Spi0AyRZ6ZvKbaI3691w" : { "node_name" : "H5dfFeA", "node_attributes" : { "bar" : "baz" }, "store" : { "shard_copy" : "NONE" }, "final_decision" : "NO", "final_explanation" : "the shard cannot be assigned because one or more allocation decider returns a 'NO' decision", "weight" : 0.06666675, "decisions" : [ { "decider" : "filter", "decision" : "NO", "explanation" : "node does not match index include filters [foo:\"bar\"]" } ] }, "Qc6VL8c5RWaw1qXZ0Rg57g" : { ...
- 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
這會(huì)是很長(zhǎng)一串 JSON,把集群里所有的節(jié)點(diǎn)都列上來(lái),挨個(gè)解釋為什么不能分配到這個(gè)節(jié)點(diǎn)。
節(jié)點(diǎn)下線
集群中個(gè)別節(jié)點(diǎn)出現(xiàn)故障預(yù)警等情況,需要下線,也是 Elasticsearch 運(yùn)維工作中常見(jiàn)的情況。如果已經(jīng)穩(wěn)定運(yùn)行過(guò)一段時(shí)間的集群,每個(gè)節(jié)點(diǎn)上都會(huì)保存有數(shù)量不少的分片。這種時(shí)候通過(guò) reroute 接口手動(dòng)轉(zhuǎn)移,就顯得太過(guò)麻煩了。這個(gè)時(shí)候,有另一種方式:
curl -XPUT 127.0.0.1:9200/_cluster/settings -d '{ "transient" :{ "cluster.routing.allocation.exclude._ip" : "10.0.0.1" }}'
- 1
- 2
- 3
- 4
- 5
Elasticsearch 集群就會(huì)自動(dòng)把這個(gè) IP 上的所有分片,都自動(dòng)轉(zhuǎn)移到其他節(jié)點(diǎn)上。等到轉(zhuǎn)移完成,這個(gè)空節(jié)點(diǎn)就可以毫無(wú)影響的下線了。
和 _ip 類似的參數(shù)還有 _host, _name 等。此外,這類參數(shù)不單是 cluster 級(jí)別,也可以是 index 級(jí)別。下一小節(jié)就是 index 級(jí)別的用例。
冷熱數(shù)據(jù)的讀寫(xiě)分離
Elasticsearch 集群一個(gè)比較突出的問(wèn)題是: 用戶做一次大的查詢的時(shí)候, 非常大量的讀 IO 以及聚合計(jì)算導(dǎo)致機(jī)器 Load 升高, CPU 使用率上升, 會(huì)影響阻塞到新數(shù)據(jù)的寫(xiě)入, 這個(gè)過(guò)程甚至?xí)掷m(xù)幾分鐘。所以,可能需要仿照 MySQL 集群一樣,做讀寫(xiě)分離。
實(shí)施方案
- N 臺(tái)機(jī)器做熱數(shù)據(jù)的存儲(chǔ), 上面只放當(dāng)天的數(shù)據(jù)。這 N 臺(tái)熱數(shù)據(jù)節(jié)點(diǎn)上面的 elasticsearc.yml 中配置
node.attr.tag: hot - 之前的數(shù)據(jù)放在另外的 M 臺(tái)機(jī)器上。這 M 臺(tái)冷數(shù)據(jù)節(jié)點(diǎn)中配置
node.attr.tag: stale - 模板中控制對(duì)新建索引添加 hot 標(biāo)簽:
{ "order" : 0, "template" : "*", "settings" : { "index.routing.allocation.include.tag" : "hot" }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 每天計(jì)劃任務(wù)更新索引的配置, 將 tag 更改為 stale, 索引會(huì)自動(dòng)遷移到 M 臺(tái)冷數(shù)據(jù)節(jié)點(diǎn)
curl -XPUT http://127.0.0.1:9200/indexname/_settings -d'{ "index": { "routing": { "allocation": { "include": { "tag": "stale" } } } }}'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
這樣,寫(xiě)操作集中在 N 臺(tái)熱數(shù)據(jù)節(jié)點(diǎn)上,大范圍的讀操作集中在 M 臺(tái)冷數(shù)據(jù)節(jié)點(diǎn)上。避免了堵塞影響。
該方案運(yùn)用的,是 Elasticsearch 中的 allocation filter 功能,詳細(xì)說(shuō)明見(jiàn):https://www.elastic.co/guide/en/elasticsearch/reference/master/shard-allocation-filtering.html
集群自動(dòng)發(fā)現(xiàn)
ES 是一個(gè) P2P 類型(使用 gossip 協(xié)議)的分布式系統(tǒng),除了集群狀態(tài)管理以外,其他所有的請(qǐng)求都可以發(fā)送到集群內(nèi)任意一臺(tái)節(jié)點(diǎn)上,這個(gè)節(jié)點(diǎn)可以自己找到需要轉(zhuǎn)發(fā)給哪些節(jié)點(diǎn),并且直接跟這些節(jié)點(diǎn)通信。
所以,從網(wǎng)絡(luò)架構(gòu)及服務(wù)配置上來(lái)說(shuō),構(gòu)建集群所需要的配置極其簡(jiǎn)單。在 Elasticsearch 2.0 之前,無(wú)阻礙的網(wǎng)絡(luò)下,所有配置了相同 cluster.name 的節(jié)點(diǎn)都自動(dòng)歸屬到一個(gè)集群中。
2.0 版本之后,基于安全的考慮,Elasticsearch 稍作了調(diào)整,避免開(kāi)發(fā)環(huán)境過(guò)于隨便造成的麻煩。
unicast 方式
ES 從 2.0 版本開(kāi)始,默認(rèn)的自動(dòng)發(fā)現(xiàn)方式改為了單播(unicast)方式。配置里提供幾臺(tái)節(jié)點(diǎn)的地址,ES 將其視作 gossip router 角色,借以完成集群的發(fā)現(xiàn)。由于這只是 ES 內(nèi)一個(gè)很小的功能,所以 gossip router 角色并不需要單獨(dú)配置,每個(gè) ES 節(jié)點(diǎn)都可以擔(dān)任。所以,采用單播方式的集群,各節(jié)點(diǎn)都配置相同的幾個(gè)節(jié)點(diǎn)列表作為 router 即可。
此外,考慮到節(jié)點(diǎn)有時(shí)候因?yàn)楦哓?fù)載,慢 GC 等原因可能會(huì)有偶爾沒(méi)及時(shí)響應(yīng) ping 包的可能,一般建議稍微加大 Fault Detection 的超時(shí)時(shí)間。
同樣基于安全考慮做的變更還有監(jiān)聽(tīng)的主機(jī)名?,F(xiàn)在默認(rèn)只監(jiān)聽(tīng)本地 lo 網(wǎng)卡上。所以正式環(huán)境上需要修改配置為監(jiān)聽(tīng)具體的網(wǎng)卡。
network.host: "192.168.0.2" discovery.zen.minimum_master_nodes: 3discovery.zen.ping_timeout: 100sdiscovery.zen.fd.ping_timeout: 100sdiscovery.zen.ping.unicast.hosts: ["10.19.0.97","10.19.0.98","10.19.0.99","10.19.0.100"]
- 1
- 2
- 3
- 4
- 5
上面的配置中,兩個(gè) timeout 可能會(huì)讓人有所迷惑。這里的 fd 是 fault detection 的縮寫(xiě)。也就是說(shuō):
- discovery.zen.ping_timeout 參數(shù)僅在加入或者選舉 master 主節(jié)點(diǎn)的時(shí)候才起作用;
- discovery.zen.fd.ping_timeout 參數(shù)則在穩(wěn)定運(yùn)行的集群中,master 檢測(cè)所有節(jié)點(diǎn),以及節(jié)點(diǎn)檢測(cè) master 是否暢通時(shí)長(zhǎng)期有用。
既然是長(zhǎng)期有用,自然還有運(yùn)行間隔和重試的配置,也可以根據(jù)實(shí)際情況調(diào)整:
discovery.zen.fd.ping_interval: 10sdiscovery.zen.fd.ping_retries: 10
- 1
- 2
(本文完)
文本整理自《ELKstack權(quán)威指南》



