天天看點

mongodb副本集

副本集介紹

mongodb的叢集大體分為兩種模式,副本集模式和分片模式。副本集模式包括無仲裁者和有仲裁者的副本集。

副本集中包含一個Primary節點和多個Secondary節點,primary負責資料的寫入,secondary從Primary同步寫入的資料,保證副本集内資料的同步,提供資料的高可用。

mongodb隻支援單一的primary節點。primary節點的選舉需要副本集中的大多數節點同意,要求大多數節點同意的目的是避免出現兩個primary節點。

比如5個副本的副本集需要至少3個節點同意才能産生primary,此時由于網絡分區導緻其中3台和另外兩台不能通信。其中兩台由于不能滿足大多數節點的要求(少于3),是以他們無法選擇primary,即使這兩個中有一個節是primary節點,當它注意到它無法擷取大多數節點的支援時,它也會退化成為備份節點。

如果讓這兩個節點可以選出primary節點,而另外3個節點也選出primary節點,這樣就存在了兩個primary節點了。

當備份節點無法與主節點連通時,它會聯系并請求其他副本內建員将自己選舉為主節點,如果競選節點成員能夠得到大多數投票,就會成為主節點。節點被選為primary的權重包括,節點資料是否最新?有沒有其他更高優先級的成員可以被選舉為主節點?

因為成為primary需要大多數節點同意,在副本集的環境中,要是所有的Secondary都當機了,隻剩下Primary,最後Primary會變成Secondary,不能提供服務。

副本集結構

mongo官網建議副本集的最少節點數為3,節點配置可以為:

mongodb副本集

或:

mongodb副本集

兩者的差別在于是否有仲裁節點,仲裁節點既不儲存資料也不為用戶端提供服務,隻參與投票。如果資源有限或不想儲存三份資料,可以使用仲裁節點代替一個備份節點。

資料同步

Primary節點負責寫入資料,secondary會檢查自己local庫的oplog.rs集合,找出最近的時間戳,檢查Primary節點local庫oplog.rs集合,找出大于此時間戳的記錄,将找到的記錄插入到自己的oplog.rs集合中,并執行這些操作。

副本集初始化時,會進行初始化同步,嘗試從副本集的另一個成員那裡進行全量複制。

Primary選舉

Primary選舉時機一般包括:

Secondary節點檢測到Primary當機時,會觸發新Primary的選舉
當有Primary節點主動stepDown(主動降級為Secondary)時,也會觸發新的Primary選舉           
節點間心跳

複制內建員間預設每2s會發送一次心跳資訊,如果10s未收到某個節點的心跳,則認為該節點已當機;如果當機的節點為Primary,Secondary(前提是可被選為Primary)會發起新的Primary選舉。

成員狀态:

STARTUP : 剛啟動時處于這個狀态,加載副本內建功後就進入STARTUP2狀态
STARTUP2 : 整個初始化同步都處于這個狀态,這個狀态下,MongDB會建立幾個線程,用于處理複制和選舉,然後就會切換到RECOVERING狀态
RECOVERING : 表示運作正常,當暫時不能處理讀取請求。如果有成員處于這個狀态,可能會造成輕微系統過載
ARBITER : 仲裁者處于這個狀态
DOWN : 一個正常運作的成員不可達,就處于DOWN狀态。這個狀态有可能是網絡問題
UNKNOWN : 成員無法到達其他任何成員,其他成員就知道無法它處于什麼狀态,就會處于UNKNOWN。表明這個未知狀态的成員挂掉了。或者兩個成員間存在網絡通路問題。
REMOVED : 被移除副本集時處于的狀态,添加回來後,就會回到正常狀态
ROLLBACK : 處于資料復原時就處于ROLLBACK狀态。復原結束後,會換為RECOVERING狀态,然後成為備份節點。
FATAL : 發生不可挽回錯誤,也不再嘗試恢複,就處于這個狀态。這個時候通常應該重新開機伺服器           
節點優先級

每個節點都傾向于投票給優先級最高的節點,優先級為0的節點不會主動發起Primary選舉,當Primary發現有優先級更高Secondary,并且該Secondary的資料落後在10s内,則Primary會主動降級,讓優先級更高的Secondary有成為Primary的機會。

網絡分區

隻有大多數投票節點間保持網絡連通,才有機會被選Primary;如果Primary與大多數的節點斷開連接配接,Primary會主動降級為Secondary。當發生網絡分區時,可能在短時間内出現多個Primary,故Driver在寫入時,最好設定大多數成功的政策,這樣即使出現多個Primary,也隻有一個Primary能成功寫入大多數。

複制集的讀寫設定

預設情況下,複制集的所有讀請求都發到Primary,Driver可通過設定Read Preference來将讀請求路由到其他的節點。

primary: 預設規則,所有讀請求發到Primary
primaryPreferred: Primary優先,如果Primary不可達,請求Secondary
secondary: 所有的讀請求都發到secondary
secondaryPreferred:Secondary優先,當所有Secondary不可達時,請求Primary
nearest:讀請求發送到最近的可達節點上(通過ping探測得出最近的節點)           

復原(rollback)

Primary執行了一個寫請求之後挂了,但是備份節點還沒有來得及複制這次操作。新選舉出來的主節點結就會漏掉這次寫操作。當舊Primary恢複之後,就要復原部分操作。復原回将失敗之前未複制的操作撤銷。

副本集執行個體

在三台伺服器上分别部署相同的mongodb,規劃:

192.168.47.129 slave
192.168.47.130 master
192.168.47.141 arbiter           

相對于單機版mongo,副本集隻需要添加如下相關配置:

replication:
    oplogSizeMB: 10240
    replSetName: nh_sc           

啟動所有mongo,之後進入任意節點配置主、備、仲裁節點:

> use admin
switched to db admin
> nh_config={_id:"nh_sc",members:[{_id:0,host:'192.168.47.130:27017',priority:9},{_id:1,host:'192.168.47.129:27017',priority:1},{_id:2,host :'192.168.47.141:27017',arbiterOnly:true}]};
{
    "_id" : "nh_sc",
    "members" : [
        {
            "_id" : 0,
            "host" : "192.168.47.130:27017",
            "priority" : 9
        },
        {
            "_id" : 1,
            "host" : "192.168.47.129:27017",
            "priority" : 1
        },
        {
            "_id" : 2,
            "host" : "192.168.47.141:27017",
            "arbiterOnly" : true
        }
    ]
}
> rs.initiate(nh_config)
{
    "ok" : 1,
    "$clusterTime" : {
        "clusterTime" : Timestamp(1589343960, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    },
    "operationTime" : Timestamp(1589343960, 1)
}           

注意仲裁節點需要有個特别的配置——arbiterOnly:true。之後執行rs.status()指令會看到如下資訊:

nh_sc:SECONDARY> rs.status()
{
    "set" : "nh_sc",
    "date" : ISODate("2020-05-13T04:28:19.430Z"),
    "myState" : 1,
    "term" : NumberLong(1),
    "syncingTo" : "",
    "syncSourceHost" : "",
    "syncSourceId" : -1,
    "heartbeatIntervalMillis" : NumberLong(2000),
    "majorityVoteCount" : 2,
    "writeMajorityCount" : 2,
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1589344091, 1),
            "t" : NumberLong(1)
        },
        "lastCommittedWallTime" : ISODate("2020-05-13T04:28:11.276Z"),
        "readConcernMajorityOpTime" : {
            "ts" : Timestamp(1589344091, 1),
            "t" : NumberLong(1)
        },
        "readConcernMajorityWallTime" : ISODate("2020-05-13T04:28:11.276Z"),
        "appliedOpTime" : {
            "ts" : Timestamp(1589344091, 1),
            "t" : NumberLong(1)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1589344091, 1),
            "t" : NumberLong(1)
        },
        "lastAppliedWallTime" : ISODate("2020-05-13T04:28:11.276Z"),
        "lastDurableWallTime" : ISODate("2020-05-13T04:28:11.276Z")
    },
    "lastStableRecoveryTimestamp" : Timestamp(1589344091, 1),
    "lastStableCheckpointTimestamp" : Timestamp(1589344091, 1),
    "electionCandidateMetrics" : {
        "lastElectionReason" : "electionTimeout",
        "lastElectionDate" : ISODate("2020-05-13T04:26:11.215Z"),
        "electionTerm" : NumberLong(1),
        "lastCommittedOpTimeAtElection" : {
            "ts" : Timestamp(0, 0),
            "t" : NumberLong(-1)
        },
        "lastSeenOpTimeAtElection" : {
            "ts" : Timestamp(1589343960, 1),
            "t" : NumberLong(-1)
        },
        "numVotesNeeded" : 2,
        "priorityAtElection" : 9,
        "electionTimeoutMillis" : NumberLong(10000),
        "numCatchUpOps" : NumberLong(0),
        "newTermStartDate" : ISODate("2020-05-13T04:26:11.252Z"),
        "wMajorityWriteAvailabilityDate" : ISODate("2020-05-13T04:26:11.733Z")
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "192.168.47.130:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 2348,
            "optime" : {
                "ts" : Timestamp(1589344091, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2020-05-13T04:28:11Z"),
            "syncingTo" : "",
            "syncSourceHost" : "",
            "syncSourceId" : -1,
            "infoMessage" : "",
            "electionTime" : Timestamp(1589343971, 1),
            "electionDate" : ISODate("2020-05-13T04:26:11Z"),
            "configVersion" : 1,
            "self" : true,
            "lastHeartbeatMessage" : ""
        },
        {
            "_id" : 1,
            "name" : "192.168.47.129:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 138,
            "optime" : {
                "ts" : Timestamp(1589344091, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1589344091, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2020-05-13T04:28:11Z"),
            "optimeDurableDate" : ISODate("2020-05-13T04:28:11Z"),
            "lastHeartbeat" : ISODate("2020-05-13T04:28:17.595Z"),
            "lastHeartbeatRecv" : ISODate("2020-05-13T04:28:18.088Z"),
            "pingMs" : NumberLong(2),
            "lastHeartbeatMessage" : "",
            "syncingTo" : "192.168.47.130:27017",
            "syncSourceHost" : "192.168.47.130:27017",
            "syncSourceId" : 0,
            "infoMessage" : "",
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "192.168.47.141:27017",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 138,
            "lastHeartbeat" : ISODate("2020-05-13T04:28:17.595Z"),
            "lastHeartbeatRecv" : ISODate("2020-05-13T04:28:17.767Z"),
            "pingMs" : NumberLong(3),
            "lastHeartbeatMessage" : "",
            "syncingTo" : "",
            "syncSourceHost" : "",
            "syncSourceId" : -1,
            "infoMessage" : "",
            "configVersion" : 1
        }
    ],
    "ok" : 1,
    "$clusterTime" : {
        "clusterTime" : Timestamp(1589344091, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    },
    "operationTime" : Timestamp(1589344091, 1)
}           

連接配接slave mongo:

root@faith129:/server/deployments/mongodb-4.2.6# mongo 192.168.47.129
nh_sc:SECONDARY> show databases
2020-05-12T21:30:23.292-0700 E  QUERY    [js] uncaught exception: Error: listDatabases failed:{
    "operationTime" : Timestamp(1589344221, 1),
    "ok" : 0,
    "errmsg" : "not master and slaveOk=false",
    "code" : 13435,
    "codeName" : "NotMasterNoSlaveOk",
    "$clusterTime" : {
        "clusterTime" : Timestamp(1589344221, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:135:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:87:12
shellHelper.show@src/mongo/shell/utils.js:906:13
shellHelper@src/mongo/shell/utils.js:790:15
@(shellhelp2):1:1
nh_sc:SECONDARY> rs.slaveOk()
nh_sc:SECONDARY> show databases
admin   0.000GB
config  0.000GB
local   0.000GB
nh_sc   0.006GB
test1   0.000GB
nh_sc:SECONDARY> use nh_sc
switched to db nh_sc
nh_sc:SECONDARY> db.cmPhoneFeatures.count()
100000           

之前slave裡沒有nh_sc庫,可以看到現在已經有了,資料也被同步過來了。

再檢視仲裁節點:

root@faith141:/server/deployments/mongodb-4.2.6# mongo 192.168.47.141
nh_sc:ARBITER> show databases
2020-05-12T21:33:00.820-0700 E  QUERY    [js] uncaught exception: Error: listDatabases failed:{
    "ok" : 0,
    "errmsg" : "not master and slaveOk=false",
    "code" : 13435,
    "codeName" : "NotMasterNoSlaveOk"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:135:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:87:12
shellHelper.show@src/mongo/shell/utils.js:906:13
shellHelper@src/mongo/shell/utils.js:790:15
@(shellhelp2):1:1
nh_sc:ARBITER> rs.slaveOk()
nh_sc:ARBITER> show databases
admin   0.000GB
config  0.000GB
local   0.000GB
nh_sc:ARBITER>            

發現仲裁節點是不存儲資料的。

測試服務轉移:

1. 關閉slave,master仍然提供服務;
2. 關閉master,此時slave更新為master并提供服務。           

有人說啟動兩個副本集,一主一從,如果master死掉或slave死掉,這時候叢集中隻有一個secondary,如果加上一個仲裁節點則正常,将這種情況的原因歸結于仲裁節點是不太對的。

其實很好了解,因為一主一從的話,大部分節點為2,根據mongo的機制,此時永遠無法得到2,是以不會有primary産生,且原primary即使存活也會降級為secondary,這與mongo機制相關,與仲裁節點無關。

參考:

https://www.jianshu.com/p/bb3f721e1557

繼續閱讀