讀古今文學網 > MongoDB實戰 > 9.5 生產環境中的分片 >

9.5 生產環境中的分片

在生產環境裡部署分片集群時,面前會出現很多選擇和挑戰。這裡我會描述幾個推薦的部署拓撲,針對常見的部署問題做出解答。我們隨後還會考慮一些服務器管理方面的問題,包括監控、備份、故障轉移和恢復。

9.5.1 部署與配置

一開始很難搞定分片集群的部署與配置,下文是一份指南,介紹了輕鬆組織並配置集群的方法。

1. 部署拓撲

要運行示例MongoDB分片集群,你一共要啟動九個進程(每個副本集三個mongod,外加三個配置服務器)。乍一看,這個數字有點嚇人。一開始用戶會假設在生產環境裡運行兩個分片的集群要有九台獨立的機器。幸運的是,實際需要的機器要少很多,看一下集群中各組件所要求的資源就能知道為什麼了。

首先考慮一下副本集,每個成員都包含分片的完整數據副本,可能是主節點,也可能是從節點。這些進程總是要求有足夠的磁盤空間來保存數據,要有足夠的內存高效地提供服務。因此,複製mongod是分片集群中最資源密集型的進程,必須佔用獨立的機器。

那副本集的仲裁節點呢?這些進程只保存副本集的配置數據,這些數據就放在一個文檔裡。所以,仲裁節點開銷很少,當然也就不需要自己的服務器了。

接下來是配置服務器,它們同樣只保存相對較少的數據。舉例來說,配置服務器上管理示例副本集的數據一共也就大約30 KB。如果假設這些數據會隨著分片集群數據的增長而線性增長,那麼1 TB的分片集群可能僅會對應30 MB數據。1也就是說配置服務器同樣不需要有自己的機器。但是,考慮到配置服務器所扮演的重要角色,一些用戶更傾向於為它們提供一些機器(或虛擬機)。

1. 這是一個相當保守的估計,真實值可能會小得多。

根據你對副本集和分片集群的瞭解,可以列出部署分片集群的最低要求。

  1. 副本集的每個成員,無論是完整的副本節點還是仲裁節點,都需要放在不同的機器上。

  2. 每個用於複製的副本集成員都需要有自己的機器。

  3. 副本集的仲裁節點是很輕量級的,和其他進程共用一台機器就可以了。

  4. 配置服務器也可以選擇與其他進程共用一台機器。唯一的硬性要求是配置集群中的所有配置服務器都必須放在不同的機器上。

你可能感覺要滿足這些規則會引起邏輯問題。我們將運用這些規則:針對示例的兩分片集群,你會看到兩個合理的部署拓撲。第一個拓撲只需要四台機器,圖9-4里描繪了進程的分佈情況。

圖9-4 部署在四台機器上的兩分片集群

這個配置滿足了剛才所說的所有規則。在每台機器上佔主導地位的是各分片的複製節點。剩下的進程經過了精心安排,所有的三個配置服務器和每個副本集的仲裁節點都部署在了不同的機器上。說起容錯性,該拓撲能容忍任何一台機器發生故障。無論哪台機器發生了故障,集群都能繼續處理讀寫請求。如果發生故障的機器正好運行了一個配置服務器,那麼所有的塊拆分和遷移都會暫停。2幸運的是,暫停分片操作基本不會影響分片集群的工作;在損失的機器恢復後,就能進行拆分和遷移了。

2. 在發生任何分片操作時,所有的三台配置服務器都必須在線。

這是兩分片集群的最小推薦配置。但是,那些要求最高可用性和最快恢復途徑的應用程序需要一些更強健的東西。正如上一章裡討論的那樣,包含兩個副本和一個仲裁節點的副本集在恢復時是很脆弱的。如果有三個節點,就能降低恢復時的脆弱程度,還能讓你在從數據中心裡部署一個節點,用於災難恢復。圖9-5是一個強壯的兩分片集群拓撲。每個分片都包含三節點的副本集,每個節點都包含數據的完整副本。為了進行災難恢復,從每個分片裡抽一個節點,加上一個配置服務器,部署在從數據中心;要保證那些節點不會變成主節點,可以將它們的優先級設置為0。

圖9-5 部署在兩個數據中心、六台機器上的兩分片集群

用了這個配置,每個分片都會被複製兩次,而非僅一次。此外,當主數據中心發生故障時,從數據中心擁有重建分片集群所需的全部數據。

數據中心故障

最有可能發生的數據中心故障是電力中斷。在沒有開啟Journaling日誌的情況下運行MongoDB服務器時,電力中斷就意味著非正常關閉MongoDB服務器,可能會損壞數據文件。發生這種故障時,唯一可靠的恢復途徑是數據庫修復,一個保證停機時間的漫長過程。

大多數用戶只將整個集群部署在一個數據中心裡,這對大量應用程序來說都沒問題。這種情況下的主要預防措施是,至少在每個分片的一個節點以及一台配置服務器上開啟Journaling日誌。在電力供應恢復時,這能極大地提高恢復速度。第10章裡會涉及Journaling日誌的相關內容。

儘管如此,一些故障更加嚴重。電力中斷有時能持續幾天。洪水、地震,以及其他自然災害能完全摧毀數據中心。對於那些想在此類故障中進行快速恢復的用戶而言,他們必須跨多個數據中心部署分片集群。

哪種分片拓撲最適合你的應用程序,這種決策總是基於一系列與你能容忍的停機時間有關的考慮,比如根據MTR(Mean Time to Recovery,平均恢復時間)進行評估。考慮潛在的故障場景,並模擬它們。如果一個數據中心發生故障,考慮一下它對應用程序(或業務)的影響。

2. 配置注意事項

下面是一些與配置分片集群相關的注意事項。

  • 估計集群大小

用戶經常想知道要部署多少個分片,每個分片應該有多大。當然,這個問題的答案取決於所在的環境。如果是部署在亞馬遜的EC2上,在超過最大的可用實例前都不應該進行分片。在本書編寫時,最大的EC2節點有68 GB內存。如果運行在自己的硬件上,你還可以擁有更大的機器。在數據量達到100 GB之前都不進行分片,這是很合理的。

當然,每增加一個分片都會引入額外的複雜性,每個分片都要求進行複製。所以說,少數大分片比大量小分片要好。

  • 對現有集合進行分片

你可以對現有集合進行分片,如果花了很多時間才將數據分佈到各分片裡,請不要大驚小怪的。每次只能做一輪均衡,遷移過程中每分鐘只能移動大約100~200 MB數據。因此,對一個50 GB的集合進行分片大約需要八個小時,其中還可能牽涉一定的磁盤活動。此外,在對這樣的大集合進行初始分片時,可能還要手動拆分以加速分片過程,因為拆分是由插入觸發的。

說到這裡,應該已經很清楚了,在最後時刻對一個集合進行分片並不是處理性能問題的好辦法。如果你計劃在未來某個時刻對集合進行分片,考慮到可以預見的性能下降,應該提前進行分片。

  • 在初始加載時預拆分塊

如果你有一個很大的數據集需要加載到分片集合裡,並且知道數據分佈的規律,那麼可以通過對塊的預拆分和預遷移節省很多時間。舉個例子,假設你想要把電子錶格導入到一個新的MongoDB分片集群裡。可以在導入時先拆分塊,隨後將它們遷移到分片裡,借此保證數據是均勻分佈的。你能用splitmoveChunk命令實現這個目標,它們的輔助方法分別是sh.splitAtsh.moveChunks

下面是一個手動塊拆分的例子。你發出split命令,指定你想要的集合,隨後指明拆分點:

> sh.splitAt( "cloud-docs.spreadsheets",
{ "username" : "Chen", "_id" : ObjectId("4d6d59db1d41c8536f001453") })
  

命令運行時會定位到某個塊,而這個塊邏輯上包含usernameChen並且_idObjectId ("4d6d59db1d41c8536f001453")的文檔3。該命令隨後會根據這個點來拆分塊,最後得到兩個塊。你能像這樣繼續拆分,直到擁有數據良好分佈的塊集合。你還要確保創建足夠數量的塊,讓平均塊大小保持在64 MB的拆分閾值以內。所以,如果想加載1GB數據,應該計劃創建大約20個塊。

3. 注意,並不需要存在這樣一個文檔。事實上,你正在對一個空集合做拆分。

第二步是確定所有分片都擁有數量相當的塊。因為所有的塊最初都在一個分片上,你需要移動它們。可以使用moveChunk命令來移動塊。輔助方法能簡化這個過程:

> sh.moveChunk("cloud-docs.spreadsheets", {username: "Chen"}, "shardB")
  

這句語句的意思是把邏輯上包含文檔{username: "Chen"}的塊移動到分片B上。

9.5.2 管理

我將簡單介紹一些分片管理的知識,讓本章內容更充實一些。

1. 監控

分片集群是整個體系中比較複雜的一塊,正因此,你應該嚴密監控它。在任何mongos上都可以運行serverStatuscurrentOp命令,命令的輸出能反映所有分片的聚合統計信息。在下一章裡我將更具體地討論這些命令。

除了聚合服務器的統計信息,你還希望能監控塊的分佈和各個塊的大小。正如在示例集群中看到的那樣,所有的信息都保存在config數據庫裡。如果發現不平衡的塊或者未經確認的塊增長,可以通過splitmoveChunk命令處理這些情況。或者,也可以查看日誌,檢查均衡操作是否出於某些原因被停止了。

2. 手動分區

有一些情況下,你可能希望手動對線上分片集群的塊進行拆分和遷移。例如,自MongoDB v2.0起,均衡器並不會直接考慮某個分片的負載。很明顯,一個分片的寫越多,它的塊就越大,最終就會造成遷移。但是,不難想像你可以通過遷移塊來減輕分片的負載。moveChunk命令在這種情況下同樣很有幫助。

3. 增加一個分片

如果你決定要增加更多容量,可以使用與先前一樣的方法向現有集群添加新的分片:

sh.addShard("shard-c/rs1.example.net:27017,rs2.example.net:27017")
  

使用這種方式增加容量時,要注意向新分片遷移數據所花費的時間。如前所述,預計的遷移速度是每分鐘100~200 MB。這意味著如果需要向分片集群增加容量,你應該早在性能下降以前就開始行動。要決定何時需要添加新分片,考慮一下數據集的增長速率。很明顯,你希望將索引和工作集保持在內存裡。因此,最好在索引和工作集達到現有分片內存90%之前的幾個星期就開始計劃添加新分片。

如果你不願意採用此處描述的安全途徑,那麼就會將自己置身於痛苦之中。一旦內存裡容納不下索引和工作集,應用程序就會中止運行,尤其是那些要求很高讀寫吞吐量的應用程序。問題在於數據庫需要在磁盤和內存之間置換分頁,這會降低讀寫速度,後台日誌操作無法放入讀寫隊列。從這點來看,增加容量是件困難的事,因為分片之間的塊遷移會增加現有分片的讀負載。很明顯,在數據庫已經超載之時,你最後想做的還是增加負載。

說了這麼多,只是為了強調你應該監控集群,在真正有需要之前就增加容量。

4. 刪除分片

在一些很少見的情況下,你可能會想刪除一個分片。可以通過removeshard命令進行刪除:

> use admin
> db.runCommand({removeshard: "shard-1/arete:30100,arete:30101"})
{
  "msg" : "draining started successfully",
  "state" : "started",
  "shard" : "shard-1-test-rs",
  "ok" : 1 }
  

命令的響應說明正在從分片中移除塊,它們將被重新分配到其他分片上。可以再次運行該命令來檢查刪除過程的狀態:

> db.runCommand({removeshard: "shard-1/arete:30100,arete:30101"})
{
  "msg" : "draining ongoing",
  "state" : "ongoing",
  "remaining" : {
    "chunks" : 376,
    "dbs" : 3
  },
  "ok" : 1 }
  

一旦分片被清空,你還要確認將要刪除的分片不是數據庫的主分片。可以通過查詢config.databases集合的分片成員進行檢查:

> use config
> db.databases.find
  { "_id" : "admin", "partitioned" : false, "primary" : "config" }
  { "_id" : "cloud-docs", "partitioned" : true, "primary" : "shardA" }
  { "_id" : "test", "partitioned" : false, "primary" : "shardB" }
  

從中可以看到,cloud-docs數據庫屬於shardA,而test數據庫則屬於shardB。因為正在刪除shardB,所以需要改變test數據庫的主節點。為此,可以使用moveprimary命令:

> db.runCommand({moveprimary: "test", to: "shard-0-test-rs" });
  

對於每個主節點是將要刪除的分片的數據庫,運行該命令。隨後,再次對每個已清空的分片運行removeshard命令:

> db.runCommand({removeshard: "shard-1/arete:30100,arete:30101"})
{ "msg": "remove shard completed successfully",
  "stage": "completed",
  "host": "arete:30100",
  "ok" : 1
}
  

一旦看到刪除完成,就可以安全地將已刪除的分片下線了。

5. 集合去分片

雖然可以刪除一個分片,但是沒有正式的途徑去掉集合的分片。如果真的需要這麼做,最好的選擇是導出集合,再用一個不同的名字將數據恢復到一個新的集合裡。4然後就能把已經導出數據的分片集合刪掉了。例如,假設foo是一個分片集合,你必須用mongodump連接mongos來導出foo集合的數據:

4. 下一章將涉及用來進行導出和恢復的工具——mongodumpmongorestore

$ mongodump -h arete --port 40000 -d cloud-docs -c foo
connected to: arete:40000
DATABASE: cloud-docs to dump/cloud-docs
  cloud-docs.foo to dump/cloud-docs/foo.bson
     100 objects
  

該命令能把該集合導出到一個名為foo.bson的文件裡,隨後再用mongorestore來恢復該文件:

$ mongorestore -h arete --port 40000 -d cloud-docs -c bar
Tue Mar 22 12:06:12 dump/cloud-docs/foo.bson
Tue Mar 22 12:06:12 going into namespace [cloud-docs.bar]
Tue Mar 22 12:06:12 100 objects found
  

將數據移動到未分片集合之後,就可以隨意刪除舊的分片集合foo了。

6. 備份分片集群

要備份分片集群,你需要配置數據以及每個分片數據的副本。有兩種途徑來獲得這些數據。第一種是使用mongodump工具,從一個配置服務器導出數據,隨後再從每個單獨的分片裡導出數據。此外,也可以通過mongos路由器運行mongodump,一次性導出整個分片集合的數據,包括配置數據庫。這種策略的主要問題是分片集合的總數據可能太大了,以至於無法導出到一台機器上。

另一種常用的備份分片集群的方法是從每個分片的一個成員裡複製數據文件,再從一台配置服務器中複製數據文件。下一章裡會介紹這種備份獨立mongod進程和副本集的方法。你只要在每個分片和一台配置服務器上執行這個過程就可以了。

無論選擇哪種備份方式,都需要確認在備份系統時沒有塊處在移動過程中。也就是說要停止均衡器進程。

  • 停止均衡器

到目前為止,禁用均衡器就是upsert一個文檔到config數據庫的settings集合:

> use config
> db.settings.update({_id: "balancer"}, {$set: {stopped: true}}, true);
  

這裡一定要小心:更新了配置之後,均衡器可能仍在工作。在備份集群之前,你還需要再次確認均衡器完成了最後一輪均衡。最好的方法就是檢查locks集合,找到_idbalancer的條目,確認它的狀態是0。下面是一個例子:

> use config
> db.locks.find({_id: "balancer"})
{ "_id" : "balancer", "process" : "arete:40000:1299516887:1804289383",
  "state" : 1,
  "ts" : ObjectId("4d890d30bd9f205b29eda79e"),
  "when" : ISODate("2011-03-22T20:57:20.249Z"),
  "who" : "arete:40000:1299516887:1804289383:Balancer:846930886",
  "why" : "doing balance round"
}
  

任何大於0的狀態值都說明均衡仍在進行中。process字段顯示了負責組織協調均衡的mongos所運行在的計算機的主機名和端口,本例中,主機是arete:40000。如果在修改配置之後,均衡器始終沒有停止,你應該檢查負責均衡的mongos的日誌,查找錯誤。

在均衡器停止之後,就可以安全地開始備份了。備份完成後,不要忘了重新啟動均衡器。為此,可以重新設置stopped的值:

> use config
> db.settings.update({_id: "balancer"}, {$set: {stopped: false}}, true);
  

為了簡化與均衡器相關的一些操作,MongoDB v2.0引入了一些Shell輔助方法。例如,可以用sh.setBalancerState來啟動和停止均衡器:

> sh.setBalancerState(false)
  

這相當於調整settings集合中的stopped值。用這種方式禁用均衡器之後,可以不停地調用sh.isBalancerRunning,直到均衡器停下為止。

7. 故障轉移與恢復

雖然我們已經講過了一般的副本集故障,但還是有必要提一下分片集群的潛在故障點和恢復的最佳實踐。

  • 分片成員故障

每個分片都由一個副本集組成。因此,如果這些副本集中的任一成員發生故障,從節點就會被選舉為主節點,mongos進程會自動連接到該節點上。第8章描述了恢復副本集故障成員的具體步驟。選擇哪種方法依賴於成員是何故障,但是不管怎麼樣,恢復的指南都是一樣的,無論副本集是否是分片集群的組成部分。

如果發現副本集在故障轉移之後有什麼不正常的表現,可以通過重啟所有mongos進程重置系統,這能保證適當連接都指向新的副本集。此外,如果發現均衡器不工作了,就檢查config數據庫的locks集合,找到process字段指向之前主節點的條目。如果有這樣的條目,鎖文檔已經舊了,你可以安全地手動刪除該文檔。

  • 配置服務器故障

一個分片集群要有三台配置服務器才能正常運作,其中最多能有兩台發生故障。無論何時,當配置服務器數量少於三台,剩餘的配置服務器會變為只讀狀態,所有的拆分和均衡操作都會停止。請注意,這對整個集群沒有負面影響,集群的讀寫仍能正常進行,當所有三台配置服務器都恢復之後,均衡器將從它停止的地方重新開始工作。

要恢復配置服務器,從現有的配置服務器把數據文件複製到發生故障的機器上,隨後重啟服務器。4

4. 和往常一樣,在複製任何數據文件之前,確保已經鎖定了mongod(第10章會做描述)或者正常關閉了該進程。不要在服務器仍在運行時複製任何數據文件。

  • mongos故障

要是mongos進程發生故障,沒有什麼好擔心的。如果mongos運行在應用服務器上,它發生故障了,那麼很有可能你的應用程序服務器也發生故障了。這時的恢復就是簡單地恢復服務器。

無論mongos出於什麼原因發生故障,進程本身都沒有自己的狀態。這意味著恢復mongos就是簡單地重啟進程,在配置服務器上指向它而已。