引言
本文主要講述在生產(chǎn)環(huán)境中使用 Elasticserch 遇到的一些坑,以及如何避開這些坑。對(duì)于正在使用或即將使用 Elasticsearch 的用戶來(lái)說(shuō)可能可做參考,以下內(nèi)容都是基于我個(gè)人的實(shí)踐經(jīng)驗(yàn)總結(jié),如有任何錯(cuò)誤或不足之處,請(qǐng)大家指正。
本文不做任何 Elasticsearch 概念和基礎(chǔ)普及,需要讀者對(duì) Elasticsearch 的概念和名詞有基礎(chǔ)的認(rèn)識(shí),每個(gè)問題都會(huì)有最佳實(shí)踐板塊,方便快速查看。大家也可先收藏,等遇到問題后再來(lái)查看。
常見坑
- Java 相關(guān)
- 分布式相關(guān)
- 集群健康度
- 節(jié)點(diǎn)診斷
- Shard 相關(guān)
- 深度分頁(yè)
Java 相關(guān)
由于 Elasticsearch 是 Java 開發(fā),Java 相關(guān)的問題如 JVM 等都是不可避免的,這里先說(shuō)說(shuō)所有人肯定會(huì)遇到的,就是 Java 的虛擬機(jī)內(nèi)存問題。
Elasticsearch 使用 ES_JAVA_OPTS 環(huán)境變量來(lái)配置 JVM。其中比較常用的配置是:
- Xms:最小堆內(nèi)存
- Xmx:最大堆內(nèi)存
這兩個(gè)值建議設(shè)置成一樣的,不超過(guò)物理機(jī)或虛擬機(jī)的 50 %,且不超過(guò) 32G。設(shè)置一樣的是減輕堆伸縮帶來(lái)堆壓力。不超過(guò) 50 % 是因?yàn)楸仨氁A(yù)留足夠的內(nèi)存給操作系統(tǒng)及其他進(jìn)程,同時(shí) JVM 本身和 Elasticsearch 的部分功能也需要。不超過(guò) 32G 是因?yàn)槌^(guò) 32G JVM 會(huì)啟用壓縮普通對(duì)象指針(compressed object pointers)(compressed oops),部分操作系統(tǒng)可能在 26G 以上就會(huì)開啟零基壓縮(zero-based compressed oops)。
最佳實(shí)踐
Elasticsearch 啟動(dòng)環(huán)境變量設(shè)置:ES_JAVA_OPTS="-Xms2g -Xmx2g",替換 2g 為使用的物理機(jī)或虛擬機(jī)內(nèi)存的一半(最低 1G,最多 32G)。
分布式相關(guān)
分布式最大的問題就是腦裂,腦裂問題簡(jiǎn)單來(lái)說(shuō)就是沒人做主。比如我們多個(gè)人一起討論問題可以使用少數(shù)服從多數(shù)來(lái)表決,但是如果是兩個(gè)人,那互不相讓就沒法決定了。分布式系統(tǒng)也是這樣,當(dāng)集群有兩個(gè)節(jié)點(diǎn)時(shí),如果因?yàn)榫W(wǎng)絡(luò)問題導(dǎo)致兩個(gè)節(jié)點(diǎn)失聯(lián),那么它們都會(huì)認(rèn)為是對(duì)方的問題(掛了),認(rèn)為自己沒問題,自己應(yīng)該變成主節(jié)點(diǎn),所以集群就變成兩個(gè)主節(jié)點(diǎn)。

如果這時(shí)候網(wǎng)絡(luò)恢復(fù)正常,而它們的數(shù)據(jù)不一致,就會(huì)變成互不相讓的局面。

而如果我們的集群有三個(gè)節(jié)點(diǎn)(A,B,C),如果 A 和 B,C 失聯(lián),那么 B 和 C 會(huì)發(fā)現(xiàn)它們都連不上 A,就會(huì)標(biāo)記 A 為失聯(lián),而它們可以選舉新的主節(jié)點(diǎn)。所以分布式系統(tǒng)一般使用 2n + 1 (n > 0)個(gè)節(jié)點(diǎn),生產(chǎn)系統(tǒng)中最少為 3 個(gè)節(jié)點(diǎn)。
最佳實(shí)踐
分布式部署節(jié)點(diǎn)數(shù)量為奇數(shù)(3,5,7 ...),且在不同的網(wǎng)絡(luò)拓?fù)渖希ɡ绻性频牟煌瑓^(qū)域或可用區(qū),機(jī)房的不同機(jī)架),同時(shí)配置彈性伸縮,在節(jié)點(diǎn)出現(xiàn)問題時(shí)自動(dòng)調(diào)節(jié)節(jié)點(diǎn)數(shù)量。這里的節(jié)點(diǎn)僅指可成為主節(jié)點(diǎn)的節(jié)點(diǎn)(配置 master: true),其他節(jié)點(diǎn)不影響。
集群健康度
Elasticsearch 的集群健康度是一個(gè)非常重要的監(jiān)控指標(biāo),是 Elasticsearch 暴露的一個(gè)整體指標(biāo),如果你只能監(jiān)控一個(gè)指標(biāo)的話,那就是它了。健康度按層級(jí)分為分片健康度、索引健康度和集群健康度,指標(biāo)分為綠、黃、紅三個(gè)等級(jí)。
分片健康度
- 紅(red):至少一個(gè)主分片沒有被分配
- 黃(yellow):至少一個(gè)副本沒有被分配
- 綠(green):主副分片都正常分配
索引健康度
索引健康度就是此索引所有分片中最差的健康度,即只要有分片是紅,索引就是紅,只有分片都是綠,索引才是綠。
集群健康度
集群健康度是此集群上所有索引中最差度健康度,即只要有索引是紅,集群就是紅,只有索引都是綠,集群才是綠。
健康度相關(guān) API
Elasticserch 提供了一系列的 API 供我們獲取健康度。
GET /_cluster/health 獲取集群的健康狀態(tài)【文檔】
GET /_cluster/health?level=indices 獲取所有索引的健康狀態(tài)
GET /_cluster/health/<index> 獲取單個(gè)索引的健康狀態(tài)
GET /_cluster/allocation/explain 返回第一個(gè)未分配分片的原因【文檔】
集群不健康排查流程
Elasticsearch 集群出現(xiàn)紅或者黃等不健康狀態(tài)是很常見等問題,當(dāng)集群出現(xiàn)不健康狀態(tài)時(shí)(紅或者黃),我們需要一步步找到問題的原因再修復(fù)。
首先,定位問題,可以通過(guò)上述的 API 查看不健康狀態(tài)的原因。 比較常見的問題原因主要有:
- 節(jié)點(diǎn)離線導(dǎo)致的分片無(wú)法分配
- 索引配置、分片規(guī)則等問題導(dǎo)致的分片無(wú)法分配
- 磁盤空間不足
在定位問題原因之后,我們就可以根據(jù)情況確定解決方案。在官網(wǎng)的這個(gè)頁(yè)面(https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-shards.html)可以看到 Elasticsearch 分片無(wú)法分配的錯(cuò)誤,下面我們來(lái)講講一些錯(cuò)誤的解決辦法。
- 節(jié)點(diǎn)離線后,由于主副分片不能位于同一節(jié)點(diǎn)上,可能導(dǎo)致分片分配失敗,解決辦法就是增加節(jié)點(diǎn),一般對(duì)于生產(chǎn)集群,我們都會(huì)根據(jù)情況保留一定的節(jié)點(diǎn)數(shù)量,在節(jié)點(diǎn)離線時(shí)及時(shí)啟動(dòng)新的節(jié)點(diǎn)加入集群。
- 對(duì)于索引配置,分配規(guī)則等問題導(dǎo)致的無(wú)法分配,如沒有滿足規(guī)則的節(jié)點(diǎn),只有一個(gè)測(cè)試節(jié)點(diǎn),但是默認(rèn)配置來(lái)一個(gè)主副分片等。我們就要根據(jù)實(shí)際情況來(lái)修改配置或規(guī)則,對(duì)于測(cè)試環(huán)境,刪除重建是最快的方法,而對(duì)于生產(chǎn)環(huán)境,就需要結(jié)合實(shí)際情況來(lái)考慮了。
- 對(duì)于磁盤空間不足的問題,需要擴(kuò)充磁盤或者將索引遷移到其他節(jié)點(diǎn)上。
- DANGLING_INDEX_IMPORTED:當(dāng)集群中的某個(gè)節(jié)點(diǎn)離線后進(jìn)行了索引的刪除,然后這個(gè)節(jié)點(diǎn)又回到集群,就會(huì)發(fā)生這個(gè)錯(cuò)誤,主要是索引的數(shù)據(jù)不一致導(dǎo)致的,可以重新在刪除已被刪除的索引即可。
- EXISTING_INDEX_RESTORED:當(dāng)集群中的某個(gè)索引被關(guān)閉(關(guān)閉不等于刪除),又恢復(fù)到這個(gè)索引就會(huì)引發(fā)這個(gè)問題,需要把索引刪除后再恢復(fù)。
同時(shí),需要注意的是,在分片重新分配的過(guò)程中,會(huì)出現(xiàn)短暫的不健康狀態(tài),在監(jiān)控時(shí)需要設(shè)置合理的告警閾值,避免過(guò)于敏感。
節(jié)點(diǎn)診斷
節(jié)點(diǎn)是從集群的物理機(jī)或者虛擬機(jī)層面來(lái)看,雖然 Elasticsearch 認(rèn)為節(jié)點(diǎn)都是不可靠的,當(dāng)節(jié)點(diǎn)出現(xiàn)問題后,我們會(huì)用一些方法保證集群的可靠。但是我們不可能在節(jié)點(diǎn)出現(xiàn)問題后,只是簡(jiǎn)單粗暴地更換節(jié)點(diǎn),還是需要一些方法來(lái)確保節(jié)點(diǎn)基本沒有問題。
節(jié)點(diǎn)診斷相關(guān) API
GET /_cat/nodes?v 查看節(jié)點(diǎn)到基本信息即負(fù)載情況【文檔】
GET /_nodes/stats/indices 查看節(jié)點(diǎn)的索引詳情【文檔】
POST /_cache/clear 清除節(jié)點(diǎn)緩存【文檔】
節(jié)點(diǎn)內(nèi)存問題
Elasticsearch 集群節(jié)點(diǎn)最有可能發(fā)生的就是內(nèi)存問題,Java 的長(zhǎng)時(shí)間 GC 等可能導(dǎo)致的集群變慢,產(chǎn)生 OOM(Out Of Memory),甚至是節(jié)點(diǎn)離線。下面我們來(lái)講講這些問題的產(chǎn)生原因及解決辦法。
首先,我們也需要通過(guò)上述的 API 及節(jié)點(diǎn)的監(jiān)控找到問題的原因,比較常見的內(nèi)存問題主要有:
緩存占用過(guò)多內(nèi)存,如 Segment 占用過(guò)多內(nèi)存可能是由于頻繁寫入導(dǎo)致產(chǎn)生了很多零散的 Segment,可以使用 Force Merge API 將它們合并為一個(gè)。如果使用了 FieldData 且沒有限制 FieldData 緩存的上限,可能因?yàn)橄亩啻髮?dǎo)致頻繁 GC。FieldData 是針對(duì) Text 類型做排序和聚合使用的(像 Keyword 一樣),一般不推薦使用(https://www.elastic.co/guide/en/elasticsearch/reference/7.5/modules-fielddata.html)。
大量復(fù)雜的嵌套聚合也可能引發(fā)頻繁 GC,因?yàn)榍短拙酆蠒?huì)在內(nèi)存中生成大量的 Bucket 對(duì)象,生產(chǎn)環(huán)境中應(yīng)該盡可能避免復(fù)雜的嵌套聚合查詢。
如果無(wú)法完全限制請(qǐng)求的查詢,Elasticsearch 提供了斷路器的功能,用戶可以根據(jù)需求配置相應(yīng)規(guī)則,只要滿足規(guī)則,請(qǐng)求就會(huì)熔斷,從而保護(hù)集群,避免生產(chǎn) OOM 問題。配置也很簡(jiǎn)單,這里就不展開,詳細(xì)可以查看官方文檔(https://www.elastic.co/guide/en/elasticsearch/reference/7.5/circuit-breaker.html)。
最佳實(shí)踐
對(duì)于頻繁寫入的索引,需要定期監(jiān)控節(jié)點(diǎn)內(nèi)存,必要時(shí)使用 Force Merge。 對(duì)于復(fù)雜查詢與聚合,要在業(yè)務(wù)端控制查詢請(qǐng)求,如無(wú)法限制,則可使用斷路器。斷路器只是最后對(duì)集群的保護(hù),如果用戶的查詢請(qǐng)求總是消耗過(guò)多的資源而被斷路器中斷,用戶可能頻繁嘗試且獲取不到結(jié)果,對(duì)用戶體驗(yàn)不好,后臺(tái)壓力也不小,還是需要業(yè)務(wù)端及時(shí)調(diào)整和控制。
Shard 相關(guān)
Elasticsearch 把一個(gè)索引切分成多個(gè) Shard 來(lái)存儲(chǔ),將一份大的數(shù)據(jù)切分成多個(gè)部分來(lái)存儲(chǔ)可以提高查詢效率,也便于將數(shù)據(jù)分布式存儲(chǔ)。不過(guò)每個(gè)索引的 Shard 數(shù)量是固定的,必須在創(chuàng)建時(shí)指定,后期修改必須要重建索引。分片的數(shù)量和大小都需要根據(jù)實(shí)際情況調(diào)整,一個(gè)分片實(shí)際上是一個(gè) Lucene 索引,過(guò)多的分片會(huì)導(dǎo)致額外的性能開銷,同時(shí)過(guò)多的分片也會(huì)導(dǎo)致聚合不準(zhǔn)的問題(https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-aggregations-bucket-terms-aggregation.html)。一般來(lái)說(shuō),單個(gè) Shard 的建議最大大小是 20G 到 50G,對(duì)于普通搜索類數(shù)據(jù),最好控制在 20G,而對(duì)于時(shí)間序列類數(shù)據(jù)(如日志)最好控制在 50G。如果每天的日志量無(wú)法預(yù)估,可以使用 Rollover API 進(jìn)行自動(dòng)的索引生命周期管理(https://www.elastic.co/guide/en/elasticsearch/reference/7.5/using-policies-rollover.html)。而單個(gè)節(jié)點(diǎn)的總數(shù)據(jù)量最好在 2 TB 以內(nèi)。
因?yàn)?Shard 直接關(guān)乎集群健康度,所以對(duì)于生產(chǎn)集群來(lái)說(shuō),這個(gè)設(shè)置尤為重要,我們也不希望因?yàn)樵O(shè)置的問題導(dǎo)致集群變黃或紅。
Shard 操作相關(guān) API
GET /_cat/shards/<index> 查看 Shard 信息【文檔】
POST /<alias>/_rollover/<target-index> 當(dāng)索引滿足某些條件時(shí)(如數(shù)據(jù)量太大)自動(dòng)切到新的索引,非常適合無(wú)法預(yù)估大小的時(shí)間序列類索引【文檔】
POST /<index>/_forcemerge 強(qiáng)制合并索引數(shù)據(jù)【文檔】
POST /<index>/_shrink/<target-index> 新建索引并減少主分片數(shù)量【文檔】
POST /<index>/_split/<target-index> 新建索引并增加主分片數(shù)量【文檔】
POST /_reindex 重建索引【文檔】
這些 API 的使用需要有很多限制條件,具體大家可以查看各個(gè) API 的文檔,大家可以提前了解下避免出現(xiàn)使用時(shí)用不了的尷尬。
最佳實(shí)踐
分片的數(shù)量和大小都需要根據(jù)實(shí)際情況調(diào)整,一般來(lái)說(shuō),單個(gè) Shard 的建議最大大小是 20G 到 50G,對(duì)于普通搜索類數(shù)據(jù),最好控制在 20G,而對(duì)于時(shí)間序列類數(shù)據(jù)(如日志)最好控制在 50G。如果每天的日志量無(wú)法預(yù)估,可以使用 Rollover API 進(jìn)行自動(dòng)的索引生命周期管理。而單個(gè)節(jié)點(diǎn)的總數(shù)據(jù)量最好在 2 TB 以內(nèi)。
深度分頁(yè)
Elasticsearch 有三種分頁(yè)方式。
from + size 參數(shù)
from 定義數(shù)據(jù)偏移量,size 定義獲取數(shù)據(jù)量,from 和 size 的方式類似 SQL 的 offset 和 limit,很好理解,但是在分頁(yè)量大時(shí)(深度分頁(yè))效率很差。因?yàn)檫@會(huì)從每個(gè)分片獲取適量的數(shù)據(jù),在查詢節(jié)點(diǎn)上緩存數(shù)據(jù)。例如 from=1000,size=10,每個(gè)分片會(huì)排序上報(bào) 1001 * 10 條數(shù)據(jù),查詢節(jié)點(diǎn)會(huì)歸并排序所有結(jié)果,并返回第 1001 到 1010 條數(shù)據(jù)。由于這個(gè)原因,當(dāng)取的頁(yè)碼很大時(shí),緩存節(jié)點(diǎn)的效率會(huì)很差,所以 Elasticsearch 會(huì)限制最大的排序數(shù)據(jù)(配置 index.max_result_window,默認(rèn)是 10000)。這種方式適合測(cè)試與小范圍的分頁(yè)。
優(yōu)點(diǎn):簡(jiǎn)單好用
缺點(diǎn):不適用深度分頁(yè)
總結(jié):適用于簡(jiǎn)單分頁(yè)場(chǎng)景,不適用于大量數(shù)據(jù)的遍歷
Scroll API
Scroll 類似 SQL 數(shù)據(jù)庫(kù)的服務(wù)端游標(biāo),通過(guò)查詢時(shí)指定 ?scroll=<時(shí)間> 參數(shù)使用,返回的結(jié)果會(huì)帶上 scroll_id,下次獲取時(shí)只需要用 scroll_id 查詢即可,同時(shí)延長(zhǎng)緩存時(shí)間。
POST /<index>/_search?scroll=1m
{
"size": 100,
"query": {
"match" : {
"title" : "elasticsearch"
}
}
}
POST /_search/scroll
{
"scroll" : "1m",
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
其實(shí)這就在第一次查詢后建立一個(gè)查詢快照(Search Context),接下來(lái)就通過(guò) scroll_id 獲取之前的查詢快照,而無(wú)需再次查詢。一看這個(gè)原理我們也可以知道,每個(gè)快照必須占用一定的內(nèi)存,且快照創(chuàng)建后無(wú)法感知新的數(shù)據(jù)變更。同時(shí),由于 Elasticsearch 在數(shù)據(jù)合并時(shí)會(huì)刪除小的 segments,創(chuàng)建大的 segments,而 Scroll 的快照如果在使用這些舊的 segments,它們會(huì)避免被刪除,這也會(huì)造成資源耗費(fèi)。
優(yōu)點(diǎn):適合深度分頁(yè),在一定時(shí)間內(nèi)緩存結(jié)果,效率高
缺點(diǎn):只能順序單向翻頁(yè),超過(guò)緩存時(shí)間后失效,如果開啟了多個(gè)翻頁(yè)會(huì)耗費(fèi)較多內(nèi)存,需要主動(dòng)關(guān)閉,快照創(chuàng)建后無(wú)法感知變更
總結(jié):不適用于用戶深度分頁(yè),適合批量數(shù)據(jù)遍歷與處理
Search After API
Search After API 用前一次的結(jié)果作為下一次的查詢條件,在查詢體中使用 search_after 參數(shù),同時(shí) from 參數(shù)必須是 0 或者 -1。這種方式必須提供 sort 排序且排序唯一(如排序最后加入 _id),如 size=10,那么每個(gè)分片只要根據(jù)排序規(guī)則獲取 10 條數(shù)據(jù)返回給查詢節(jié)點(diǎn)處理即可。和 Scroll API 一樣可以適用于深度分頁(yè),但 Search after 方式是無(wú)狀態(tài)的,無(wú)需保存查詢快照。
GET <index>/_search
{
"size": 10,
"query": {
"match" : {
"title" : "elasticsearch"
}
},
"search_after": [1463538857, "654323"],
"sort": [
{"date": "asc"},
{"_id": "asc"}
]
}
優(yōu)點(diǎn):適用于深度分頁(yè),且分頁(yè)無(wú)狀態(tài)
缺點(diǎn):只能順序單向翻頁(yè),且需要唯一的 sort 排序
總結(jié):適用于用戶的單向深度分頁(yè)
最佳實(shí)踐
對(duì)于普通用戶查詢場(chǎng)景,使用 from + size 方式,對(duì)于用戶順序遍歷(載入更多數(shù)據(jù),無(wú)頁(yè)碼),使用 Search After API,對(duì)于內(nèi)部數(shù)據(jù)處理或?qū)С?,使?Scroll API。
實(shí)用利器
最后分享一些我經(jīng)常使用的工具,用好了絕對(duì)事半功倍。
Kibana:這個(gè)相信大家都用過(guò),Elasticsearch 最好的 UI 和可視化,功能強(qiáng)大,開發(fā)測(cè)試使用的 Dev Tools 尤其好用。
Cerebro:這個(gè)是 Elasticsearch 集群的可視化監(jiān)控工具,優(yōu)點(diǎn)是將集群健康狀態(tài),分片分布,節(jié)點(diǎn)狀態(tài)等可視化展示,便于操作。缺點(diǎn)是沒有認(rèn)證管理,生產(chǎn)環(huán)境如果沒有其他訪問控制手段很危險(xiǎn)。下圖盜自百度。

Elasticsearch Curator:Elastic 官方出品的命令行運(yùn)維管理工具,主要是將一些常用的管理 API 包裝成命令,便于使用和自動(dòng)化運(yùn)維,如定期刪除過(guò)期的日志索引等(需要結(jié)合定時(shí)器觸發(fā))。
這些工具的使用這里就不展開了,如果大家想了解的話就評(píng)論告訴我,我再具體展開。
先寫這些,以后再補(bǔ)充...
參考
- https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html
- https://www.elastic.co/guide/en/elasticsearch/reference/7.5/using-policies-rollover.html
- https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-shards.html
- https://www.elastic.co/guide/en/elasticsearch/reference/7.5/modules-fielddata.html
- https://www.elastic.co/guide/en/elasticsearch/reference/7.5/circuit-breaker.html
- https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-request-body.html#request-body-search-from-size
- https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-request-body.html#request-body-search-scroll
- https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-request-body.html#request-body-search-search-after
- https://github.com/elastic/kibana
- https://github.com/lmenezes/cerebro
- https://github.com/elastic/curator