作者 | 蔡柱梁
審校 | 重樓
目錄
精通ES,這一篇就夠了
目標
前言
1 ES 的分布式設計
1.1 集群添加節(jié)點和移除節(jié)點
2 ES 的讀寫
2.1 文檔
2.2 一個文檔寫入到一個分片中
2.3 使文本可被搜索
2.3.1 動態(tài)更新索引
2.3.2 準實時搜索
2.3.3 持久化變更
2.3.4 段合并
2.4 取回文檔(讀文檔)
2.4.1 取回單個文檔
2.4.2 分布式查詢
2.4.3 深度分頁
3 管理和部署
3.1 如何合理設置分片數(shù)
3.2 擴容
3.3 推遲分片分配
3.4 部署
3.4.1 上生產(chǎn)前應該要注意的事項
3.4.2 滾動重啟
3.4.3 備份集群
3.4.4 從快照恢復
4 總結(jié)
作者介紹
目標
1.了解 ES 的分布式設計
2.了解 ES 的讀寫
3.了解 ES 的管理和部署
前言
本文圖片大多來自官網(wǎng),其余為作者作圖。ES 的官網(wǎng)其實介紹的很詳細,但是很少人將它整理出來。我這里整理了出來,并加上了自己的看法,希望可以和大家一起深入學習ES。
參考文檔:??Elasticsearch 7.17??
1 ES 的分布式設計
ES 天生就是分布式的,并且在設計時屏蔽了分布式的復雜性,可以橫向擴展至數(shù)百(甚至數(shù)千)的服務器節(jié)點,同時可以處理PB級數(shù)據(jù)。
我們先來了解可以處理的一些事情:
- 分配文檔到不同的容器或分片中,文檔可以儲存在一個或多個節(jié)點中
- 按集群節(jié)點來均衡分配這些分片,從而對索引和搜索過程進行負載均衡
- 復制每個分片以支持數(shù)據(jù)冗余,從而防止硬件故障導致的數(shù)據(jù)丟失
- 將集群中任一節(jié)點的請求路由到存有相關數(shù)據(jù)的節(jié)點
- 集群擴容時無縫整合新節(jié)點,重新分配分片以便從離群節(jié)點恢復接下來,我們來看下集群中一些重要的概念:
- 分片
分片是 ES 實際存儲數(shù)據(jù)的地方,我們都知道 ES 是使用 Java 語言實現(xiàn)的,而它的搜索的底層是基于 Lucene 實現(xiàn)的,而每一個分片就是一個 Lucene 實例。在 Elasticsearch 6.x 以及之前的版本,每一個索引創(chuàng)建時默認是 5 個分片的,在 Elasticsearc 7.0 開始默認是 1 個分片,即:
- 節(jié)點角色(這里列出一部分角色,更多請參考:??Elasticsearch-Node??)
– master
主節(jié)點是集群中的控制節(jié)點,主要負責管理和維護集群的元數(shù)據(jù)(如索引、映射、分片分配等),并且負責選舉和維護集群中的主分片。每個 ES 集群只有一個 Master 節(jié)點,但是如果 Master 節(jié)點宕機,集群會通過選舉機制重新選舉一個新的 Master 節(jié)點。
– data
數(shù)據(jù)節(jié)點持有包含你所索引的文檔的分片。數(shù)據(jù)節(jié)點處理與數(shù)據(jù)有關的操作,如CRUD、搜索和聚合。這些操作是I/O、內(nèi)存和CPU密集型的。監(jiān)控這些資源是很重要的,如果它們過載了,可以增加更多的數(shù)據(jù)節(jié)點。擁有專用數(shù)據(jù)節(jié)點的主要好處是 master 和 data 角色分離。
– Coordinating node
協(xié)調(diào)節(jié)點是一種特殊的節(jié)點類型,它主要負責協(xié)調(diào)集群中的各個節(jié)點之間的通信和任務分配,不參與數(shù)據(jù)的存儲和處理。需要注意的是,協(xié)調(diào)節(jié)點本身不存儲數(shù)據(jù),因此它們可以在任何節(jié)點上運行(即任何節(jié)點都可以成為協(xié)調(diào)節(jié)點),并且可以隨時添加或刪除。通過將協(xié)調(diào)節(jié)點與數(shù)據(jù)節(jié)點分開,可以實現(xiàn)更好的性能和可伸縮性,同時保證集群的穩(wěn)定性和可靠性,但是不建議過多。因為 master 節(jié)點需要維護集群中的節(jié)點,如果數(shù)量過多,會給 master 造成過多的負擔,從而影響性能。具體作用如下:
1.路由請求
協(xié)調(diào)節(jié)點負責處理集群中的路由請求,即將請求路由到正確的節(jié)點上進行處理。例如,當一個搜索請求被發(fā)送到集群時,協(xié)調(diào)節(jié)點會確定哪些分片需要被搜索,并將請求路由到包含這些分片的節(jié)點上進行處理。
2.分配任務
協(xié)調(diào)節(jié)點負責分配各種任務到集群中的節(jié)點上進行處理,例如分片的重新分配、節(jié)點的加入和移除等。
3.監(jiān)控集群
協(xié)調(diào)節(jié)點負責監(jiān)控集群中各個節(jié)點的狀態(tài)和健康情況,并對集群中的故障進行處理。例如,當一個節(jié)點宕機時,協(xié)調(diào)節(jié)點會負責將該節(jié)點上的分片重新分配到其他節(jié)點上。
- 管理索引和映射(這里和 master 不同,master 是維護元數(shù)據(jù),而這里管理的是行為)
協(xié)調(diào)節(jié)點負責管理集群中的索引和映射,例如創(chuàng)建和刪除索引、修改映射等。
- Transport client(傳輸客戶端)
輕量級的傳輸客戶端可以將請求發(fā)送到遠程集群。它本身不加入集群,但是它可以將請求轉(zhuǎn)發(fā)到集群中的一個節(jié)點上。
- Node client(節(jié)點客戶端)
節(jié)點客戶端作為一個非數(shù)據(jù)節(jié)點加入到本地集群中。換句話說,它本身不保存任何數(shù)據(jù),但是它知道數(shù)據(jù)在集群中的哪個節(jié)點中,并且可以把請求轉(zhuǎn)發(fā)到正確的節(jié)點。
1.1 集群添加節(jié)點和移除節(jié)點
當配置了同樣的cluster.name: my-cluster的節(jié)點被發(fā)現(xiàn)后或者被移出集群,集群會對分片進行再平衡。
1)假設當前我集群中只有一個節(jié)點,該節(jié)點只有一個 index,設置了 3 個分片,具體如下:
這時副分片是沒有意義的,所以只有主分片,假設目前有三個分片,如下圖:
2)假如此時,我添加了一個節(jié)點,集群將會為這些主分片創(chuàng)建副本,并分開存放(高可用,需要做到備份容災,主分片與自己的備份不應該在同一節(jié)點),
假設只有一個副分片,如下圖:
3)我們搭建最小集群,節(jié)點個數(shù)應該為 3 個(防止網(wǎng)絡分區(qū)問題)。因此,再增加一個節(jié)點,分片將發(fā)生再平衡
4)這時 node 1 失聯(lián)了,重現(xiàn)選主 node 2 作為 master 節(jié)點,分片也會發(fā)生再平衡
2 ES 的讀寫
ES 的讀寫,其實就是創(chuàng)建文檔和讀取文檔的操作。因此,要了解這些操作,我們就要先了解文檔。
2.1 文檔
下面了解一些關于文檔的概念:
- 索引文檔
所謂的索引文檔就是指 通過使用 index API ,文檔可以被 索引 —— 存儲和使文檔可被搜索。使文檔可以被搜索,其實就是構建倒排索引。
- 文檔ID
ES 的每一個文檔都有一個 ID,這個 ID 要么就是自己創(chuàng)建文檔時提供,要么讓 ES 自己生成,并且 index_name + id 可以確定唯一的文檔(如果是 ES8 之前,則是 index_name + type + id 確定唯一的文檔)。
- 版本號
在 ES 中每個文檔都有一個版本號,每當對文檔進行修改時(包括刪除),版本號的值就會遞增,版本號用于處理并發(fā)沖突的場景。
上面提到了刪除文檔時,版本號也會遞增,這是為什么呢?
因為在 ES 中,文檔是不可變的,不能修改它們。因此,如果我們需要對一個文檔進行修改時,那只能 重建索引 或者 進行替換。而刪除只是更新文檔的一種特殊情況而已。不管對文檔進行修改還是刪除,版本號遞增后,我們只能讀取到最新的文檔。舊的文檔會被標記為已刪除,但是不會被馬上物理刪除。
修改文檔的過程:
- 從舊文檔構建
- 更改該
- 刪除舊文檔
- 索引一個新文檔
2.2 一個文檔寫入到一個分片中
一個文檔寫入到一個分片中,又可以稱為路由一個文檔到一個分片。在說路由之前,我們先梳理下節(jié)點,分片,index,文檔之間的關系:
- index 是具有同一特征的文檔的集合
- 我們在創(chuàng)建 index 時需要指定分片數(shù)
- 一個分片就是一個 Lucene 實例,而 Lucene 實例運行在 ES 節(jié)點上,一個節(jié)點允許有多個分片
下圖表示一個擁有 3 個節(jié)點的小集群,一共有 2 個 index,每個 index 有 3 個分片(指的是有 3 個主分片,每個主分片有 1 個副本)。
假設有一個集群由三個節(jié)點組成。 它包含一個叫blogs的索引,有兩個主分片,每個主分片有兩個副本分片,具體如下(這是官網(wǎng)的例子):
假設現(xiàn)有要寫入一個文檔到 blogs,那么這個過程是怎樣的呢?
首先要知道只有主分片具有處理寫請求的能力,副本只提供讀的能力,然后才是路由一個文檔到一個主分片中存儲起來。
至于是怎么路由的,請看下面的路由公式:
shard = hash(routing) % number_of_primary_shards
- routing
routing 默認是文檔 ID,也可以設置成一個自定義的值
- number_of_primary_shards
number_of_primary_shards 指的是主分片的數(shù)量
這也是為什么我們確定好 index 的分片數(shù)后不能修改的原因,如果我們需要擴容,只能通過重建索引等手段來進行水平擴容了。
新建、索引(構建倒排索引)和刪除 請求都是 寫 操作, 必須在主分片上面完成之后才能被復制到相關的副本分片,如下圖所示:
具體步驟如下:
- 客戶端向任意節(jié)點發(fā)出寫請求(圖示例寫著請求的是 node 1)
- node 1 通過路由算法確定文檔應該分配到分片 P0 上,隨后將請求轉(zhuǎn)發(fā)到 node 3
- 主分片 P0 完成寫請求后,轉(zhuǎn)發(fā)請求給 node 1 和 node 2,讓它們的副本分片 R0 完成數(shù)據(jù)同步
- 等到所有副本報告同步成功后,node 3 將向協(xié)調(diào)節(jié)點報告成功(這里的協(xié)調(diào)節(jié)點就是 node 1),協(xié)調(diào)節(jié)點向客戶端報告成功
2.3 使文本可被搜索?
上面說了一個文檔寫入的流程,但是文檔是一下就寫到磁盤嗎?是一寫入就立馬可以被搜索嗎?這些問題會在這一節(jié)中給出答案。
我們知道最早開始學全文搜索查詢到對應的信息。ES 也是如此,那么如何實現(xiàn)全文搜索呢?前面也提到了 ES 采用的是倒排索引,我在《ES入門》有寫倒排索引的原理,這里就不累述了。
當討論倒排索引時,我們會談到 文檔 標引,因為歷史原因,倒排索引被用來對整個非結(jié)構化文本文檔進行標引。 ES 中的 文檔 是有字段和值的結(jié)構化 JSON 文檔。事實上,在 JSON 文檔中, 每個被索引的字段都有自己的倒排索引。
早期的全文檢索會為整個文檔集合建立一個很大的倒排索引并將其寫入到磁盤。一旦新的索引就緒,舊的就會被其替換,這樣最近的變化便可以被檢索到。倒排索引被寫入磁盤后是不可改變的(我們稱之為倒排索引具有不變性)。這個特性的好處有:
- 不需要鎖
不變性意味著不怕高并發(fā),所以不需要鎖。
- 極大提高了 ES 的性能
一旦索引被讀入內(nèi)核的文件系統(tǒng)緩存,便會一直在那。由于不變性,只要緩存還有足夠的空間,那么大部分請求會直接請求內(nèi)存。這就很大程度上提升了性能。
- 其他緩存(如 filter)在索引生命周期內(nèi)始終有效
因為數(shù)據(jù)是不會變的(索引都沒變,數(shù)據(jù)當然沒變)。
- 減少 I/O 和需要被緩存到內(nèi)存的索引的使用量
因為不變性,所以可以在對單個大的倒排索引寫入時進行數(shù)據(jù)壓縮。
當然,有好處,就會有壞處:我要新增一個文檔,就需要重新構建一個新的索引,因為舊的索引是不可變的。這種設計會對索引包含的數(shù)據(jù)量和可被更新的頻率有極大的限制。
2.3.1 動態(tài)更新索引
動態(tài)更新索引指的是增量操作文檔后,會對索引進行補充索引來反映新的修改。
上面說了倒排索引的不變性的好處與壞處,那么如何在保留不變性的前提下實現(xiàn)倒排索引的更新呢?答案是:用更多的索引。
具體方法如下:
當發(fā)生了一些操作導致需要更新倒排索引,ES 不會直接更新索引,而是通過增加新的補充索引來反映最新的修改(也就是所謂的用更多索引)。那么要查詢時,關于這個 index 的索引這么多,怎么用呢?也簡單,就是按創(chuàng)建時間(升序)來遍歷這些索引查詢文檔,最后對查詢結(jié)果進行合并。
ES 基于上面的理論,引入了 按段搜索的概念,每一個段都是一個倒排索引,但是索引在段的集合外,還有一個概念提交點。在提交點內(nèi)的段才是可見的,它包含的文檔才是可被搜索的。
到這里,很容易將文檔的集合——index(索引) 和 倒排索引(索引,分片中的段)搞混,也不知道它們與分片(Lucene 實例)是一個怎樣的關系。因此,這里有必要再梳理下它們的關系:一個 index可以有多個分片,而一個分片中也可以有多個段用于搜索文檔。
下面用一個例子說明動態(tài)更新索引是如何在保留倒排索引的不變性的同時實現(xiàn)索引更新,從而讓新增的文檔可被搜索的:
1)假設現(xiàn)在有一個 Lucene 索引當前包含了一個提交點和三個段
2)現(xiàn)在我們新增了一個新的文檔。因為之前的索引是不變的,所以 ES 會進行補充索引,具體如下:
2.1)新文檔被收集到內(nèi)存索引緩存
2.2)不時地提交緩存
完整的提交緩存流程如下:
- 在磁盤上寫入一個追加的新段(也就是上面說的補充索引)
- 在磁盤上寫入一個新的提交點,該提交點包含新的段
經(jīng)過上面兩步后得到下圖:
- 所有在文件系統(tǒng)緩存中等待的寫入都刷新到磁盤,以確保它們被寫入物理文件(通過 fsync 來 flush,這樣在斷電的時候就不會丟失數(shù)據(jù))
2.3)新的段被開啟,讓它包含的文檔可以被搜索
2.4)內(nèi)存緩存被清空,等待接收新的文檔
上面說了新增文檔導致索引更新的情況,那么修改和刪除文檔呢?
因為段是不變的,所以文檔變更導致的索引變化是沒法在舊索引體現(xiàn)的。因此,每個提交點都會有一個 .del 文件,這個文件記錄著這些過時的文檔。不管是修改還是刪除都會被標記成刪除,但是沒有真的從磁盤上刪掉。當我們遍歷段時,這些文檔其實還是會被檢索出來的,只是 ES 做結(jié)果合并時會過濾這部分被標記為刪除的文檔。
2.3.2 準實時搜索
ES 從數(shù)據(jù)寫入到可被搜索不是實時的,而是有一定時延的,所以無法接受這一點的場景可能就不適合使用 ES 了。
2.3.1 說到了動態(tài)更新索引 是如何在保留不變性的前提下更新索引,讓新的文檔可被搜索的,但是這方案中的提交階段需要 fsync 來確保段被物理性寫入磁盤。如果每一次索引一個文檔都要執(zhí)行 fsync 的話,那么會對性能造成很大的影響。在 ES 和磁盤之間是文件系統(tǒng)緩存,為了提升性能, ES 做了一些列優(yōu)化使得 ES 可以近實時搜索。
下面用一個例子說明 ES 的優(yōu)化(還是用 “有一個 Lucene 索引當前包含了一個提交點和三個段” 這個前提):
1)新文檔被寫入了內(nèi)存索引緩存中,如下圖:
2)新文檔被寫入一個新的段中,該段被寫入了文件系統(tǒng)緩存(即只是寫入磁盤,但是沒有用 fsync 來 flush)。Lucene 允許這個新段被寫入和打開,使該段可以被搜索。
上面提到的 寫入和打開一個新段的過程 就是 refresh,ES 提供 refresh API,我們可以手動觸發(fā),不手動觸發(fā)的話,默認是每個分片每秒自動刷新一次。因此,我們說 ES 是準實時搜索。將 “使新的段可以被搜索” 拆得更細一點的話,具體步驟如下:
- 新文檔被寫入一個新的段中
- 該段被寫入文件系統(tǒng)緩存并打開(refresh)
- 同步數(shù)據(jù)到副本分片(如果有的話)
- 更新集群狀態(tài)(如果是集群的話)
master更新集群元數(shù)據(jù)
下面是對上面這些過程,幫助提升性能的一些思路:
- refresh 的時間
合理調(diào)整 refresh 頻率或者使用讀寫性能更強大的 SSD 硬盤。
- 同步數(shù)據(jù)的時間
同步數(shù)據(jù)受網(wǎng)絡,物理設備的狀態(tài),集群狀態(tài),副分片數(shù)量等因素影響,可以從這幾個角度考慮提升。
- master更新集群元數(shù)據(jù)的時間
這個受集群的大小和復雜程度影響,所以要合理搭建集群。
2.3.3 持久化變更
2.3.2說到為了實現(xiàn)準實時搜索完全拋棄了2.3.1也提到了動態(tài)更新索引最近一次完整的提交會將段刷到磁盤,但是這個提交點之后的操作呢?無法保證了,這樣就可能會出現(xiàn)數(shù)據(jù)丟失。
為了保證 ES 的可靠性,需要確保數(shù)據(jù)變化被持久化到磁盤,為此 ES 增加了一個 translog(事務日志,類似 MySQL 的 redo 日志)。整個流程如下:
1)一個新文檔被索引之后,就會被添加到內(nèi)存緩沖區(qū),并且相應的文檔操作被寫入到 translog(順序IO)
2)refresh(分片每秒被 refresh 一次),具體操作如下:
- 內(nèi)存緩沖區(qū)中的文檔被寫到一個新段中,且沒有進行 fsync
- 新段被打開,使新段可以被搜索
打開新段:
- 將新段加入到已有段的文件列表中,并且建立該段的倒排索引、文檔存儲和其他元數(shù)據(jù)
- 更新索引的內(nèi)存結(jié)構,包括倒排索引、字段信息、文檔數(shù)和大小等
- 內(nèi)存緩存區(qū)被清空
因為索引刷新會觸發(fā) Lucene 的 SegmentReader 和 SegmentSearcher 的更新操作,使得新段可以被加入到搜索中,所以新段在刷新操作后可以被搜索。
3)這個進程繼續(xù)工作,更多的文檔被添加到內(nèi)存緩沖區(qū)和追加到事務日志
4)不定時對索引 flush(分片每 30 分鐘被 flush一次或者 translog 過大的時候也會觸發(fā)),段被全量提交,并且事務日志被清空,具體操作如下:
- 內(nèi)存緩沖區(qū)中的所有文檔都被寫入一個新段
- 緩沖區(qū)被清空
- 一個提交點被寫入磁盤
- 文件系統(tǒng)緩存通過 fsync 被 flush
- 老的 translog 被刪除
translog 提供所有還沒有被刷到磁盤的操作的一個持久化記錄。當 ES 啟動的時候, 它會從磁盤中使用最后一個提交點去恢復已知的段,并且會重放 translog 中所有在最后一次提交后發(fā)生的變更操作。
這種執(zhí)行一個提交并且截斷 translog 的行為在 ES 中被稱為一次 flush。分片每 30 分鐘被自動 flush 一次或者 translog 太大的時候也會觸發(fā)。具體可看 Translog。我們也能手動觸發(fā) flush,具體請看 Flush API。
2.3.4 段合并
段合并的時候會將那些舊的已刪除文檔從文件系統(tǒng)中清除,被刪除的文檔(或被更新文檔的舊版本)不會被拷貝到新的大段中。
默認每秒一次刷新,會導致段的數(shù)量在短時間能激增,但是如果降低 refresh 的頻率,對于一些實時性要求較高的場景又不能滿足。而且,段數(shù)量過多也會造成各種各樣的問題:每一個段都會消耗文件句柄、內(nèi)存和CPU運行周期;更重要的是,每個搜索請求都必須輪流檢查每個段;所以段越多,搜索也就越慢。ES 通過在后臺進行 段合并 來解決這個問題。啟動 段合并 不需要我們做任何事,ES 進行索引和搜索時會自動進行。
假設現(xiàn)在要進行段合并了:
1)合并進程選擇大小相似的小段并且將他們合并到更大的段中(這樣就不會中斷索引和搜索)。如下圖所示:
2)一旦合并結(jié)束,老的段被刪除
合并大的段需要消耗大量的 I/O 和 CPU 資源,如果任其發(fā)展會影響搜索性能。ES 在默認情況下會對合并流程進行資源限制,所以搜索仍然有足夠的資源很好地執(zhí)行。
?2.3.4.1 段合并建議
2.3.4 末尾說到了 “合并大的段需要消耗大量的 I/O 和 CPU 資源”,但是合并是在后臺定期操作的。因為這些操作可能要很長時間才能完成,尤其是比較大的段。這個通常來說都沒問題,因為大規(guī)模段合并的概率是很小的。不過有時候合并會拖累寫入速率,如果這個真的發(fā)生了,ES 會自動限制索引請求到單個線程里。這樣可以防止出現(xiàn) 段爆炸 問題(即數(shù)以百計的段在被合并之前就生成出來)。
ES 默認設置在這塊比較保守:不希望搜索性能被后臺合并影響。不過有時候(尤其是 SSD,或者日志場景)限流閾值太低了。默認值是 20 MB/s,對機械磁盤應該是個不錯的設置。
- 如果服務器用的是 SSD,可以考慮提高到 100–200 MB/s。測試驗證對系統(tǒng)哪個值比較合適:
- 如果在做批量導入,完全不在意搜索,那么可以徹底關掉合并限流。這樣讓索引速度跑到你磁盤允許的極限:
最后,可以增加 index.translog.flush_threshold_size 設置,從默認的 512 MB 到更大一些的值,比如 1 GB。這可以在一次清空觸發(fā)的時候,在事務日志里積累出更大的段。而通過構建更大的段,清空的頻率變低,大段合并的頻率也變低。這一切合起來導致更少的磁盤 I/O 開銷和更好的索引速率。當然,這會需要對應量級的 heap 內(nèi)存用以積累更大的緩沖空間,調(diào)整這個設置的時候請記住這點。
不過一般對于中小公司的業(yè)務或者試探性的業(yè)務建議用默認設置就好了,否則自己不是特別熟悉 ES 的情況下,容易讓 ES 性能下降又找不出原因。
2.3.4.2 科學的測試性能
2.3.4.1 有些設置給出的值是經(jīng)驗值,但不適用所有公司。因此,真的要改默認設置的話,性能測試很重要。下面給出一些方法論,如下:
- 在單個節(jié)點上,對單個分片,無副本的場景測試性能。
- 在 100% 默認配置的情況下記錄性能結(jié)果,這樣就有了一個對比基線。
- 確保性能測試運行足夠長的時間(30 分鐘以上),這樣可以評估長期性能,而不是短期的峰值或延遲。
一些事件(比如段合并,GC)不會立刻發(fā)生,所以性能概況會隨著時間繼續(xù)而改變的。
- 開始在基線上逐一修改默認值。
嚴格測試需要修改的配置,如果性能提升可以接受,保留這個配置項,開始下一項。
2.4 取回文檔(讀文檔)
關于讀請求需要知道:
- 在處理讀取請求時,協(xié)調(diào)結(jié)點在每次請求的時候都會通過輪詢所有的副本分片來達到負載均衡。
- 在文檔被檢索時,已經(jīng)被索引的文檔可能已經(jīng)存在于主分片上但是還沒有復制到副本分片。在這種情況下,副本分片可能會報告文檔不存在,但是主分片可能成功返回文檔。一旦索引請求成功返回給用戶,文檔在主分片和副本分片都是可用的。
2.4.1 取回單個文檔
通過文檔 ID 查詢文檔。
以下是從主分片或者副本分片檢索文檔的步驟順序(官網(wǎng)例子):
- 客戶端向某個節(jié)點(假設現(xiàn)在是 Node 1) 發(fā)送獲取請求。
- 節(jié)點使用文檔的 _id 來確定文檔屬于哪個分片。
假設是屬于分片0,并應該請求 Node 2 的 R0,這時會將請求轉(zhuǎn)發(fā)到 Node 2。
- Node 2 將文檔返回給 Node 1,然后將文檔返回給客戶端。
下面是上面步驟的流程圖:
2.4.2 分布式查詢
我們大多數(shù)時候都不是根據(jù)文檔 ID 查詢文檔,而是根據(jù)某些條件篩選做聚合查詢。這時,我們就需要了解下分布式查詢是怎樣處理請求的了。整體上分為兩個階段:查詢階段 和 取回階段。
2.4.2.1 查詢階段
協(xié)調(diào)節(jié)點向各個分片發(fā)出查詢請求,每個分片(可能是主分片,也可能是副分片,但是不會重復分片)查詢出自己的結(jié)果并返回給協(xié)調(diào)節(jié)點,協(xié)調(diào)節(jié)點合并所有結(jié)果并得到最終查詢結(jié)果。
下圖是客戶端的一次分布式查詢請求圖(以下圖為例子說明查詢階段需要做的事情)
查詢階段包含以下三個步驟:
- 客戶端發(fā)送一個 search 請求到 Node 3 (協(xié)調(diào)節(jié)點可以是任意節(jié)點,這時 Node 3 成為了協(xié)調(diào)節(jié)點), Node 3 會創(chuàng)建一個大小為 from + size 的空優(yōu)先隊列。
協(xié)調(diào)節(jié)點將在之后的請求中輪詢所有的分片來分攤負載。
- Node 3 將查詢請求轉(zhuǎn)發(fā)到索引的每個 主分片/副分片 中。每個分片在本地執(zhí)行查詢并添加結(jié)果到大小為 from + size 的本地有序優(yōu)先隊列中。
- 每個分片返回各自優(yōu)先隊列中所有文檔的 ID 和 排序值 給協(xié)調(diào)節(jié)點,也就是 Node 3,它合并這些值到自己的優(yōu)先隊列中來產(chǎn)生一個全局排序后的結(jié)果列表。
2.4.2.2 取回階段
查詢階段得到的結(jié)果只是最終結(jié)果的文檔 ID,但是要返回給客戶端的需要是文檔,所以需要取回文檔這么一個動作。
2.4.2.1 的例子的分布式搜索的取回階段的流程,大概如下圖:
下面是步驟解釋:
- 協(xié)調(diào)節(jié)點辨別出哪些文檔需要被取回并向相關的分片提交多個
- 每個分片加載對應文檔返回給協(xié)調(diào)節(jié)點
- 全部文檔取回之后,協(xié)調(diào)節(jié)點返回結(jié)果給客戶端
2.4.3 深度分頁
了解了分布式查詢后,相信大家都會有一個問題:
分片要返回給協(xié)調(diào)節(jié)點from + size個結(jié)果,那么協(xié)調(diào)節(jié)點將會收到number_of_shards * (from + size)個結(jié)果,然后再排序。如果我要查第如果數(shù)據(jù)量更大,那么會占用多少
因此,我們要避免深度分頁,如果真的無法避免,那么請使用??scroll??(Elasticsearch 7.x 開始不再推薦使用 scroll)或者 search after。
3 管理和部署
3.1 如何合理設置分片數(shù)
我們知道每個原因??僧敼敬蟮揭欢ㄒ?guī)?;蛘呃习逵X得自己發(fā)展很好要求我們要預設一段時間內(nèi)可以承受的訪問量時,就不可能用默認的分片數(shù)或者隨便設置了。
要注意的點如下:
- 硬件
索引的分片數(shù)量應該與集群的可用硬件資源相匹配,特別是磁盤和內(nèi)存資源。如果分片數(shù)量太大,可能會導致磁盤空間不足或內(nèi)存不足,影響搜索性能。如果分片數(shù)量太少,可能會導致硬件資源的浪費。
- 數(shù)據(jù)量
索引的分片數(shù)量應該與數(shù)據(jù)量相匹配,特別是對于大型數(shù)據(jù)集。通常,建議每個分片至少包含幾十 GB 的數(shù)據(jù)。如果分片數(shù)量太多,可能會導致管理和查詢索引變得困難,如果分片數(shù)量太少,可能會導致搜索性能下降。
- 并發(fā)查詢
索引的分片數(shù)量應該與集群中同時進行的查詢數(shù)量相匹配。每個分片都需要處理查詢請求,并返回結(jié)果;如果分片數(shù)量太少,可能會導致查詢請求在隊列中排隊等待,影響查詢性能。
- 索引更新頻率
索引的分片數(shù)量應該考慮索引的更新頻率,如果索引更新頻率很高,可能會導致更新操作在集群中的競爭,影響寫入性能。
分片數(shù)建議不要超過節(jié)點數(shù),當我們計算出分片數(shù)大于節(jié)點數(shù)時,我們應該增加節(jié)點。
因為分片數(shù)超過了節(jié)點數(shù),同一個文檔的主分片有多個在同一節(jié)點會讓該節(jié)點負載過重,而且一旦該節(jié)點掛掉,會影響到多個分片的可用性。當然,資金短缺的情況就沒辦法了,那這時建議每個節(jié)點分配到的分片數(shù)盡量一致。
具體數(shù)值應該怎么定呢?
- 要盡量和生產(chǎn)中要使用的硬件資源,網(wǎng)絡資源保持一致,然后單節(jié)點單分片,然后進行壓測,逐漸增大數(shù)據(jù)量,看我們能接受的平均響應值對應的數(shù)據(jù)量是多少,假設 50 G。
- 再去看看我們實際業(yè)務場景預計需要支撐的數(shù)據(jù)量是多少(包括預留未來 3 年的預估值,如果業(yè)務給不出,就按照今年的值的 3 倍),然后除以 50,就是我們一個理論值的分片數(shù)了。
- 但是要考慮到 索引更新頻率 問題,如果是文檔里面某個字段高頻更新導致的,考慮是否可以拆出來;如果不行,那就得對這個理論值進行實際壓測,看看是否要減少分片數(shù)了。
3.2 擴容
大多數(shù)的擴容問題可以通過添加節(jié)點來解決。但有一種資源是有限制的,因此值得我們認真對待:集群狀態(tài)。
集群狀態(tài)是一種數(shù)據(jù)結(jié)構,貯存下列集群級別的信息:
- 集群級別的設置
- 集群中的節(jié)點
- 索引以及它們的設置、映射、分析器、預熱器(Warmers)和別名
- 與每個索引關聯(lián)的分片以及它們分配到的節(jié)點
集群狀態(tài)存在于集群中的每個節(jié)點,包括客戶端節(jié)點。這就是為什么任何一個節(jié)點都可以將請求直接轉(zhuǎn)發(fā)至被請求數(shù)據(jù)的節(jié)點——每個節(jié)點都知道每個文檔應該在哪里。
一旦某個索引要增加一個字段(數(shù)據(jù)結(jié)構需要變化的情況),那么接收到這個請求的主分片所在的節(jié)點必須向 master 匯報(因為只有 master 可以修改集群狀態(tài))。然后,master 要將更改合并到集群狀態(tài)中,并向集群中所有節(jié)點發(fā)布一個新版本。請注意:集群狀態(tài)包括了映射!因此,我們字段越多,集群狀態(tài)也會越大,網(wǎng)絡開銷也會越大,所以最好想辦法拆一些字段出來(垂直拆分)。而拆字段出來導致文檔更多了也沒關系,ES 本來就是為了解決大數(shù)據(jù)的,保持集群狀態(tài)小而敏捷更重要。
3.3 推遲分片分配
我們都知道 ES 將自動在可用節(jié)點間進行分片均衡,包括新節(jié)點的加入和現(xiàn)有節(jié)點的離線。理論上來說,這個是理想的行為,我們想要提拔副本分片來盡快恢復丟失的主分片。我們同時也希望保證資源在整個集群的均衡,用以避免熱點。
然而,在實踐中,立即的再均衡所造成的問題會比其解決的更多。舉例來說,考慮到以下情形:
- 集群中的某個節(jié)點突然失聯(lián)了(只是短暫的失聯(lián))。
- master 立即發(fā)現(xiàn)了有節(jié)點離線了,然后在集群內(nèi)提拔了擁有該節(jié)點主分片副本的副分片為主分片。
- 在副本被提拔為主分片以后,master 節(jié)點開始執(zhí)行恢復操作來重建缺失的副本。集群中的節(jié)點之間互相拷貝分片數(shù)據(jù),網(wǎng)卡壓力劇增,集群狀態(tài)嘗試變綠。
- 由于目前集群處于非平衡狀態(tài),這個過程還有可能會觸發(fā)小規(guī)模的分片移動。其他不相關的分片將在節(jié)點間遷移來達到一個最佳的平衡狀態(tài)。
與此同時,失聯(lián)的節(jié)點又回歸集群了。不幸的是,這個節(jié)點被告知當前的數(shù)據(jù)已經(jīng)沒有用了,數(shù)據(jù)已經(jīng)在其他節(jié)點上重新分配了。因此,它只能刪除本地數(shù)據(jù),然后重新開始恢復集群的其他分片(然后這又導致了一個新的再平衡)。這種開銷無疑是極大而且是沒有必要的。為了避免這種情況,ES 可以推遲分片的分配。這可以讓集群在重新分配之前有時間去檢測這個節(jié)點是否會再次重新加入。
下面將延時修改成 5 分鐘:
3.4 部署
經(jīng)過 1 和 2 章節(jié),相信大家對 ES 的核心原理已經(jīng)比較清楚了,但是我們更多是作為使用者,所以如何正確部署、管理和使用其實更為重要。
3.4.1 上生產(chǎn)前應該要注意的事項
? 硬件
– 內(nèi)存
如果有一種資源是最先被耗盡的,它很可能是內(nèi)存。排序和聚合都很耗內(nèi)存,所以有足夠的堆空間來應付它們是很重要的。即使堆空間比較小的時候, 也能為操作系統(tǒng)文件緩存提供額外的內(nèi)存。因為
內(nèi)存一般選 8 ~ 64 GB,64 GB 內(nèi)存的機器是非常理想的。少于8 GB 會適得其反(你最終需要很多很多的小機器),大于64 GB 的機器也會有問題。
– CPU
大多數(shù)他資源,具體配置多少個(CPU)不是那么關鍵。常見的集群使用兩到八個核的機器。
– 硬盤
硬盤對所有的集群都很重要,對大量寫入的集群更是加倍重要(例如那些存儲日志數(shù)據(jù)的)。硬盤是服務器上最慢的子系統(tǒng),這意味著那些寫入量很大的集群很容易讓硬盤飽和,使得它成為集群的瓶頸。純看速度的話,推薦
– 網(wǎng)絡
快速可靠的網(wǎng)絡顯然對分布式系統(tǒng)的性能是很重要的。低延時能幫助確保節(jié)點間能順暢通訊,大帶寬能幫助分片移動和恢復。現(xiàn)代數(shù)據(jù)中心網(wǎng)絡(1 GbE, 10 GbE)對絕大多數(shù)集群都是足夠的。
- JVM
每個 ES 版本都會有和它相匹配的推薦的 JDK 版本,自己注意就好了。這里說下關于堆的設置。首先 xms 和 xmx 應該設置成一樣的。其次,堆占用的內(nèi)存不應該超過服務器內(nèi)存的一半。更詳細請看:set-jvm-heap-size 。
- 文件描述符和 MMap
Lucene 使用了大量的文件。同時, ES 在節(jié)點和 HTTP 客戶端之間進行通信也使用了大量的套接字。這一切都需要足夠的文件描述符。因此,安裝好 ES 后,要請求 GET /_nodes/process 確認下 max_file_descriptors 是否足夠大(比如 64000)。另外,ES 對各種文件混合使用了 NioFs(非阻塞文件系統(tǒng))和 MMapFs (內(nèi)存映射文件系統(tǒng))。請確認系統(tǒng)配置的最大映射數(shù)量,以便有足夠的虛擬內(nèi)存可用于 mmapped 文件。可以在 /etc/sysctl.conf 通過修改 vm.max_map_count 永久設置它。
3.4.2 滾動重啟
我們平常給應用上線,是一個個節(jié)點部署,做的好一點的公司甚至是逐漸放量到新節(jié)點,穩(wěn)定后再上另一個節(jié)點的。ES 如果要重啟,也應該是如此。操作流程如下:
- 可能的話,停止索引新的數(shù)據(jù)。雖然不是每次都能真的做到,但是這一步可以幫助提高恢復速度。
- 禁止分片分配。這一步阻止 ES 再平衡缺失的分片,直到我們處理好。禁止分配如下:
- 關閉單個節(jié)點
- 執(zhí)行維護/升級
- 重啟節(jié)點,然后確認它加入到集群了
- 用如下命令重啟分片分配:
分片再平衡會花一些時間。一直等到集群變成綠色狀態(tài)后再繼續(xù)
- 重重復第 2 到 6 步操作剩余節(jié)點
- 到這步可以安全的恢復索引了(如果之前停止了的話),不過等待集群完全均衡后再恢復索引,也會有助于提高處理速度。
3.4.3 備份集群
不管是備份容災,還是遷移集群,都是需要備份的。
要備份集群,可以使用??snapshot API??。這個會拿到集群里當前的狀態(tài)和數(shù)據(jù),然后保存到一個共享倉庫里。這個備份過程是"智能"的。第一個快照會是一個數(shù)據(jù)的完整拷貝,但是所有后續(xù)的快照會保留的是已存快照和新數(shù)據(jù)之間的差異。隨著不時的對數(shù)據(jù)進行快照,備份也在增量的添加和刪除。這意味著后續(xù)備份會相當快速,因為它們只傳輸很小的數(shù)據(jù)量。
1.要使用這個功能,必須首先創(chuàng)建一個保存數(shù)據(jù)的倉庫。有多個倉庫類型可以選擇:
– 共享文件系統(tǒng),比如 NAS
– Amazon S3
– HDFS (Hadoop 分布式文件系統(tǒng))
– Azure Cloud
2.選好倉庫后,就要部署一個共享文件系統(tǒng)倉庫,如下:
注意:共享文件系統(tǒng)路徑必須確保集群所有節(jié)點都可以訪問到。
3.快照所有打開的索引 PUT _snapshot/my_backup/snapshot_1
這個調(diào)用會立刻返回,然后快照會在后臺運行。
– 如果希望等待快照完成才有返回:PUT _snapshot/my_backup/snapshot_1?wait_for_completion=true
– 快照指定索引,如下:
– 刪除快照:DELETE _snapshot/my_backup/snapshot_2這個要慎用。
– 監(jiān)控快照進度:GET _snapshot/my_backup/snapshot_2/_status
– 取消一個快照,用于刪除一個進行中的快照:DELETE _snapshot/my_backup/snapshot_2
這個會中斷快照進程,然后刪除倉庫里進行到一半的快照。
3.4.4 從快照恢復
官網(wǎng)介紹:restore snapshot api
備份好數(shù)據(jù)后,就可以通過備份的快照快速恢復數(shù)據(jù)了。如下:
4 總結(jié)
紙上得來終覺淺,絕知此事要躬行。希望你能在工作中,根據(jù)公司實際情況按照學到的案例實際應用就最好了。
作者介紹
蔡柱梁,51CTO社區(qū)編輯,從事Java后端開發(fā)8年,做過傳統(tǒng)項目廣電BOSS系統(tǒng),后投身互聯(lián)網(wǎng)電商,負責過訂單,TMS,中間件等。