【4.分佈式存儲】-mongodb

本文分兩部分,分佈式和單機。單個db的存儲引擎,物理和數據存儲簡介,事務實現等。分佈式架構,分佈式涉及的複製集,分片等可靠性和擴展性保障。linux

第一部分 單機存儲引擎介紹mondod

引擎概述

wiredTiger(簡稱WT)支持行存儲、列存儲以及LSM等3種存儲形式,Mongodb使用時,只是將其做爲普通的KV存儲引擎來使用,mongodb的每一個集合對應一個WT的table,table裏包含多個Key-value pairs,以B樹形式存儲。mongodb的集合和索引都對應一個wiredTiger的table。並依賴於wiredTiger提供的checkpoint + write ahead log機制提供高數據可靠性,目前支持單機事務。
按照Mongodb默認的配置,WiredTiger的寫操做會先寫入Cache,並持久化到WAL(Write ahead log journal),每60s或log文件達到2GB時會作一次Checkpoint,將當前的數據持久化,產生一個新的快照。Wiredtiger鏈接初始化時,首先將數據恢復至最新的快照狀態,而後根據WAL恢復數據,以保證存儲可靠性。
Wiredtiger的索引採用Btree的方式組織
Wiredtiger採用Copy on write[ps:存儲的COW意在W的時候C一份,不在原來上修改。linux的意在W的時候才C一份,不更改只會存在一份]的方式管理修改操做(insert、update、delete),保證一致性,而且不像InnoDB同樣,須要一個DoubleWriteBuffer保證非disk block 512B寫時對原有頁可能發生conrrupt。修改操做會先緩存在cache裏,持久化時,修改操做不會在原來的leaf page上進行,而是寫入新分配的page,每次checkpoint都會產生一個新的root page。算法

數據組織

內存結構:B樹索引頁和數據頁(B樹作了改版,中間節點不放數據,要加update和insert佔內存),新插入跳錶(有序)更新list(會變動,無需有序),copy on write,wal
物理結構:mongodb

clipboard.png

clipboard.png

讀寫,ACID,checkpoint等功能與性能

  • ACID
    寫入:寫入頁的跳錶,不改變原值
    更新:寫入更新數組中
    讀取:如有update合併
    ACID:隔離用的未提交快照。注意這個快照只是讀用的。寫仍是到最新頁。
    關於快照,緩存,數據
    每一個事務有本身的快照(多是舊的page_root),新的事務會獲取當前最新page_root(可能還未作checkpoint,所以繼續用舊的,加入寫入跳錶和更新數組),checkpoint對最新的作持久化和生成新page_root到磁盤,按需讀取到內存。新建鏈接會先進行磁盤的數據的數據恢復:最新快照+WAL,按需讀入內存。快照也是經過事務id判斷update和Insert是否可見
  • journal(wal):併發,預先分配slots,申請slot用CAS,buffer和lsn, 刷盤。傳統的WAL使用互斥體來協調多線程的寫入,這在單核架構上能夠很好地工做,多核只有一個鎖浪費,journal的多線程申請slots,並行複製
  • 作checkpoint時,將更新和寫入到新的頁中,生成新的page_root快照。
  • cache 驅逐:略
  • 數據清理:數據庫

    1. compact 加的是DB級別的互斥寫鎖,同一個DB上的讀寫都會被阻塞
    2. compact基本不須要額外的空間,wiredtiger compact的原理是將數據不斷往前面的空洞挪動,並不須要把數據存儲到臨時的位置(額外的存儲空間)。
  • 運行中內存佔用
    存儲引擎cache,集合,索引元數據,新寫入數據
    MongoDB Driver(client) 會跟 mongod 進程創建 tcp 鏈接,一個鏈接一個線程的模型。並在鏈接上發送數據庫請求,接受應答,tcp 協議棧除了爲鏈接維護socket元數據爲,每一個鏈接會有一個read buffer及write buffer,用戶收發網絡包,buffer的大小經過以下sysctl系統參數配置,分別是buffer的最小值、默認值以及最大值。500個相似的鏈接就會佔用掉 1GB 的內存 ,ss -m
    其餘併發大時排序等
    主備節點差別,備節點buffer存儲oplog

分佈式

clipboard.png
分片
balancer+route
config數組

擴展性

分片:範圍,hash
遷移步驟:
集合分片開啓後,默認會建立一個新的chunk,shard key取值[minKey, maxKey]內的文檔(即全部的文檔)都會存儲到這個chunk。當使用Hash分片策略時,能夠預先建立多個chunk,以減小chunk的遷移。緩存

一個 Sharded Cluster 裏可能有不少個 mongos(能夠理解一個庫一個),若是全部的 mongos 的 Balancer 同時去觸發遷移,整個集羣就亂了,爲了避免出亂子,同一時刻只能讓一個 Balancer 去作負載均衡。
Balancer 在開始負載均衡前,會先搶鎖(config.locks集合下的一個特殊文檔),搶到鎖的 Balancer 繼續幹活,沒搶到鎖的則繼續等待,一段時間後再嘗試搶鎖。網絡

Step1: mongos 發送 moveChunk 給源 shard
mongos 接受到用戶發送的遷移 chunk 命令,或者因負載均衡策略須要遷移 chunk,會構建一個 moveChunk 的命令,併發送給源 shard。

Step2:源 shard 通知目標 shard 開始同步 chunk數據
源 shard 收到 mongos 發送的 moveChunk 命令後,會向目標 shard 發送 _recvChunkStart 的命令,通知目標 shard 開始遷移數據(真正的數據遷移由目標shard 主動發起)。接下來,源 shard 會記錄該 chunk 在遷移過程當中的全部增量修改操做。

Step3: 目標 shard 同步 chunk 數據到本地
目標 shard 接受到 _recvChunkStart 命令後,就會啓動單獨的線程來讀取 chunk 數據並寫到本地,主要步驟包括:
    目標 shard 建立集合及索引(若是有必要)
        若是遷移的集合在目標 shard 上沒有任何 chunk,則須要先在目標 shard 上建立集合,並建立跟源 shard 上集合相同的索引
    目標 shard 清理髒數據 (若是有必要)
        若是目標 shard 上已經存在該 chunk 範圍內的數據,則確定爲某次遷移失敗致使的髒數據,先將這些數據清理掉。
    目標 shard 向源 shard 發送 _migrateClone 命令讀取 chunk 範圍內的全部文檔並寫入到本地,即遷移 chunk 全量數據,遷移完後更新狀態爲 STEADY(能夠理解爲全量遷移完成的狀態)。
    源 shard 會不斷調用查詢目標 shard 上的遷移狀態,看是否爲 STEADY 狀態,若是已是 STEADY 狀態,就會中止源 shard 上的寫操做(經過對集合加互斥寫鎖實現)。接下來發送 _recvChunkCommit 告訴目標 shard 不會再有新的寫入了。
    目標 shard 的遷移線程不斷向源 shard 發送 _transferMods 命令,讀取遷移過程當中的增量修改,並應用到本地,增量遷移完成後,向源確認 _recvChunkCommit 的結果。
    源 shard 收到 _recvChunkCommit 的結果,整個數據遷移的步驟完成。
Step4:源 shard 更新 config server 元數據
數據遷移完成後,源 shard 就會向 config server 更新 chunk 對應的 shard 信息,同時也會更新 chunk 的版本信息,這樣 mongos 發現本地版本更低就會主動的 reload 元數據,具體機制參考 MongoDB Sharded Cluster 路由策略。

Step5:源 shard 刪除 chunk 數據
chunk 遷移到目標 shard 後,源上的 chunk 就沒有必要再保存了,源 shard 會將 chunk 數據刪除,默認狀況下源 shard 會將刪除操做加入到隊列,異步刪除,若是 moveChunk 時,指定了 _waitForDelete 參數爲 true,則同步刪除完再返回。


一旦源shard 查詢到目標 shard 進入到 STEADY 狀態了,源 shard 就會進入臨界區,測試源上的寫就會排隊等待。等整個遷移完了,這些等待的寫操做就會繼續執行,但此時 chunk 的版本號已經更新了,會告訴客戶端版本太低,客戶端從新從 config server 讀取配置,此時拿到的路由信息裏 chunk 已經在目標 shard 了,而後寫會發往目標 shard 。

複製集:

數據同步

Secondary初次同步數據時,會先進行init sync,從Primary(或其餘數據更新的Secondary)同步全量數據,而後不斷經過tailable cursor從Primary的local.oplog.rs集合裏查詢最新的oplog並應用到自身。
oplog: 冪等(incr會轉爲set),循環覆蓋,
順序保證:寫入 oplog前,會先加鎖給 oplog 分配時間戳,並註冊到未提交列表裏,正式寫入 oplog,在寫完後,將對應的 oplog 從未提交列表裏移除,在拉取 oplog 時若未提交列表爲空,全部 oplog 均可讀,不然只能到未提交列表最小值之前的 oplog
Secondary 拉取到一批 oplog 後,在重放這批 oplog 時,會加一個特殊的 Lock::ParallelBatchWriterMode 的鎖,這個鎖會阻塞全部的讀請求,直到這批 oplog 重放完成多線程

故障檢測恢復

client與複製集心跳,複製集之間心跳
複製集成員間默認每2s會發送一次心跳信息,若是10s未收到某個節點的心跳,則認爲該節點已宕機;若是宕機的節點爲Primary,Secondary(前提是可被選爲Primary)會發起新的Primary選舉。Bully算法。
每一個節點都傾向於投票給優先級最高的節點(oplog時間戳,同樣誰先就誰)
優先級爲0的節點不會主動發起Primary選舉
當Primary發現有優先級更高Secondary,而且該Secondary的數據落後在10s內,則Primary會主動降級,讓優先級更高的Secondary有成爲Primary的機會。
若是Primary與大多數的節點斷開鏈接,Primary會主動降級爲Secondary
當複製集內存活成員數量不足大多數時,整個複製集將沒法選舉出Primary,複製集將沒法提供寫服務,處於只讀狀態
當Primary宕機時,若是有數據未同步到Secondary,當Primary從新加入時,若是新的Primary上已經發生了寫操做,則舊Primary須要回滾部分操做,以保證數據集與新的Primary一致。舊Primary將回滾的數據寫到單獨的rollback目錄下,數據庫管理員可根據須要使用mongorestore進行恢復。
Bully架構

若是P是最大的ID,直接向全部人發送Victory消息,成功新的Leader;不然向全部比他大的ID的進程發送Election消息
若是P再發送Election消息後沒有收到Alive消息,則P向全部人發送Victory消息,成功新的Leader
若是P收到了從比本身ID還要大的進程發來的Alive消息,P中止發送任何消息,等待Victory消息(若是過了一段時間沒有等到Victory消息,從新開始選舉流程)
若是P收到了比本身ID小的進程發來的Election消息,回覆一個Alive消息,而後從新開始選舉流程
若是P收到Victory消息,把發送者當作Leader

部署

Primary
Secondary:
Arbiter節點只參與投票,不能被選爲Primary,而且不從Primary同步數據,偶數時加入
Priority0節點的選舉優先級爲0,不會被選舉爲Primary
Vote0複製集成員最多50個,參與Primary選舉投票的成員最多7個,其餘成員(Vote0)
Hidden(Vote0)可以使用Hidden節點作一些數據備份、離線計算的任務,不會影響複製集的服務。
Delayed節點必須是Hidden節點,而且其數據落後與Primary一段時間(錯誤恢復)併發

相關文章
相關標籤/搜索