【MongoDB】高可用方案之副本集(Replica Set)

神諭丶發表於2016-11-15
Replicat Set比起傳統的Master - Slave結構而言,應用場景更加多,也有了自動failover的能力。
在mongo3.2的文件中明確寫道:
  1. Replica sets replace master-slave replication for most use cases. 
  2. If possible, use replica sets rather than master-slave replication for all new production deployments.

本文將簡單介紹mongodb副本集搭建

〇 副本集結構圖


我在此處大致理解為“MySQL中1主2從+mha_manager”的結構。
Replication透過Oplog實現語句復現。










〇 副本整合員的屬性:
分別為Primary、Secondary(Secondaries)
Primary負責處理所有的write請求,並記錄到oplog(operation log)中。
Secondary負責複製oplog並應用這些write請求到他們自己的資料集中。(有一些類似於MySQL Replication)


所有的副本集都可以處理讀操作,但是需要設定。

最小化的副本集配置建議有三個成員:
1個Primary + 2個Secondary
或者
1個Primary + 1個Secondary + 1個Arbiter(仲裁者)






〇 Arbiter和選舉機制:
Arbiter可以作為副本集的一部分,但它不是一個資料副本,故它不會成為Primary。
Arbiter在Primary不可用的時候,作為一個選舉的角色存在。
如果Arbiter不存在,Primary掛掉的情況下,剩下的兩個Secondary則也會進行選舉。

1個Primary + 2個Secondary的情況下,failover如下:








〇 搭建

實驗結構:
185(Arbiter)(後文用到)
186\187\188(1個Primary、2個Secondary)

在三臺機器上分別建立對應目錄然後啟動mongodb:
  1. mkdir -p /data/mongo_replset
  2. mongod --dbpath=/data/mongo_replset --logpath=/data/mongo_replset/mongo.log --fork --replSet=first_replset

登入任意一臺副本集的mongo shell 
此處用的是192.168.1.187
  1. > use admin

輸入:
  1. > cnf = {_id:"first_replset", members:[
  2.         {_id:1, host:"192.168.1.186:27017"},
  3.         {_id:2, host:"192.168.1.187:27017"},
  4.         {_id:3, host:"192.168.1.188:27017"},
  5.         ]
  6.     }

  7. 輸出結果:
  8. > {
  9.         "_id" : "first_replset",
  10.         "members" : [
  11.                 {
  12.                         "_id" : 1,
  13.                         "host" : "192.168.1.186:27017"
  14.                 },
  15.                 {
  16.                         "_id" : 2,
  17.                         "host" : "192.168.1.187:27017"
  18.                 },
  19.                 {
  20.                         "_id" : 3,
  21.                         "host" : "192.168.1.188:27017"
  22.                 }
  23.         ]
  24. }

初始化配置:
  1. > rs.initiate(cnf);
  2. { "ok" : 1 }
  3. first_replset:OTHER>
  4. first_replset:PRIMARY>
  5. first_replset:PRIMARY>

此時可以發現該mongodb例項已經成為PRIMARY了。

可以看一下這個副本集的各個成員的狀態:
  1. first_replset:PRIMARY> rs.status();
  2. {
  3.         "set" : "first_replset",
  4.         "date" : ISODate("2016-11-15T08:22:10.316Z"),
  5.         "myState" : 2,
  6.         "term" : NumberLong(0),
  7.         "heartbeatIntervalMillis" : NumberLong(2000),
  8.         "members" : [
  9.                 {
  10.                         "_id" : 1,
  11.                         "name" : "192.168.1.186:27017",
  12.                         "health" : 1,
  13.                         "state" : 2,
  14.                         "stateStr" : "SECONDARY",
  15.                         ……………………
  16.                 },
  17.                 {
  18.                         "_id" : 2,
  19.                         "name" : "192.168.1.187:27017",
  20.                         "health" : 1,
  21.                         "state" : 2,
  22.                         "stateStr" : "PRIMARY",
  23.                         ……………………
  24.                 },
  25.                 {
  26.                         "_id" : 3,
  27.                         "name" : "192.168.1.188:27017",
  28.                         "health" : 1,
  29.                         "state" : 2,
  30.                         "stateStr" : "SECONDARY",
  31.                         ……………………
  32.                 }
  33.         ],
  34.         "ok" : 1
  35. }



〇 副本集複製狀態測試
PRIMARY上insert:
  1. [root@192.168.1.187]# mongo 127.0.0.1/test
  2. MongoDB shell version: 3.2.10
  3. connecting to: 127.0.0.1/test
  4. first_replset:PRIMARY> db.test_table.insert({"id": 1})
  5. WriteResult({ "nInserted" : 1 })
  6. first_replset:PRIMARY> db.test_table.find()
  7. { "_id" : ObjectId("582abba066fdf9fae28a1ba7"), "id" : 1 }

在186、188任意SECONDARY上查詢:
同master - slave結構一樣,預設SECONDARY是不可讀的,需要執行rs.slaveOk()。
  1. first_replset:SECONDARY> rs.slaveOk()
  2. first_replset:SECONDARY> db.test_table.find()
  3. { "_id" : ObjectId("582abba066fdf9fae28a1ba7"), "id" : 1 }





〇 failover測試

此時情況:
187:PRIAMRY
186、188:SECONDARY


停掉PRIAMRY:
  1. first_replset:PRIMARY> use admin
  2. switched to db admin
  3. first_replset:PRIMARY> db.shutdownServer()
  4. server should be down...
  5. 2016-11-15T16:31:25.921+0800 I NETWORK [thread1] trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed
  6. 2016-11-15T16:31:27.299+0800 I NETWORK [thread1] Socket recv() errno:104 Connection reset by peer 127.0.0.1:27017
  7. 2016-11-15T16:31:27.299+0800 I NETWORK [thread1] SocketException: remote: (NONE):0 error: 9001 socket exception [RECV_ERROR] server [127.0.0.1:27017]
  8. 2016-11-15T16:31:27.299+0800 I NETWORK [thread1] reconnect 127.0.0.1:27017 (127.0.0.1) failed failed
  9. 2016-11-15T16:31:27.302+0800 I NETWORK [thread1] trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed
  10. 2016-11-15T16:31:27.302+0800 W NETWORK [thread1] Failed to connect to 127.0.0.1:27017, reason: errno:111 Connection refused
  11. 2016-11-15T16:31:27.302+0800 I NETWORK [thread1] reconnect 127.0.0.1:27017 (127.0.0.1) failed failed
  12. >


(原SECONDARY)188上查到,此時188已經為PRIMARY了。
  1. first_replset:PRIMARY> rs.status()
  2. {
  3.         "set" : "first_replset",
  4.         "date" : ISODate("2016-11-15T16:31:45.773Z"),
  5.         "myState" : 1,
  6.         "term" : NumberLong(2),
  7.         "heartbeatIntervalMillis" : NumberLong(2000),
  8.         "members" : [
  9.                 {
  10.                         "_id" : 1,
  11.                         "name" : "192.168.1.186:27017",
  12.                         "health" : 1,
  13.                         "state" : 2,
  14.                         "stateStr" : "SECONDARY",
  15.                         ……………………
  16.                 },
  17.                 {
  18.                         "_id" : 2,
  19.                         "name" : "192.168.1.187:27017",
  20.                         "health" : 0,
  21.                         "state" : 8,
  22.                         "stateStr" : "(not reachable/healthy)",
  23.                         ……………………
  24.                 },
  25.                 {
  26.                         "_id" : 3,
  27.                         "name" : "192.168.1.188:27017",
  28.                         "health" : 1,
  29.                         "state" : 1,
  30.                         "stateStr" : "PRIMARY",
  31.                         ……………………
  32.                 }
  33.         ],
  34.         "ok" : 1
  35. }





〇 在副本集中新增一個屬性為Arbiter的成員
然此處只做新增實踐,實際上並不建議在Secondary-Primary-Secondary的結構上再多一個Arbiter成員形成偶數個節點。
文件寫到:
  1. Only add an arbiter to sets with even numbers of voting members. 
  2. If you add an arbiter to a set with an odd number of voting members, the set may suffer from tied elections.


192.168.1.185(另一臺)上啟動一個mongod例項:
  1. mkdir -p /data/arb
  2. mongod --dbpath=/data/arb/ --logpath=/data/arb/mongo.log --fork --replSet=first_replset


在failover後的PRIMARY節點新增Arbiter
  1. first_replset:PRIMARY> rs.addArb("192.168.1.185:27017")
  2. { "ok" : 1 }
  3. first_replset:PRIMARY> rs.status()
  4.                 …………………
  5.                 {
  6.                         "_id" : 4,
  7.                         "name" : "192.168.1.185:27017",
  8.                         "health" : 1,
  9.                         "state" : 7,
  10.                         "stateStr" : "ARBITER",
  11.                         …………………
  12.                 }
  13.                 ……………………


此時回到arbiter的mongo shell,發現正如文件所說,arbiter是不會存有副本集中資料的。
  1. first_replset:ARBITER> rs.slaveOk()
  2. first_replset:ARBITER> use test;
  3. switched to db test
  4. first_replset:ARBITER> show tables;
  5. first_replset:ARBITER> db.test_table.find();
  6. Error: error: { "ok" : 0, "errmsg" : "node is recovering", "code" : 13436 }






〇 文件:
Replication > Replica Set Tutorials > Replica Set Deployment Tutorials 
Reference > mongo Shell Methods > Replication Methods

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29773961/viewspace-2128530/,如需轉載,請註明出處,否則將追究法律責任。

相關文章