作者 | 蔡柱梁
審校 | 重樓
目錄
精通ES,這一篇就夠了
目標
前言
1 ES 的分布式設計
1.1 集群添加節點和移除節點
2 ES 的讀寫
2.1 文檔
2.2 一個文檔寫入到一個分片中
2.3 使文本可被搜索
2.3.1 動態更新索引
2.3.2 準實時搜索
2.3.3 持久化變更
2.3.4 段合并
2.4 取回文檔(讀文檔)
2.4.1 取回單個文檔
2.4.2 分布式查詢
2.4.3 深度分頁
3 管理和部署
3.1 如何合理設置分片數
3.2 擴容
3.3 推遲分片分配
3.4 部署
3.4.1 上生產前應該要注意的事項
3.4.2 滾動重啟
3.4.3 備份集群
3.4.4 從快照恢復
4 總結
作者介紹
目標
1.了解 ES 的分布式設計
2.了解 ES 的讀寫
3.了解 ES 的管理和部署
前言
本文圖片大多來自官網,其余為作者作圖。ES 的官網其實介紹的很詳細,但是很少人將它整理出來。我這里整理了出來,并加上了自己的看法,希望可以和大家一起深入學習ES。
參考文檔:??Elasticsearch 7.17??
1 ES 的分布式設計
ES 天生就是分布式的,并且在設計時屏蔽了分布式的復雜性,可以橫向擴展至數百(甚至數千)的服務器節點,同時可以處理PB級數據。
我們先來了解可以處理的一些事情:
- 分配文檔到不同的容器或分片中,文檔可以儲存在一個或多個節點中
- 按集群節點來均衡分配這些分片,從而對索引和搜索過程進行負載均衡
- 復制每個分片以支持數據冗余,從而防止硬件故障導致的數據丟失
- 將集群中任一節點的請求路由到存有相關數據的節點
- 集群擴容時無縫整合新節點,重新分配分片以便從離群節點恢復接下來,我們來看下集群中一些重要的概念:
- 分片
分片是 ES 實際存儲數據的地方,我們都知道 ES 是使用 Java 語言實現的,而它的搜索的底層是基于 Lucene 實現的,而每一個分片就是一個 Lucene 實例。在 Elasticsearch 6.x 以及之前的版本,每一個索引創建時默認是 5 個分片的,在 Elasticsearc 7.0 開始默認是 1 個分片,即:
- 節點角色(這里列出一部分角色,更多請參考:??Elasticsearch-Node??)
– master
主節點是集群中的控制節點,主要負責管理和維護集群的元數據(如索引、映射、分片分配等),并且負責選舉和維護集群中的主分片。每個 ES 集群只有一個 Master 節點,但是如果 Master 節點宕機,集群會通過選舉機制重新選舉一個新的 Master 節點。
– data
數據節點持有包含你所索引的文檔的分片。數據節點處理與數據有關的操作,如CRUD、搜索和聚合。這些操作是I/O、內存和CPU密集型的。監控這些資源是很重要的,如果它們過載了,可以增加更多的數據節點。擁有專用數據節點的主要好處是 master 和 data 角色分離。
– Coordinating node
協調節點是一種特殊的節點類型,它主要負責協調集群中的各個節點之間的通信和任務分配,不參與數據的存儲和處理。需要注意的是,協調節點本身不存儲數據,因此它們可以在任何節點上運行(即任何節點都可以成為協調節點),并且可以隨時添加或刪除。通過將協調節點與數據節點分開,可以實現更好的性能和可伸縮性,同時保證集群的穩定性和可靠性,但是不建議過多。因為 master 節點需要維護集群中的節點,如果數量過多,會給 master 造成過多的負擔,從而影響性能。具體作用如下:
1.路由請求
協調節點負責處理集群中的路由請求,即將請求路由到正確的節點上進行處理。例如,當一個搜索請求被發送到集群時,協調節點會確定哪些分片需要被搜索,并將請求路由到包含這些分片的節點上進行處理。
2.分配任務
協調節點負責分配各種任務到集群中的節點上進行處理,例如分片的重新分配、節點的加入和移除等。
3.監控集群
協調節點負責監控集群中各個節點的狀態和健康情況,并對集群中的故障進行處理。例如,當一個節點宕機時,協調節點會負責將該節點上的分片重新分配到其他節點上。
- 管理索引和映射(這里和 master 不同,master 是維護元數據,而這里管理的是行為)
協調節點負責管理集群中的索引和映射,例如創建和刪除索引、修改映射等。
- Transport client(傳輸客戶端)
輕量級的傳輸客戶端可以將請求發送到遠程集群。它本身不加入集群,但是它可以將請求轉發到集群中的一個節點上。
- Node client(節點客戶端)
節點客戶端作為一個非數據節點加入到本地集群中。換句話說,它本身不保存任何數據,但是它知道數據在集群中的哪個節點中,并且可以把請求轉發到正確的節點。
1.1 集群添加節點和移除節點
當配置了同樣的cluster.name: my-cluster的節點被發現后或者被移出集群,集群會對分片進行再平衡。
1)假設當前我集群中只有一個節點,該節點只有一個 index,設置了 3 個分片,具體如下:
這時副分片是沒有意義的,所以只有主分片,假設目前有三個分片,如下圖:

2)假如此時,我添加了一個節點,集群將會為這些主分片創建副本,并分開存放(高可用,需要做到備份容災,主分片與自己的備份不應該在同一節點),
假設只有一個副分片,如下圖:

3)我們搭建最小集群,節點個數應該為 3 個(防止網絡分區問題)。因此,再增加一個節點,分片將發生再平衡

4)這時 node 1 失聯了,重現選主 node 2 作為 master 節點,分片也會發生再平衡

2 ES 的讀寫
ES 的讀寫,其實就是創建文檔和讀取文檔的操作。因此,要了解這些操作,我們就要先了解文檔。
2.1 文檔
下面了解一些關于文檔的概念:
- 索引文檔
所謂的索引文檔就是指 通過使用 index API ,文檔可以被 索引 —— 存儲和使文檔可被搜索。使文檔可以被搜索,其實就是構建倒排索引。
- 文檔ID
ES 的每一個文檔都有一個 ID,這個 ID 要么就是自己創建文檔時提供,要么讓 ES 自己生成,并且 index_name + id 可以確定唯一的文檔(如果是 ES8 之前,則是 index_name + type + id 確定唯一的文檔)。
- 版本號
在 ES 中每個文檔都有一個版本號,每當對文檔進行修改時(包括刪除),版本號的值就會遞增,版本號用于處理并發沖突的場景。
上面提到了刪除文檔時,版本號也會遞增,這是為什么呢?
因為在 ES 中,文檔是不可變的,不能修改它們。因此,如果我們需要對一個文檔進行修改時,那只能 重建索引 或者 進行替換。而刪除只是更新文檔的一種特殊情況而已。不管對文檔進行修改還是刪除,版本號遞增后,我們只能讀取到最新的文檔。舊的文檔會被標記為已刪除,但是不會被馬上物理刪除。
修改文檔的過程:
- 從舊文檔構建
- 更改該
- 刪除舊文檔
- 索引一個新文檔
2.2 一個文檔寫入到一個分片中
一個文檔寫入到一個分片中,又可以稱為路由一個文檔到一個分片。在說路由之前,我們先梳理下節點,分片,index,文檔之間的關系:
- index 是具有同一特征的文檔的集合
- 我們在創建 index 時需要指定分片數
- 一個分片就是一個 Lucene 實例,而 Lucene 實例運行在 ES 節點上,一個節點允許有多個分片
下圖表示一個擁有 3 個節點的小集群,一共有 2 個 index,每個 index 有 3 個分片(指的是有 3 個主分片,每個主分片有 1 個副本)。

假設有一個集群由三個節點組成。 它包含一個叫blogs的索引,有兩個主分片,每個主分片有兩個副本分片,具體如下(這是官網的例子):

假設現有要寫入一個文檔到 blogs,那么這個過程是怎樣的呢?
首先要知道只有主分片具有處理寫請求的能力,副本只提供讀的能力,然后才是路由一個文檔到一個主分片中存儲起來。
至于是怎么路由的,請看下面的路由公式:
shard = hash(routing) % number_of_primary_shards
- routing
routing 默認是文檔 ID,也可以設置成一個自定義的值
- number_of_primary_shards
number_of_primary_shards 指的是主分片的數量
這也是為什么我們確定好 index 的分片數后不能修改的原因,如果我們需要擴容,只能通過重建索引等手段來進行水平擴容了。
新建、索引(構建倒排索引)和刪除 請求都是 寫 操作, 必須在主分片上面完成之后才能被復制到相關的副本分片,如下圖所示:

具體步驟如下:
- 客戶端向任意節點發出寫請求(圖示例寫著請求的是 node 1)
- node 1 通過路由算法確定文檔應該分配到分片 P0 上,隨后將請求轉發到 node 3
- 主分片 P0 完成寫請求后,轉發請求給 node 1 和 node 2,讓它們的副本分片 R0 完成數據同步
- 等到所有副本報告同步成功后,node 3 將向協調節點報告成功(這里的協調節點就是 node 1),協調節點向客戶端報告成功
2.3 使文本可被搜索?
上面說了一個文檔寫入的流程,但是文檔是一下就寫到磁盤嗎?是一寫入就立馬可以被搜索嗎?這些問題會在這一節中給出答案。
我們知道最早開始學全文搜索查詢到對應的信息。ES 也是如此,那么如何實現全文搜索呢?前面也提到了 ES 采用的是倒排索引,我在《ES入門》有寫倒排索引的原理,這里就不累述了。
當討論倒排索引時,我們會談到 文檔 標引,因為歷史原因,倒排索引被用來對整個非結構化文本文檔進行標引。 ES 中的 文檔 是有字段和值的結構化 JSON 文檔。事實上,在 JSON 文檔中, 每個被索引的字段都有自己的倒排索引。
早期的全文檢索會為整個文檔集合建立一個很大的倒排索引并將其寫入到磁盤。一旦新的索引就緒,舊的就會被其替換,這樣最近的變化便可以被檢索到。倒排索引被寫入磁盤后是不可改變的(我們稱之為倒排索引具有不變性)。這個特性的好處有:
- 不需要鎖
不變性意味著不怕高并發,所以不需要鎖。
- 極大提高了 ES 的性能
一旦索引被讀入內核的文件系統緩存,便會一直在那。由于不變性,只要緩存還有足夠的空間,那么大部分請求會直接請求內存。這就很大程度上提升了性能。
- 其他緩存(如 filter)在索引生命周期內始終有效
因為數據是不會變的(索引都沒變,數據當然沒變)。
- 減少 I/O 和需要被緩存到內存的索引的使用量
因為不變性,所以可以在對單個大的倒排索引寫入時進行數據壓縮。
當然,有好處,就會有壞處:我要新增一個文檔,就需要重新構建一個新的索引,因為舊的索引是不可變的。這種設計會對索引包含的數據量和可被更新的頻率有極大的限制。
2.3.1 動態更新索引
動態更新索引指的是增量操作文檔后,會對索引進行補充索引來反映新的修改。
上面說了倒排索引的不變性的好處與壞處,那么如何在保留不變性的前提下實現倒排索引的更新呢?答案是:用更多的索引。
具體方法如下:
當發生了一些操作導致需要更新倒排索引,ES 不會直接更新索引,而是通過增加新的補充索引來反映最新的修改(也就是所謂的用更多索引)。那么要查詢時,關于這個 index 的索引這么多,怎么用呢?也簡單,就是按創建時間(升序)來遍歷這些索引查詢文檔,最后對查詢結果進行合并。
ES 基于上面的理論,引入了 按段搜索的概念,每一個段都是一個倒排索引,但是索引在段的集合外,還有一個概念提交點。在提交點內的段才是可見的,它包含的文檔才是可被搜索的。
到這里,很容易將文檔的集合——index(索引) 和 倒排索引(索引,分片中的段)搞混,也不知道它們與分片(Lucene 實例)是一個怎樣的關系。因此,這里有必要再梳理下它們的關系:一個 index可以有多個分片,而一個分片中也可以有多個段用于搜索文檔。
下面用一個例子說明動態更新索引是如何在保留倒排索引的不變性的同時實現索引更新,從而讓新增的文檔可被搜索的:
1)假設現在有一個 Lucene 索引當前包含了一個提交點和三個段

2)現在我們新增了一個新的文檔。因為之前的索引是不變的,所以 ES 會進行補充索引,具體如下:
2.1)新文檔被收集到內存索引緩存

2.2)不時地提交緩存
完整的提交緩存流程如下:
- 在磁盤上寫入一個追加的新段(也就是上面說的補充索引)
- 在磁盤上寫入一個新的提交點,該提交點包含新的段
經過上面兩步后得到下圖:

- 所有在文件系統緩存中等待的寫入都刷新到磁盤,以確保它們被寫入物理文件(通過 fsync 來 flush,這樣在斷電的時候就不會丟失數據)
2.3)新的段被開啟,讓它包含的文檔可以被搜索
2.4)內存緩存被清空,等待接收新的文檔

上面說了新增文檔導致索引更新的情況,那么修改和刪除文檔呢?
因為段是不變的,所以文檔變更導致的索引變化是沒法在舊索引體現的。因此,每個提交點都會有一個 .del 文件,這個文件記錄著這些過時的文檔。不管是修改還是刪除都會被標記成刪除,但是沒有真的從磁盤上刪掉。當我們遍歷段時,這些文檔其實還是會被檢索出來的,只是 ES 做結果合并時會過濾這部分被標記為刪除的文檔。
2.3.2 準實時搜索
ES 從數據寫入到可被搜索不是實時的,而是有一定時延的,所以無法接受這一點的場景可能就不適合使用 ES 了。
2.3.1 說到了動態更新索引 是如何在保留不變性的前提下更新索引,讓新的文檔可被搜索的,但是這方案中的提交階段需要 fsync 來確保段被物理性寫入磁盤。如果每一次索引一個文檔都要執行 fsync 的話,那么會對性能造成很大的影響。在 ES 和磁盤之間是文件系統緩存,為了提升性能, ES 做了一些列優化使得 ES 可以近實時搜索。
下面用一個例子說明 ES 的優化(還是用 “有一個 Lucene 索引當前包含了一個提交點和三個段” 這個前提):
1)新文檔被寫入了內存索引緩存中,如下圖:

2)新文檔被寫入一個新的段中,該段被寫入了文件系統緩存(即只是寫入磁盤,但是沒有用 fsync 來 flush)。Lucene 允許這個新段被寫入和打開,使該段可以被搜索。

上面提到的 寫入和打開一個新段的過程 就是 refresh,ES 提供 refresh API,我們可以手動觸發,不手動觸發的話,默認是每個分片每秒自動刷新一次。因此,我們說 ES 是準實時搜索。將 “使新的段可以被搜索” 拆得更細一點的話,具體步驟如下:
- 新文檔被寫入一個新的段中
- 該段被寫入文件系統緩存并打開(refresh)
- 同步數據到副本分片(如果有的話)
- 更新集群狀態(如果是集群的話)
master更新集群元數據
下面是對上面這些過程,幫助提升性能的一些思路:
- refresh 的時間
合理調整 refresh 頻率或者使用讀寫性能更強大的 SSD 硬盤。
- 同步數據的時間
同步數據受網絡,物理設備的狀態,集群狀態,副分片數量等因素影響,可以從這幾個角度考慮提升。
- master更新集群元數據的時間
這個受集群的大小和復雜程度影響,所以要合理搭建集群。
2.3.3 持久化變更
2.3.2說到為了實現準實時搜索完全拋棄了2.3.1也提到了動態更新索引最近一次完整的提交會將段刷到磁盤,但是這個提交點之后的操作呢?無法保證了,這樣就可能會出現數據丟失。
為了保證 ES 的可靠性,需要確保數據變化被持久化到磁盤,為此 ES 增加了一個 translog(事務日志,類似 MySQL 的 redo 日志)。整個流程如下:
1)一個新文檔被索引之后,就會被添加到內存緩沖區,并且相應的文檔操作被寫入到 translog(順序IO)

2)refresh(分片每秒被 refresh 一次),具體操作如下:
- 內存緩沖區中的文檔被寫到一個新段中,且沒有進行 fsync
- 新段被打開,使新段可以被搜索
打開新段:
- 將新段加入到已有段的文件列表中,并且建立該段的倒排索引、文檔存儲和其他元數據
- 更新索引的內存結構,包括倒排索引、字段信息、文檔數和大小等
- 內存緩存區被清空

因為索引刷新會觸發 Lucene 的 SegmentReader 和 SegmentSearcher 的更新操作,使得新段可以被加入到搜索中,所以新段在刷新操作后可以被搜索。
3)這個進程繼續工作,更多的文檔被添加到內存緩沖區和追加到事務日志

4)不定時對索引 flush(分片每 30 分鐘被 flush一次或者 translog 過大的時候也會觸發),段被全量提交,并且事務日志被清空,具體操作如下:
- 內存緩沖區中的所有文檔都被寫入一個新段
- 緩沖區被清空
- 一個提交點被寫入磁盤
- 文件系統緩存通過 fsync 被 flush
- 老的 translog 被刪除

translog 提供所有還沒有被刷到磁盤的操作的一個持久化記錄。當 ES 啟動的時候, 它會從磁盤中使用最后一個提交點去恢復已知的段,并且會重放 translog 中所有在最后一次提交后發生的變更操作。
這種執行一個提交并且截斷 translog 的行為在 ES 中被稱為一次 flush。分片每 30 分鐘被自動 flush 一次或者 translog 太大的時候也會觸發。具體可看 Translog。我們也能手動觸發 flush,具體請看 Flush API。
2.3.4 段合并
段合并的時候會將那些舊的已刪除文檔從文件系統中清除,被刪除的文檔(或被更新文檔的舊版本)不會被拷貝到新的大段中。
默認每秒一次刷新,會導致段的數量在短時間能激增,但是如果降低 refresh 的頻率,對于一些實時性要求較高的場景又不能滿足。而且,段數量過多也會造成各種各樣的問題:每一個段都會消耗文件句柄、內存和CPU運行周期;更重要的是,每個搜索請求都必須輪流檢查每個段;所以段越多,搜索也就越慢。ES 通過在后臺進行 段合并 來解決這個問題。啟動 段合并 不需要我們做任何事,ES 進行索引和搜索時會自動進行。
假設現在要進行段合并了:
1)合并進程選擇大小相似的小段并且將他們合并到更大的段中(這樣就不會中斷索引和搜索)。如下圖所示:

2)一旦合并結束,老的段被刪除

合并大的段需要消耗大量的 I/O 和 CPU 資源,如果任其發展會影響搜索性能。ES 在默認情況下會對合并流程進行資源限制,所以搜索仍然有足夠的資源很好地執行。
?2.3.4.1 段合并建議
2.3.4 末尾說到了 “合并大的段需要消耗大量的 I/O 和 CPU 資源”,但是合并是在后臺定期操作的。因為這些操作可能要很長時間才能完成,尤其是比較大的段。這個通常來說都沒問題,因為大規模段合并的概率是很小的。不過有時候合并會拖累寫入速率,如果這個真的發生了,ES 會自動限制索引請求到單個線程里。這樣可以防止出現 段爆炸 問題(即數以百計的段在被合并之前就生成出來)。
ES 默認設置在這塊比較保守:不希望搜索性能被后臺合并影響。不過有時候(尤其是 SSD,或者日志場景)限流閾值太低了。默認值是 20 MB/s,對機械磁盤應該是個不錯的設置。
- 如果服務器用的是 SSD,可以考慮提高到 100–200 MB/s。測試驗證對系統哪個值比較合適:
- 如果在做批量導入,完全不在意搜索,那么可以徹底關掉合并限流。這樣讓索引速度跑到你磁盤允許的極限:
最后,可以增加 index.translog.flush_threshold_size 設置,從默認的 512 MB 到更大一些的值,比如 1 GB。這可以在一次清空觸發的時候,在事務日志里積累出更大的段。而通過構建更大的段,清空的頻率變低,大段合并的頻率也變低。這一切合起來導致更少的磁盤 I/O 開銷和更好的索引速率。當然,這會需要對應量級的 heap 內存用以積累更大的緩沖空間,調整這個設置的時候請記住這點。
不過一般對于中小公司的業務或者試探性的業務建議用默認設置就好了,否則自己不是特別熟悉 ES 的情況下,容易讓 ES 性能下降又找不出原因。
2.3.4.2 科學的測試性能
2.3.4.1 有些設置給出的值是經驗值,但不適用所有公司。因此,真的要改默認設置的話,性能測試很重要。下面給出一些方法論,如下:
- 在單個節點上,對單個分片,無副本的場景測試性能。
- 在 100% 默認配置的情況下記錄性能結果,這樣就有了一個對比基線。
- 確保性能測試運行足夠長的時間(30 分鐘以上),這樣可以評估長期性能,而不是短期的峰值或延遲。
一些事件(比如段合并,GC)不會立刻發生,所以性能概況會隨著時間繼續而改變的。
- 開始在基線上逐一修改默認值。
嚴格測試需要修改的配置,如果性能提升可以接受,保留這個配置項,開始下一項。
2.4 取回文檔(讀文檔)
關于讀請求需要知道:
- 在處理讀取請求時,協調結點在每次請求的時候都會通過輪詢所有的副本分片來達到負載均衡。
- 在文檔被檢索時,已經被索引的文檔可能已經存在于主分片上但是還沒有復制到副本分片。在這種情況下,副本分片可能會報告文檔不存在,但是主分片可能成功返回文檔。一旦索引請求成功返回給用戶,文檔在主分片和副本分片都是可用的。
2.4.1 取回單個文檔
通過文檔 ID 查詢文檔。
以下是從主分片或者副本分片檢索文檔的步驟順序(官網例子):
- 客戶端向某個節點(假設現在是 Node 1) 發送獲取請求。
- 節點使用文檔的 _id 來確定文檔屬于哪個分片。
假設是屬于分片0,并應該請求 Node 2 的 R0,這時會將請求轉發到 Node 2。
- Node 2 將文檔返回給 Node 1,然后將文檔返回給客戶端。
下面是上面步驟的流程圖:

2.4.2 分布式查詢
我們大多數時候都不是根據文檔 ID 查詢文檔,而是根據某些條件篩選做聚合查詢。這時,我們就需要了解下分布式查詢是怎樣處理請求的了。整體上分為兩個階段:查詢階段 和 取回階段。
2.4.2.1 查詢階段
協調節點向各個分片發出查詢請求,每個分片(可能是主分片,也可能是副分片,但是不會重復分片)查詢出自己的結果并返回給協調節點,協調節點合并所有結果并得到最終查詢結果。
下圖是客戶端的一次分布式查詢請求圖(以下圖為例子說明查詢階段需要做的事情)

查詢階段包含以下三個步驟:
- 客戶端發送一個 search 請求到 Node 3 (協調節點可以是任意節點,這時 Node 3 成為了協調節點), Node 3 會創建一個大小為 from + size 的空優先隊列。
協調節點將在之后的請求中輪詢所有的分片來分攤負載。
- Node 3 將查詢請求轉發到索引的每個 主分片/副分片 中。每個分片在本地執行查詢并添加結果到大小為 from + size 的本地有序優先隊列中。
- 每個分片返回各自優先隊列中所有文檔的 ID 和 排序值 給協調節點,也就是 Node 3,它合并這些值到自己的優先隊列中來產生一個全局排序后的結果列表。
2.4.2.2 取回階段
查詢階段得到的結果只是最終結果的文檔 ID,但是要返回給客戶端的需要是文檔,所以需要取回文檔這么一個動作。
2.4.2.1 的例子的分布式搜索的取回階段的流程,大概如下圖:

下面是步驟解釋:
- 協調節點辨別出哪些文檔需要被取回并向相關的分片提交多個
- 每個分片加載對應文檔返回給協調節點
- 全部文檔取回之后,協調節點返回結果給客戶端
2.4.3 深度分頁
了解了分布式查詢后,相信大家都會有一個問題:
分片要返回給協調節點from + size個結果,那么協調節點將會收到number_of_shards * (from + size)個結果,然后再排序。如果我要查第如果數據量更大,那么會占用多少
因此,我們要避免深度分頁,如果真的無法避免,那么請使用??scroll??(Elasticsearch 7.x 開始不再推薦使用 scroll)或者 search after。
3 管理和部署
3.1 如何合理設置分片數
我們知道每個原因。可當公司大到一定規模或者老板覺得自己發展很好要求我們要預設一段時間內可以承受的訪問量時,就不可能用默認的分片數或者隨便設置了。
要注意的點如下:
- 硬件
索引的分片數量應該與集群的可用硬件資源相匹配,特別是磁盤和內存資源。如果分片數量太大,可能會導致磁盤空間不足或內存不足,影響搜索性能。如果分片數量太少,可能會導致硬件資源的浪費。
- 數據量
索引的分片數量應該與數據量相匹配,特別是對于大型數據集。通常,建議每個分片至少包含幾十 GB 的數據。如果分片數量太多,可能會導致管理和查詢索引變得困難,如果分片數量太少,可能會導致搜索性能下降。
- 并發查詢
索引的分片數量應該與集群中同時進行的查詢數量相匹配。每個分片都需要處理查詢請求,并返回結果;如果分片數量太少,可能會導致查詢請求在隊列中排隊等待,影響查詢性能。
- 索引更新頻率
索引的分片數量應該考慮索引的更新頻率,如果索引更新頻率很高,可能會導致更新操作在集群中的競爭,影響寫入性能。
分片數建議不要超過節點數,當我們計算出分片數大于節點數時,我們應該增加節點。
因為分片數超過了節點數,同一個文檔的主分片有多個在同一節點會讓該節點負載過重,而且一旦該節點掛掉,會影響到多個分片的可用性。當然,資金短缺的情況就沒辦法了,那這時建議每個節點分配到的分片數盡量一致。
具體數值應該怎么定呢?
- 要盡量和生產中要使用的硬件資源,網絡資源保持一致,然后單節點單分片,然后進行壓測,逐漸增大數據量,看我們能接受的平均響應值對應的數據量是多少,假設 50 G。
- 再去看看我們實際業務場景預計需要支撐的數據量是多少(包括預留未來 3 年的預估值,如果業務給不出,就按照今年的值的 3 倍),然后除以 50,就是我們一個理論值的分片數了。
- 但是要考慮到 索引更新頻率 問題,如果是文檔里面某個字段高頻更新導致的,考慮是否可以拆出來;如果不行,那就得對這個理論值進行實際壓測,看看是否要減少分片數了。
3.2 擴容
大多數的擴容問題可以通過添加節點來解決。但有一種資源是有限制的,因此值得我們認真對待:集群狀態。
集群狀態是一種數據結構,貯存下列集群級別的信息:
- 集群級別的設置
- 集群中的節點
- 索引以及它們的設置、映射、分析器、預熱器(Warmers)和別名
- 與每個索引關聯的分片以及它們分配到的節點
集群狀態存在于集群中的每個節點,包括客戶端節點。這就是為什么任何一個節點都可以將請求直接轉發至被請求數據的節點——每個節點都知道每個文檔應該在哪里。
一旦某個索引要增加一個字段(數據結構需要變化的情況),那么接收到這個請求的主分片所在的節點必須向 master 匯報(因為只有 master 可以修改集群狀態)。然后,master 要將更改合并到集群狀態中,并向集群中所有節點發布一個新版本。請注意:集群狀態包括了映射!因此,我們字段越多,集群狀態也會越大,網絡開銷也會越大,所以最好想辦法拆一些字段出來(垂直拆分)。而拆字段出來導致文檔更多了也沒關系,ES 本來就是為了解決大數據的,保持集群狀態小而敏捷更重要。
3.3 推遲分片分配
我們都知道 ES 將自動在可用節點間進行分片均衡,包括新節點的加入和現有節點的離線。理論上來說,這個是理想的行為,我們想要提拔副本分片來盡快恢復丟失的主分片。我們同時也希望保證資源在整個集群的均衡,用以避免熱點。
然而,在實踐中,立即的再均衡所造成的問題會比其解決的更多。舉例來說,考慮到以下情形:
- 集群中的某個節點突然失聯了(只是短暫的失聯)。
- master 立即發現了有節點離線了,然后在集群內提拔了擁有該節點主分片副本的副分片為主分片。
- 在副本被提拔為主分片以后,master 節點開始執行恢復操作來重建缺失的副本。集群中的節點之間互相拷貝分片數據,網卡壓力劇增,集群狀態嘗試變綠。
- 由于目前集群處于非平衡狀態,這個過程還有可能會觸發小規模的分片移動。其他不相關的分片將在節點間遷移來達到一個最佳的平衡狀態。
與此同時,失聯的節點又回歸集群了。不幸的是,這個節點被告知當前的數據已經沒有用了,數據已經在其他節點上重新分配了。因此,它只能刪除本地數據,然后重新開始恢復集群的其他分片(然后這又導致了一個新的再平衡)。這種開銷無疑是極大而且是沒有必要的。為了避免這種情況,ES 可以推遲分片的分配。這可以讓集群在重新分配之前有時間去檢測這個節點是否會再次重新加入。
下面將延時修改成 5 分鐘:
3.4 部署
經過 1 和 2 章節,相信大家對 ES 的核心原理已經比較清楚了,但是我們更多是作為使用者,所以如何正確部署、管理和使用其實更為重要。
3.4.1 上生產前應該要注意的事項
? 硬件
– 內存
如果有一種資源是最先被耗盡的,它很可能是內存。排序和聚合都很耗內存,所以有足夠的堆空間來應付它們是很重要的。即使堆空間比較小的時候, 也能為操作系統文件緩存提供額外的內存。因為
內存一般選 8 ~ 64 GB,64 GB 內存的機器是非常理想的。少于8 GB 會適得其反(你最終需要很多很多的小機器),大于64 GB 的機器也會有問題。
– CPU
大多數他資源,具體配置多少個(CPU)不是那么關鍵。常見的集群使用兩到八個核的機器。
– 硬盤
硬盤對所有的集群都很重要,對大量寫入的集群更是加倍重要(例如那些存儲日志數據的)。硬盤是服務器上最慢的子系統,這意味著那些寫入量很大的集群很容易讓硬盤飽和,使得它成為集群的瓶頸。純看速度的話,推薦
– 網絡
快速可靠的網絡顯然對分布式系統的性能是很重要的。低延時能幫助確保節點間能順暢通訊,大帶寬能幫助分片移動和恢復。現代數據中心網絡(1 GbE, 10 GbE)對絕大多數集群都是足夠的。
- JVM
每個 ES 版本都會有和它相匹配的推薦的 JDK 版本,自己注意就好了。這里說下關于堆的設置。首先 xms 和 xmx 應該設置成一樣的。其次,堆占用的內存不應該超過服務器內存的一半。更詳細請看:set-jvm-heap-size 。
- 文件描述符和 MMap
Lucene 使用了大量的文件。同時, ES 在節點和 HTTP 客戶端之間進行通信也使用了大量的套接字。這一切都需要足夠的文件描述符。因此,安裝好 ES 后,要請求 GET /_nodes/process 確認下 max_file_descriptors 是否足夠大(比如 64000)。另外,ES 對各種文件混合使用了 NioFs(非阻塞文件系統)和 MMapFs (內存映射文件系統)。請確認系統配置的最大映射數量,以便有足夠的虛擬內存可用于 mmapped 文件。可以在 /etc/sysctl.conf 通過修改 vm.max_map_count 永久設置它。
3.4.2 滾動重啟
我們平常給應用上線,是一個個節點部署,做的好一點的公司甚至是逐漸放量到新節點,穩定后再上另一個節點的。ES 如果要重啟,也應該是如此。操作流程如下:
- 可能的話,停止索引新的數據。雖然不是每次都能真的做到,但是這一步可以幫助提高恢復速度。
- 禁止分片分配。這一步阻止 ES 再平衡缺失的分片,直到我們處理好。禁止分配如下:
- 關閉單個節點
- 執行維護/升級
- 重啟節點,然后確認它加入到集群了
- 用如下命令重啟分片分配:
分片再平衡會花一些時間。一直等到集群變成綠色狀態后再繼續
- 重重復第 2 到 6 步操作剩余節點
- 到這步可以安全的恢復索引了(如果之前停止了的話),不過等待集群完全均衡后再恢復索引,也會有助于提高處理速度。
3.4.3 備份集群
不管是備份容災,還是遷移集群,都是需要備份的。
要備份集群,可以使用??snapshot API??。這個會拿到集群里當前的狀態和數據,然后保存到一個共享倉庫里。這個備份過程是"智能"的。第一個快照會是一個數據的完整拷貝,但是所有后續的快照會保留的是已存快照和新數據之間的差異。隨著不時的對數據進行快照,備份也在增量的添加和刪除。這意味著后續備份會相當快速,因為它們只傳輸很小的數據量。
1.要使用這個功能,必須首先創建一個保存數據的倉庫。有多個倉庫類型可以選擇:
– 共享文件系統,比如 NAS
– Amazon S3
– HDFS (Hadoop 分布式文件系統)
– Azure Cloud
2.選好倉庫后,就要部署一個共享文件系統倉庫,如下:
注意:共享文件系統路徑必須確保集群所有節點都可以訪問到。
3.快照所有打開的索引 PUT _snapshot/my_backup/snapshot_1
這個調用會立刻返回,然后快照會在后臺運行。
– 如果希望等待快照完成才有返回:PUT _snapshot/my_backup/snapshot_1?wait_for_completion=true
– 快照指定索引,如下:
– 刪除快照:DELETE _snapshot/my_backup/snapshot_2這個要慎用。
– 監控快照進度:GET _snapshot/my_backup/snapshot_2/_status
– 取消一個快照,用于刪除一個進行中的快照:DELETE _snapshot/my_backup/snapshot_2
這個會中斷快照進程,然后刪除倉庫里進行到一半的快照。
3.4.4 從快照恢復
官網介紹:restore snapshot api
備份好數據后,就可以通過備份的快照快速恢復數據了。如下:
4 總結
紙上得來終覺淺,絕知此事要躬行。希望你能在工作中,根據公司實際情況按照學到的案例實際應用就最好了。
作者介紹
蔡柱梁,51CTO社區編輯,從事Java后端開發8年,做過傳統項目廣電BOSS系統,后投身互聯網電商,負責過訂單,TMS,中間件等。




























