mongodb利用rs,實現ha和備份

來源:互聯網
上載者:User

mongodb最簡單的部署方式,是單節點部署。國慶前找了台8核,48G RAM的server,在100+並發的壓力下跑了一個星期,也沒有探索資料丟失和server負載過大的情況


但是單節點部署還是有一些問題,第一是無法做HA,如果該mongod down掉,或者部署的server down掉,應用就無法工作;第二是不利於備份,因為在備份的時候,會給mongod額外的負擔,有可能影響業務;第三是無法做讀寫分離。所以在生產環境下,依然考慮使用叢集部署


mongod支援的叢集部署方式有3種:

1、master-slave

2、replica set

3、sharding


master-slave可以解決備份的問題,但是無法透明地HA,所以也不大好;sharding是mongo的一個亮點特性,可以自動分區。但是根據我的測試結果,在單集合達到2000萬條資料的門檻之後,sharding才開始體現出效能優勢,而sharding的資料是分布式的,所以備份會比較複雜,而且也需要更多的伺服器,不利於成本。所以最後我考慮,還是使用replica set方式的叢集部署比較合適。可以解決HA,備份,讀寫分離的問題


基本上採用官網推薦的這種TOPO:


理想情況下,最好是3個mongod執行個體分別部署在單獨的server上,這樣就需要;出於成本考慮,也可以把2台secondary部署在同一台server上,primary由於要處理讀寫請求(讀寫暫不分離),需要很多記憶體,並且考慮HA因素,所以primary是需要保證獨佔一台server比較好,這樣就一共需要

一、叢集配置方式


1、以-replset方式啟動mongod

也可以用命令列啟動,但是不利於管理,所以建議採用--config參數來啟動,設定檔如下:

port=2222
bind_ip=127.0.0.1
dbpath=/home/zhengshengdong/mongo_dbs/db1
fork=true
replSet=rs0
logpath=/home/zhengshengdong/mongo_log/log1.txt
logappend=true
journal=true

./mongod --config /home/zhengshengdong/mongo_confs/mongo1.conf

然後如法炮製,啟動另外2個mongod執行個體

2、初始化replica set,並添加secondary

用./mongo --port 2222連上準備作為primary的mongod執行個體,然後依次執行以下命令

rs.initiate()rs.conf()rs.add("host1:port")rs.add("host2:port")
3、檢查

在primary執行個體上執行

rs.status()
應該能看到類似的效果


二、驗證HA情境

用java driver寫了以下代碼來做驗證

public static void main(String[] args) throws UnknownHostException {ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();final MongoClient client = MongoConnectionHelper.getClient();client.setWriteConcern(WriteConcern.REPLICA_ACKNOWLEDGED);Runnable task = new Runnable() {private int i = 0;@Overridepublic void run() {DB db = client.getDB("yilos");DBCollection collection = db.getCollection("test");DBObject o = new BasicDBObject("name", "MongoDB" + i).append("count", i);try {collection.insert(o);} catch (Exception exc) {exc.printStackTrace();}i++;}};executor.scheduleWithFixedDelay(task, 1, 1, TimeUnit.SECONDS);}
每隔1秒往叢集中寫入一條資料,然後手動把primary shutdown,觀察發現jvm console給出提示:

警告: Primary switching from zhengshengdong-K43SA/127.0.0.1:2222 to mongodb.kyfxbl.net/127.0.0.1:4444

這條訊息說明,mongo自動處理了primary的切換,對於應用來說是透明的。然後查看mongo中的記錄,發現在切換完成以後,寫入操作確實繼續了


可以看到,在2926和2931之間,進行中primary倒換,在完成之後,應用可以繼續寫入資料

但是明顯可以看到,中間2927,2928,2929,2930這4條資料丟失了(mongo的primary倒換大約需要3-5秒時間),對於業務來說,雖然時間不長,但是如果因此丟失了業務資料,也是不能接受的

接下來再仔細看下這段時間內代碼拋出的異常:

com.mongodb.MongoException$Network: Write operation to server mongodb.kyfxbl.net/127.0.0.1:4444 failed on database yilos
at com.mongodb.DBTCPConnector.say(DBTCPConnector.java:153)
at com.mongodb.DBTCPConnector.say(DBTCPConnector.java:115)
at com.mongodb.DBApiLayer$MyCollection.insert(DBApiLayer.java:248)
at com.mongodb.DBApiLayer$MyCollection.insert(DBApiLayer.java:204)
at com.mongodb.DBCollection.insert(DBCollection.java:76)
at com.mongodb.DBCollection.insert(DBCollection.java:60)
at com.mongodb.DBCollection.insert(DBCollection.java:105)
at com.yilos.mongo.HATester$1.run(HATester.java:36)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:98)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:204)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
Caused by: java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
at org.bson.io.PoolOutputBuffer.pipe(PoolOutputBuffer.java:129)
at com.mongodb.OutMessage.pipe(OutMessage.java:236)
at com.mongodb.DBPort.go(DBPort.java:133)
at com.mongodb.DBPort.go(DBPort.java:106)
at com.mongodb.DBPort.findOne(DBPort.java:162)
at com.mongodb.DBPort.runCommand(DBPort.java:170)
at com.mongodb.DBTCPConnector._checkWriteError(DBTCPConnector.java:100)
at com.mongodb.DBTCPConnector.say(DBTCPConnector.java:142)
... 16 more

可以發現,實際上這並不是mongo的問題,而是這段測試代碼的BUG引發的:

Runnable task = new Runnable() {private int i = 0;@Overridepublic void run() {DB db = client.getDB("yilos");DBCollection collection = db.getCollection("test");DBObject o = new BasicDBObject("name", "MongoDB" + i).append("count", i);try {collection.insert(o);} catch (Exception exc) {exc.printStackTrace();}i++;}};
問題就出在這裡,try語句中捕獲到了MongoException,但是我寫的這段代碼並沒有進行處理,只是簡單地列印出異常,然後把i自增後,進入下一次迴圈。

從中也可以判斷出,mongo提供的java driver,並不會把失敗的write操作暫存在隊列中,稍後重試;而是拋棄這次寫操作,拋出異常讓用戶端自行處理。我覺得這個設計也是沒問題的,但是用戶端一定要進行處理才行。由於後續是使用js driver,所以對這段代碼就不繼續深究了,在js driver中再仔細研究。關鍵是要處理mongo exception,以及設定較進階別的write concern。replica set本身的HA機制是可行的

三、備份方案

採用2級備份:

第一層是叢集部署提供的天然備份,由於存在2個secondary node,會和primary始終保持同步,因此在任何時候,叢集都有2份完整的資料鏡像副本

第二層則是使用mongo提供的mongodump和fsync工具,通過手工執行或者指令碼的方式,在業務負載低(淩晨3點)的時候,擷取關鍵collection的每日副本。備份採集也在secondary上執行,不給primary額外的壓力

採集的備份,儲存到本地或者其他的server,避免server的儲存損壞,造成資料和備份全部丟失

同時,在啟動mongod的時候,開啟journal參數,這樣在極端情況下(上述2種備份都失敗),還可以通過oplog進行手工恢複

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.