Elasticsearch系列---shard內部原理


概要

本篇我們來看看shard內部的一些操作原理,了解一下人家是怎么玩的。

倒排索引

倒排索引的結構,是非常適合用來做搜索的,Elasticsearch會為索引的每個index為analyzed的字段建立倒排索引。

基本結構

倒排索引包含以下幾個部分:

  • 某個關鍵詞的doc list
  • 某個關鍵詞的所有doc的數量IDF(inverse document frequency)
  • 某個關鍵詞在每個doc中出現的次數:TF(term frequency)
  • 某個關鍵詞在這個doc中的次序
  • 每個doc的長度:length norm
  • 某個關鍵詞的所有doc的平均長度

記錄這些信息,就是為了方便搜索的效率和_score分值的計算。

不可變性

倒排索引寫入磁盤后就是不可變的,這樣有幾個好處:

  1. 不需要鎖,如果不更新索引,不用擔心鎖的問題,可以支持較高的並發能力
  2. 如果cache內存足夠,不更新索引的話,索引可以一直保存在os cache中,可以提升IO性能。
  3. 如果數據不變,filter cache會一直駐留在內存。
  4. 索引數據可以壓縮,節省cpu和io開銷。

doc底層原理

前面提到倒排索引是基於不可變模式設計的,但實際Elasticsearch源源不斷地有新數據進來,那光是建立、刪除倒排索引,豈不是非常忙?

如果真是不停地建立,刪除倒排索引,那ES壓力也太大了,肯定不是這么實現的。ES通過增加新的補充索引來接收新的文檔和修改的文檔,而不是直接用刪除重建的方式重寫整個索引。

doc寫入

整個寫入過程如下圖所示:

  1. 新文檔先寫入內存索引緩存
  2. 當間隔一定時間(1秒),將緩存的數據進行提交,這個過程會創建一個Commit Point,Commit Point包含index segment的信息。
  3. 緩存的數據寫入新的index segment。
  4. index segment的數據先寫入os-cache中
  5. 等待操作系統將os-cache的數據強制刷新到磁盤中
  6. 寫入磁盤完成后,新的index segment被打開,此時segment內的文檔可以被搜索到。
  7. 同時buffer的數據被清空,等待下一次新的文檔寫入。

index segment翻譯過來叫"段",每秒會創建一個,ES把這個1秒內收到的、需要處理的文檔都放在這個段里,可以把段認為是倒排索引的一個子集。

索引、分片、段的關系如下:
索引包含多個分片,每個分片是一個Lucene索引實例,一個分片下面有多個段。如果把分片看作是一個獨立的倒排索引結構,那么這個倒排索引是由多個段文件的集合。
三者之間是包含關系:索引包含多個分片,分片包含多個段。

doc刪除和更新

當文檔被刪除時,Commit Point會把信息記錄在.del文件中,在.del文件中會標識哪些文檔是有deleted標記的,但該文檔還是存在於原先的index segment文件里,同樣能夠被檢索到,只是在最終結果處理時,標記為deleted的文檔被會過濾掉。

更新也是類似的操作,更新會把舊版本的文檔標記為deleted,新的文檔會存儲在新的index segment中。

近實時搜索

上面的流程細節的童鞋可以會發現,每次都需要fsync磁盤,數據才是可搜索的,那IO壓力將特別大,耗費時間比較長,並且執行周期由操作系統控制,從一個新文檔寫入到可以被搜索,超過1分鍾那是常有的事。

所以Elasticsearch對此做了一個改進:
index segment信息寫入到os-cache中,即完成上面的第4步,該segment內的文檔信息就可以被搜索到了。fsync操作就不立即執行了,

os-cache的寫入代價比較低,最耗時的fsync操作交由操作系統調度執行。

上述的index segment寫入到os-cache,並打開搜索的過程,叫做refresh,默認是每隔1秒refresh一次所以,es是近實時的,數據寫入到可以被搜索,默認是1秒。

refresh的時間也可以設置,比如我們一些日志系統,數據量特別大,但實時性要求不高,我們為了優化資源分配,就可以把refresh設置得大一些:

PUT /music
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

此參數需要在創建索引時使用,要注意一下的是除非有充分的依據,才會對refresh進行設置,一般使用默認的即可。

translog機制

上述的寫入流程當中,如果fsync到磁盤的操作沒執行完成,服務器斷電宕機了,可能會導致Elasticsearch數據丟失。Elasticsearch也設計了translog機制,跟關系型數據庫的事務日志機制非常像,整個寫入過程將變成這樣:

  1. 新文檔寫入內存buffer的同時,也寫一份到translog當中。
  2. 內存buffer的數據每隔1秒寫入到index segment,並寫入os-cache,完成refresh操作。
  3. 內存buffer被清空,但translog一直累加。
  4. 每隔5秒translog信息fsync到磁盤上。
  5. 默認每30分鍾或translog累積到512MB時,執行全量commit操作,os-cache中的segment信息和translog信息fsync到磁盤中,持久化完成。
  6. 生成新的translog,舊的translog歸檔(6.x版本translog做歸檔操作,不刪除)。

flush API

這個執行一個提交並且歸檔translog的行為稱作一次flush。分片每30分鍾被自動刷新(flush),或者在 translog 太大的時候(默認512MB)也會刷新,當然也可以手動觸發flush的執行,如下請求:

POST /music/_flush

但任其自動flush就夠了。如果重啟節點前擔心會對索引造成影響,可以手動flush一下。畢竟節點重啟后需要從translog里恢復數據,translog越小,恢復就越快。

durability同步和異步

translog寫磁盤行為主要有兩種,是由index.translog.durability配置項決定的:

  • request:同步寫磁盤,每次寫請求完成之后立即執行(新增、刪除、更新文檔),以及primary shard和replica shard同步都會觸發,數據安全有保障,不丟失,但會帶來一些性能損失。如果是bulk數據導入,每個文檔平攤下來的損失是比較小的。
  • async:異步寫磁盤,默認5秒fsync一次,如果有宕機事件,可能會丟失幾秒的數據,適用於允許偶爾有數據丟失的場景,如日志系統。

如果系統不接受數據丟失,用translog同步方式,示例設置:

# 異步方式
PUT /music_new
{
  "settings": {
    "index.translog.durability": "async",
    "index.translog.sync_interval": "5s"
  }
}

# 同步方式
PUT /music_new
{
  "settings": {
    "index.translog.durability": "request"
  }
}

segment合並

Elasticsearch針對活躍的索引,每秒都會生成一個新的index segment,這些segment最終會以文件的形式存儲在磁盤里,如果不對其進行處理,那么索引運用一段時間后,會有特別多的文件,零碎的文件太多了,也不是什么好事情,更耗費更多的文件資源,句柄等,搜索過程也會變慢。

合並過程

Elasticsearch會在后台對segment進行合並,減少文件的數量,同時,標記為deleted的文檔在合並時會被丟棄(delete請求只是將文檔標記為deleted狀態,真正的物理刪除是在段合並的過程中),合並過程不需要人工干預,讓Elasticsearch自行完成即可。

兩個已經提交的段和一個未提交的段合並成為一個大的段文件

合並時會挑一些大小接近的段,合並到更大的段中,段合並過程不阻塞索引和搜索。

合並完成后,新的更大的段flush到磁盤中,並完成refresh操作,老的段被刪除掉。

optimize API

optimize命令可以強制合並API,並指定最終段的數量,如下命令:

POST /music_new/optimize
{
  "max_num_segments": 1
}

指定segment最大數量為1,表示該索引最終只有一個segment文件。

適用場景
  1. 正常活躍的、經常有更新的索引不建議使用
  2. 日志類的索引,對老數據進行優化時,可以將每個分片的段進行合並
使用建議
  1. 一般不需要人工干預合並過程
  2. optimize操作會消耗大量的IO資源,使用要慎重考慮

小結

本篇主要介紹shard內部的原理,包含寫入、更新刪除,translog機制,segment合並等,了解數據庫的童鞋對translog機制應該非常熟悉,原理上大同小異,僅作拋磚引玉,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM