Mongodb集羣

1. 集羣概述

在大數據領域常常提到的4V特徵中,Volume(數據量大)是首當其衝被提及的。
由於單機垂直擴展能力的侷限,水平擴展的方式則顯得更加的靠譜。 MongoDB 自帶了這種能力,可以將數據存儲到多個機器上以提供更大的容量和負載能力。
此外,同時爲了保證數據的高可用,MongoDB 採用副本集的方式來實現數據複製。

一個典型的MongoDB集羣架構會同時採用分片+副本集的方式,如下圖:

1.1 架構說明

數據分片(Shards)

分片用於存儲真正的集羣數據,可以是一個單獨的 Mongod實例,也可以是一個副本集。 生產環境下Shard一般是一個 Replica Set,以防止該數據片的單點故障。

對於分片集合(sharded collection)來說,每個分片上都存儲了集合的一部分數據(按照分片鍵切分),如果集合沒有分片,那麼該集合的數據都存儲在數據庫的 Primary Shard中。

配置服務器(Config Servers)

保存集羣的元數據(metadata),包含各個Shard的路由規則,配置服務器由一個副本集(ReplicaSet)組成。

查詢路由(Query Routers)

Mongos是 Sharded Cluster 的訪問入口,其本身並不持久化數據 。Mongos啓動後,會從 Config Server 加載元數據,開始提供服務,並將用戶的請求正確路由到對應的Shard。

Sharding 集羣可以部署多個 Mongos 以分擔客戶端請求的壓力。

2. 分片機制

下面的幾個細節,對於理解和應用 MongoDB 的分片機制比較重要,所以有必要提及一下:

2.1 數據如何切分

首先,基於分片切分後的數據塊稱爲 chunk,一個分片後的集合會包含多個 chunk,每個 chunk 位於哪個分片(Shard) 則記錄在 Config Server(配置服務器)上。

Mongos 在操作分片集合時,會自動根據分片鍵找到對應的 chunk,並向該 chunk 所在的分片發起操作請求。

數據是根據分片策略來進行切分的,而分片策略則由 分片鍵(ShardKey)+分片算法(ShardStrategy)組成。

MongoDB 支持兩種分片算法:

2.1.1 範圍分片

如上圖所示,假設集合根據x字段來分片,x的取值範圍爲[minKey, maxKey](x爲整型,這裏的minKey、maxKey爲整型的最小值和最大值),將整個取值範圍劃分爲多個chunk,每個chunk(默認配置爲64MB)包含其中一小段的數據:
如Chunk1包含x的取值在[minKey, -75)的所有文檔,而Chunk2包含x取值在[-75, 25)之間的所有文檔...

範圍分片能很好的滿足範圍查詢的需求,比如想查詢x的值在[-30, 10]之間的所有文檔,這時 Mongos 直接能將請求路由到 Chunk2,就能查詢出所有符合條件的文檔。 範圍分片的缺點在於,如果 ShardKey 有明顯遞增(或者遞減)趨勢,則新插入的文檔多會分佈到同一個chunk,無法擴展寫的能力,比如使用_id作爲 ShardKey,而MongoDB自動生成的id高位是時間戳,是持續遞增的。

2.1.2 哈希分片

Hash分片是根據用戶的 ShardKey 先計算出hash值(64bit整型),再根據hash值按照範圍分片的策略將文檔分佈到不同的 chunk。

由於 hash值的計算是隨機的,因此 Hash 分片具有很好的離散性,可以將數據隨機分發到不同的 chunk 上。 Hash 分片可以充分的擴展寫能力,彌補了範圍分片的不足,但不能高效的服務範圍查詢,所有的範圍查詢要查詢多個 chunk 才能找出滿足條件的文檔。

2.2 如何保證均衡

如前面的說明中,數據是分佈在不同的 chunk上的,而 chunk 則會分配到不同的分片上,那麼如何保證分片上的 數據(chunk) 是均衡的呢?
在真實的場景中,會存在下面兩種情況:

  • A. 全預分配,chunk 的數量和 shard 都是預先定義好的,比如 10個shard,存儲1000個chunk,那麼每個shard 分別擁有100個chunk。此時集羣已經是均衡的狀態(這裏假定)
  • B. 非預分配,這種情況則比較複雜,一般當一個 chunk 太大時會產生分裂(split),不斷分裂的結果會導致不均衡;或者動態擴容增加分片時,也會出現不均衡的狀態。 這種不均衡的狀態由集羣均衡器進行檢測,一旦發現了不均衡則執行 chunk數據的搬遷達到均衡。

MongoDB 的數據均衡器運行於 Primary Config Server(配置服務器的主節點)上,而該節點也同時會控制 Chunk 數據的搬遷流程。

對於數據的不均衡是根據兩個分片上的 Chunk 個數差異來判定的,閾值對應表如下:

Number of Chunks Migration Threshold
Fewer than 20 2
20-79 4
80 and greater 8

MongoDB 的數據遷移對集羣性能存在一定影響,這點無法避免,目前的規避手段只能是將均衡窗口對齊到業務閒時段。

2.3 應用高可用

應用節點可以通過同時連接多個 Mongos 來實現高可用,如下:

當然,連接高可用的功能是由 Driver 實現的。

3. 副本集

副本集又是另一個話題,實質上除了前面架構圖所體現的,副本集可以作爲 Shard Cluster 中的一個Shard(片)之外,對於規模較小的業務來說,也可以使用一個單副本集的方式進行部署。

MongoDB 的副本集採取了一主多從的結構,即一個Primary Node + N* Secondary Node的方式,數據從主節點寫入,並複製到多個備節點。

典型的架構如下:

利用副本集,我們可以實現:

  • 數據庫高可用,主節點宕機後,由備節點自動選舉成爲新的主節點;
  • 讀寫分離,讀請求可以分流到備節點,減輕主節點的單點壓力。

請注意,讀寫分離只能增加集羣"讀"的能力,對於寫負載非常高的情況卻無能爲力。

對此需求,使用分片集羣並增加分片,或者提升數據庫節點的磁盤IO、CPU能力可以取得一定效果。

2.3.1 選舉

MongoDB 副本集通過 Raft 算法來完成主節點的選舉,這個環節在初始化的時候會自動完成,如下面的命令:

config = {
    _id : "my_replica_set",
    members : [
        {_id : 0, host : "rs1.example.net:27017"},
        {_id : 1, host : "rs2.example.net:27017"},
        {_id : 2, host : "rs3.example.net:27017"},
  ]
}
rs.initiate(config)

initiate 命令用於實現副本集的初始化,在選舉完成後,通過 isMaster()命令就可以看到選舉的結果:

> db.isMaster()
 
{
    "hosts" : [
    "192.168.100.1:27030",
    "192.168.100.2:27030",
    "192.168.100.3:27030"
    ],
    "setName" : "myReplSet",
    "setVersion" : 1,
    "ismaster" : true,
    "secondary" : false,
    "primary" : "192.168.100.1:27030",
    "me" : "192.168.100.1:27030",
    "electionId" : ObjectId("7fffffff0000000000000001"),
    "ok" : 1
}

受 Raft算法的影響,主節點的選舉需要滿足"大多數"原則,可以參考下表:

投票成員數 大多數
1 1
2 2
3 2
4 3
5 3

因此,爲了避免出現平票的情況,副本集的部署一般採用是基數個節點,比如3個,正所謂三人行必有我師..

2.3.2 心跳

在高可用的實現機制中,心跳(heartbeat)是非常關鍵的,判斷一個節點是否宕機就取決於這個節點的心跳是否還是正常的。
副本集中的每個節點上都會定時向其他節點發送心跳,以此來感知其他節點的變化,比如是否失效、或者角色發生了變化。
利用心跳,MongoDB 副本集實現了自動故障轉移的功能,如下圖:

默認情況下,節點會每2秒向其他節點發出心跳,這其中包括了主節點。 如果備節點在10秒內沒有收到主節點的響應就會主動發起選舉。

此時新一輪選舉開始,新的主節點會產生並接管原來主節點的業務。 整個過程對於上層是透明的,應用並不需要感知,因爲 Mongos 會自動發現這些變化。

如果應用僅僅使用了單個副本集,那麼就會由 Driver 層來自動完成處理。

2.3.3 複製

主節點和備節點的數據是通過日誌(oplog)複製來實現的,這很類似於 mysql 的 binlog。

在每一個副本集的節點中,都會存在一個名爲local.oplog.rs的特殊集合。 當 Primary 上的寫操作完成後,會向該集合中寫入一條oplog,而 Secondary 則持續從 Primary 拉取新的 oplog 並在本地進行回放以達到同步的目的。

下面,看看一條 oplog 的具體形式:

{
"ts" : Timestamp(1446011584, 2),
"h" : NumberLong("1687359108795812092"),
"v" : 2,
"op" : "i",
"ns" : "test.nosql",
"o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "100" }
}

其中的一些關鍵字段有:

  • ts 操作的 optime,該字段不僅僅包含了操作的時間戳(timestamp),還包含一個自增的計數器值。
  • h 操作的全局唯一表示
  • v oplog 的版本信息
  • op 操作類型,比如 i=insert,u=update..
  • ns 操作集合,形式爲 database.collection
  • o 指具體的操作內容,對於一個 insert 操作,則包含了整個文檔的內容

MongoDB 對於 oplog 的設計是比較仔細的,比如:

  • oplog 必須保證有序,通過 optime 來保證。
  • oplog 必須包含能夠進行數據回放的完整信息。
  • oplog 必須是冪等的,即多次回放同一條日誌產生的結果相同。
  • oplog 集合是固定大小的,爲了避免對空間佔用太大,舊的 oplog 記錄會被滾動式的清理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章