1. 程式人生 > >MongoDB副本集配置和資料遷移實戰

MongoDB副本集配置和資料遷移實戰

MongoDB副本集配置和資料遷移實戰

https://gitee.com/et/ops/blob/master/MongoDB副本集配置和資料遷移實戰.md

環境:Ubuntu 16.04, MongoDB 3.6

基本概念

MongoDB 的副本集就是有自動故障恢復功能的 MongoDB 主從叢集。由於 MongoDB 的主從複製功能不支援高可用,所以從 3.2 版本開始已經被廢棄了,轉而用副本集來代替。一個副本集總會有一個活躍節點(Primary)和若干個備份節點(Secondary),還有一個可選的一個仲裁者(Arbiter)節點來實現HA中的故障切換。

準備工作

配置副本集 Primary 和 Secondary 節點

  • 建立資料目錄
$ mkdir -p /mnt/mongodb/replset
  • 啟動名為“my-repl”的副本集,埠為27017,繫結到任意IP(也可以指定IP)
$ mongod --dbpath /mnt/mongodb/replset --port 27017 --replSet "my-repl" --bind_ip_all
  • 初始化副本集
    • 用mongo客戶端連線 Primary 節點:
    $ mongo
    
    • 執行初始化指令碼來建立副本集:
    > rs.initiate({
     _id:"my-repl",
      members:[
        {_id:0, host:"192.168.10.58:27017"},
        {_id:1, host:"192.168.10.59:27017"}
      ]
    });
    
    輸出結果:
    {
    	"ok" : 1,
    	"operationTime" : Timestamp(1523237919, 1),
    	"$clusterTime" : {
    		"clusterTime" : Timestamp(1523237919, 1),
    		"signature" : {
    			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
    			"keyId" : NumberLong(0)
    		}
    	}
    }
    
    • 檢視配置結果
    > rs.conf();
    {
    	"_id" : "my-repl",
    	"version" : 1,
    	"protocolVersion" : NumberLong(1),
    	"members" : [
    		{
    			"_id" : 0,
    			"host" : "192.168.10.58:27017",
    			"arbiterOnly" : false,
    			"buildIndexes" : true,
    			"hidden" : false,
    			"priority" : 1,
    			"tags" : {
    
    			},
    			"slaveDelay" : NumberLong(0),
    			"votes" : 1
    		},
    		{
    			"_id" : 1,
    			"host" : "192.168.10.59:27017",
    			"arbiterOnly" : false,
    			"buildIndexes" : true,
    			"hidden" : false,
    			"priority" : 1,
    			"tags" : {
    
    			},
    			"slaveDelay" : NumberLong(0),
    			"votes" : 1
    		}
    	],
    	"settings" : {
    		"chainingAllowed" : true,
    		"heartbeatIntervalMillis" : 2000,
    		"heartbeatTimeoutSecs" : 10,
    		"electionTimeoutMillis" : 10000,
    		"catchUpTimeoutMillis" : -1,
    		"catchUpTakeoverDelayMillis" : 30000,
    		"getLastErrorModes" : {
    
    		},
    		"getLastErrorDefaults" : {
    			"w" : 1,
    			"wtimeout" : 0
    		},
    		"replicaSetId" : ObjectId("5acac41fded47067da446ddd")
    	}
    }
    
    配置過程非常簡單,可以看到在副本整合員中有0和1兩個節點,此時主從兩個伺服器已經可以工作了,伺服器0(Primary)有任何資料的變化都會同步到伺服器1(Secondary)上。但是,此時的副本集只是提供了資料備份的功能,並不能達到高可用。如果要達到這一點,那麼需要配置一個仲裁者節點(Arbiter)

配置仲裁者(Arbiter)

仲裁者在 Primary 節點發生故障時,參與副本集的選舉投票決定哪個副本成為 Primary 節點。仲裁節點不儲存資料也不會成為 Primary 節點。

  • 仲裁者通常不部署在大磁碟空間的伺服器上,因此為了最小化預設建立資料,修改配置:

    $ vim /etc/mongod.conf
    
    storage.journal.enabled=false
    storage.mmapv1.smallFiles = true.
    
  • 建立仲裁者目錄並啟動服務

    $ mkdir /mnt/mongodb/arbiter
    $ mongod --port 27017 --dbpath /mnt/mongodb/arbiter --replSet 'my-repl' --bind_ip_all
    
  • 把仲裁者新增到副本集中

    • 連線至 Primary 伺服器

      $mongo --host 192.168.10.58
      
      my-repl:PRIMARY> rs.addArb("192.168.10.57:27017")
      {
      	"ok" : 1,
      	"operationTime" : Timestamp(1523326877, 1),
      	"$clusterTime" : {
      		"clusterTime" : Timestamp(1523326877, 1),
      		"signature" : {
      			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
      			"keyId" : NumberLong(0)
      		}
      	}
      }
      
  • 檢視副本集的效果:

    >rs.status();
    my-repl:PRIMARY> rs.status();
    {
    	"set" : "my-repl",
    	"date" : ISODate("2018-04-10T02:21:44.826Z"),
    	"myState" : 1,
    	"term" : NumberLong(2),
    	"heartbeatIntervalMillis" : NumberLong(2000),
    	"optimes" : {
    		"lastCommittedOpTime" : {
    			"ts" : Timestamp(1523326895, 1),
    			"t" : NumberLong(2)
    		},
    		"readConcernMajorityOpTime" : {
    			"ts" : Timestamp(1523326895, 1),
    			"t" : NumberLong(2)
    		},
    		"appliedOpTime" : {
    			"ts" : Timestamp(1523326895, 1),
    			"t" : NumberLong(2)
    		},
    		"durableOpTime" : {
    			"ts" : Timestamp(1523326895, 1),
    			"t" : NumberLong(2)
    		}
    	},
    	"members" : [
    		{
    			"_id" : 0,
    			"name" : "192.168.10.58:27017",
    			"health" : 1,
    			"state" : 1,
    			"stateStr" : "PRIMARY",
    			"uptime" : 2891,
    			"optime" : {
    				"ts" : Timestamp(1523326895, 1),
    				"t" : NumberLong(2)
    			},
    			"optimeDate" : ISODate("2018-04-10T02:21:35Z"),
    			"electionTime" : Timestamp(1523324284, 1),
    			"electionDate" : ISODate("2018-04-10T01:38:04Z"),
    			"configVersion" : 2,
    			"self" : true
    		},
    		{
    			"_id" : 1,
    			"name" : "192.168.10.59:27017",
    			"health" : 1,
    			"state" : 2,
    			"stateStr" : "SECONDARY",
    			"uptime" : 2624,
    			"optime" : {
    				"ts" : Timestamp(1523326895, 1),
    				"t" : NumberLong(2)
    			},
    			"optimeDurable" : {
    				"ts" : Timestamp(1523326895, 1),
    				"t" : NumberLong(2)
    			},
    			"optimeDate" : ISODate("2018-04-10T02:21:35Z"),
    			"optimeDurableDate" : ISODate("2018-04-10T02:21:35Z"),
    			"lastHeartbeat" : ISODate("2018-04-10T02:21:43.080Z"),
    			"lastHeartbeatRecv" : ISODate("2018-04-10T02:21:43.083Z"),
    			"pingMs" : NumberLong(0),
    			"syncingTo" : "192.168.10.58:27017",
    			"configVersion" : 2
    		},
    		{
    			"_id" : 2,
    			"name" : "192.168.10.57:27017",
    			"health" : 1,
    			"state" : 7,
    			"stateStr" : "ARBITER",
    			"uptime" : 27,
    			"lastHeartbeat" : ISODate("2018-04-10T02:21:43.079Z"),
    			"lastHeartbeatRecv" : ISODate("2018-04-10T02:21:42.088Z"),
    			"pingMs" : NumberLong(0),
    			"configVersion" : 2
    		}
    	],
    	"ok" : 1,
    	"operationTime" : Timestamp(1523326895, 1),
    	"$clusterTime" : {
    		"clusterTime" : Timestamp(1523326895, 1),
    		"signature" : {
    			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
    			"keyId" : NumberLong(0)
    		}
    	}
    }
    

    可以看到狀態顯示:伺服器0為 Primary, 伺服器1為 Secondary, 伺服器2為 Arbiter。
    此時帶有高可用的副本集已經配置完成,Arbiter 會監控 Primary 節點的執行情況,如果伺服器0發生了宕機,那麼仲裁者 Arbiter 節點會發起選舉,最終選取多個 Secondary 中的某一個來作為 Primary 節點。我們測試的架構中只有一個 Secondary 節點,那麼此伺服器1就會成為 Primary。而當伺服器0恢復工作時,它會被當成 Secondary 來執行。

  • 副本優先順序
    副本集會在 Primary 節點出現故障時把 Secondary 提升為 Primary,但是很多情況下 Secondary 只是作為備用節點,我們不希望它長期作為 Primary 節點執行。那麼為了達到這個目的,我們修改副本的優先順序。

    • 連線 Primary 節點,執行以下指令碼:
    cfg = rs.conf()
    cfg.members[0].priority = 10
    cfg.members[1].priority = 5
    rs.reconfig(cfg)
    {
    	"ok" : 1,
    	"operationTime" : Timestamp(1523411797, 2),
    	"$clusterTime" : {
    		"clusterTime" : Timestamp(1523411797, 2),
    		"signature" : {
    			"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
    			"keyId" : NumberLong(0)
    		}
    	}
    }
    
    • 如果在非 Prmiary 上比如Arbiter上執行,會報下面的錯誤:
    {
      "ok" : 0,
      "errmsg" : "replSetReconfig should only be run on PRIMARY, but my state is ARBITER; use the \"force\" argument to override",
      "code" : 10107,
      "codeName" : "NotMaster"
    }
    
    • 以上操作讓伺服器0的優先順序高於伺服器1,那麼當伺服器0從故障中恢復時,它會重新成為 Primary 節點來提供服務。

資料遷移

在配置副本集之前,你可能已經存在了一個單獨的 MongoDB 例項儲存了一些資料,你需要把原來例項中的資料遷移到新的副本集中。(你也可以一開始就把原來的單個例項配置成副本集的 Primary 節點,然後新增副本來同步資料,此方法不在本文討論範圍之內)

  • 登入原例項所在的伺服器,匯出資料:

    $ mongoexport -h localhost -p 27017 -u xxx -p xxx -d MY_DB_NAME -c MY_COLLECTION_NAME -o MY_COLLECTION_NAME.dmp
    

    在阿里雲上測試了一個匯出資料大約為1.2G大小的集合,匯出效能如下:

    • 時間:3分11秒
    • 匯出1728423條記錄,每秒讀取記錄數=1728423/191=9050條/秒
    • 匯出1264441038位元組,每秒處理位元組數=1264441038/191=6.6M/秒
  • 將匯出的資料檔案 MY_COLLECTION_NAME.dmp 已任何方式(比如scp)同步到 Primary 節點所在的伺服器0上

  • 匯入資料至副本集 Primay 節點

    mongoimport -h my-repl/192.168.10.58:27017 -d MY_DB_NAME -c MY_COLLECTION_NAME --file MY_COLLECTION_NAME.dmp
    

    測試匯入效能

    • 時間:82秒
    • 匯入1728423條記錄,每秒寫入記錄數=1728423/82=21078條/秒
    • 匯入1264441038位元組,每秒處理位元組數=1264441038/82=14.7M/秒

    注意:由於不是相同的伺服器,所以不能通過這個來比較匯入和匯出的效能差別,只能所謂一個參考:

  • 總結:

    • 在 Primary 節點匯入資料後,登入 Secondary 節點檢視,可以看到一百七十多萬條資料全部複製過去了,主從複製成功。
    • 從效能分析結果來看,MongoDB的讀取和寫入效能是比較好的,特別是匯入的同時還要和副本之間同步資料。