副本集整理。 開始逐步把mongodb部落格補齊了。
副本集是一組伺服器,其中一個是用於處理寫入操作的主節點,還有多個用於儲存主節點的資料副本的從節點。
如果主節點崩潰了,則從節點會從其中選取出一個新的主節點。
起到一個熱備份 和 容災的作用,如果出現不可預料的事故,比如主節點磁碟損壞,那麼可以故障轉移,其他節點將會提到主節點進行寫入。
現在一臺機器上演示。
建立對應的目錄:
資料:
mkdir -p ~/data/rs{1,2,3}
紀錄檔:
mkdir -p ~/logs/rs{1,2,3}
啟動3個mongod:
mongod --replSet mydb --dbpath ~/data/rs1 --logpath ~/logs/rs1/log --port 27017 --smallfiles --oplogSize=200 &
mongod --replSet mydb --dbpath ~/data/rs2 --logpath ~/logs/rs2/log --port 27018 --smallfiles --oplogSize=200 &
mongod --replSet mydb --dbpath ~/data/rs3 --logpath ~/logs/rs3/log --port 27019 --smallfiles --oplogSize=200 &
把副本連線到一起,副本集設定傳遞:
mongo --port 27017
初始化副本集設定:
rsconf={
"_id" : "mydb",
"members" : [
{
"_id" : 0,
"host" : "localhost:27017"
},
{
"_id" : 1,
"host" : "localhost:27018"
},
{
"_id" : 2,
"host" : "localhost:27019"
}
]
}
rs.initiate(rsconf)
這個設定檔案就是副本集的設定。在localhost:27017 上執行的成員會解析設定並將訊息傳送給其他成員,提醒他們存在新的設定。
一旦所有成員都載入了設定,他們就會選擇一個主節點並開始處理讀寫操作。
注意:
不能在不停機的情況下將單機伺服器轉換為副本集,以重新啟動並初始化該副本集。因此,即使一開始只有一臺伺服器,你也希望將其設定為一個單成員的副本集。
這樣,如果以後想新增更多成員,則可以在不停止執行的情況下進行新增。
然後使用rs.status() 檢視副本集的狀態
{
"set" : "mydb",
"date" : ISODate("2022-10-16T02:51:57.670Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1665888717, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1665888717, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1665888717, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1665888717, 1),
"t" : NumberLong(1)
}
},
"members" : [
{
"_id" : 0,
"name" : "localhost:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 955,
"optime" : {
"ts" : Timestamp(1665888717, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-10-16T02:51:57Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1665888285, 1),
"electionDate" : ISODate("2022-10-16T02:44:45Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "localhost:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 443,
"optime" : {
"ts" : Timestamp(1665888707, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1665888707, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-10-16T02:51:47Z"),
"optimeDurableDate" : ISODate("2022-10-16T02:51:47Z"),
"lastHeartbeat" : ISODate("2022-10-16T02:51:55.983Z"),
"lastHeartbeatRecv" : ISODate("2022-10-16T02:51:57.128Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "localhost:27017",
"syncSourceHost" : "localhost:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "localhost:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 443,
"optime" : {
"ts" : Timestamp(1665888707, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1665888707, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-10-16T02:51:47Z"),
"optimeDurableDate" : ISODate("2022-10-16T02:51:47Z"),
"lastHeartbeat" : ISODate("2022-10-16T02:51:56.020Z"),
"lastHeartbeatRecv" : ISODate("2022-10-16T02:51:57.065Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "localhost:27017",
"syncSourceHost" : "localhost:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1
}
],
"ok" : 1,
"operationTime" : Timestamp(1665888717, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1665888717, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
這樣就可以看到27017 是主節點,其他的不是。
然後rs 是一個mongo 命令的封裝:
mydb:PRIMARY> rs.help()
rs.status() { replSetGetStatus : 1 } checks repl set status
rs.initiate() { replSetInitiate : null } initiates set with default settings
rs.initiate(cfg) { replSetInitiate : cfg } initiates set with configuration cfg
rs.conf() get the current configuration object from local.system.replset
rs.reconfig(cfg) updates the configuration of a running replica set with cfg (disconnects)
rs.add(hostportstr) add a new member to the set with default attributes (disconnects)
rs.add(membercfgobj) add a new member to the set with extra attributes (disconnects)
rs.addArb(hostportstr) add a new member which is arbiterOnly:true (disconnects)
rs.stepDown([stepdownSecs, catchUpSecs]) step down as primary (disconnects)
rs.syncFrom(hostportstr) make a secondary sync from the given member
rs.freeze(secs) make a node ineligible to become primary for the time specified
rs.remove(hostportstr) remove a host from the replica set (disconnects)
rs.secondaryOk() allow queries on secondary nodes
rs.printReplicationInfo() check oplog size and time range
rs.printSecondaryReplicationInfo() check replica set members and replication lag
db.isMaster() check who is primary
db.hello() check who is primary
reconfiguration helpers disconnect from the database so the shell will display
an error, even if the command succeeds.
可以看下哪些是自己想要的。
非主節點是否可以寫入資料:
結果是不行的。
主節點寫入是否從節點會同步:
連線27017 判斷是否是主節點:
那麼開始連線一個從節點,並從節點上讀取資料,和mysql 一樣。
可以看到不讓我們進行存取
這裡面是一個這樣的機制:
因為從節點落後於主節點,那麼預設情況下是不讓從從節點上讀取資料。
這和mysql 是不一樣的。
那麼是否能修改設定來從從節點上讀取資料,這也是可以的。
可以修改使用者端的連線設定,可以來進行完成, 然後就可以存取從節點資料了。
下面來實驗,如果主節點沒了,那麼是否這個副本集是否就沒法使用了?
db.adminCommand({"shutdown":1});
使用上面這個命令進行關閉。
可以看到的確關閉了。
那麼隨便進入一個節點看下。
進入27018 檢視。
那麼主節點就變成了27018了。
用rs.status() 檢視27017的狀況:
已經不健康了,那麼再次啟動27017一下。
27017 就變成從節點了。
這個時候要增加副本集怎麼辦:
可以使用rs.add({"localhost":"27020"})
如果要刪除怎麼辦:
可以使用rs.remove({"localhost":"27020"})
如果進行修改怎麼辦:
var config = rs.config()
config.members[0].host="localhost":"27017"
rs.reconfig(config)
這樣就從新載入了。
前面知道了,副本集3個情況下,如果一個沒了,還是可以執行的。
那麼如果兩個掛了,是否最後一個可以繼續執行呢?
答案是不能的,這裡就不實驗了。
因為有一個理論知識:
副本集在絕大多數活著的情況下才能正常執行。
也就是說3個有兩個正常才能執行,5個就要3個。
為什麼這樣呢? 假設一個問題,現在有3個副本集,一個因為網路問題,無法與其他節點連線。
那麼它自己就選自己為主節點,其他兩個選他們自己其中一個為主節點。
那麼問題來了,就有兩個主節點了,這樣就可以寫入不同的主節點,那麼就出現問題了,兩邊資料無法同步了。
下一個問題,副本集是如何選舉的呢? 什麼時候進行選舉呢?
當一個從節點無法與主節點連通時候,他就會聯絡並請求其他的副本整合員將自己選舉為主節點。
沒錯,就像邊關大將,如果察覺皇帝不在了,就開始密謀向其他邊關大臣推舉自己作為皇帝。
然後其他邊關大臣要確認是不是皇帝駕崩了,如果確認皇帝沒有駕崩,那麼是不會進行支援的。
如果確定皇帝不在了,那麼也不一樣選發起的這個邊關大將。
而是先考慮哪個副本集是最新資料,然後是優先順序最高的節點作為主節點。
如果一個副本是最新資料,而其他沒有,那麼會選擇這個節點作為主節點。但是值得注意的是,如果優先順序更高的節點同步了最新資料後,那麼優先順序更高的節點,將會成為新的主節點。
副本集之間會互相間隔兩秒傳送一次心跳。如果某個成員在10秒內沒有反饋心跳,則其他成員會將改不良成員標記為無法存取。
前文提及到優先順序最高的會作為主節點:
那麼如何設定優先順序呢?
rs.add({"host":"localhost:27017", "priority":1.5})
這個優先順序有一個特殊值,如果選擇0的話,那麼這個節點永遠不會成為新的節點。
然後優先順序更高的一般會成為主節點,是這樣的,如果設定了優先順序更高的副本集,那麼當這個副本集擁有最新資料的時候,當前節點會自動退位給這個節點。
config = rs.config()
config.members[0].priority=1.5
rs.reconfig(config)
我們可以看到前面是27018,現在變成了27017了。
根據上面結論得出:優先順序最高的節點,才是合法繼承人。
如果你想在從節點上讀取資料,但是有一個從節點只是用來做備份的,機器效能很差的話,你不希望使用者端去存取這臺機器怎麼辦?
你可以設定該節點隱藏。
config.members[0].hidden=true
這樣去設定。
其原理也很簡單,就是使用者端獲取副本集資訊的時候是呼叫db.isMaster() 來檢視。
如果設定了,那麼isMaster() 是不包含該隱藏的資訊的。
所以使用者端就不會去存取這個副本集了。
然後如果副本集只是作為備份的話,那麼其實是不需要索引的,那麼可以設定:
{"buildIndexs":false},這樣從節點就不會建立索引了,同樣它的優先順序也應該設定為0,這樣就不會選為主節點了。
這個破爛之道,並不是指垃圾的意思。
而是節約成本的方式,個人稱為破爛之道。
從上面我們知道副本集肯定是3個和以上。
但是有些資料庫很大,比如16T左右的話,如果購買的是雲伺服器,成本也是不低的。
這個時候公司認為要節約成本,那麼其他這個時候只需要一個作為副本集作為備份就好。
這個時候就可以加入仲裁者這種方式。
mongodb 支援一種特殊型別的副本成員,叫做仲裁者。
作用就是參與選舉,但是不會選擇自己作為主節點。
仲裁者並不儲存資料,也不會為使用者端提供服務;它只是為了幫助具有兩個成員的副本集滿足大多數的條件。
設定仲裁者的方式:
rs.addArb("localhost:27021")
或者:
rs.add({"_id":4,"host":"localhost:27021","arbiterOnly":true})
仲裁者一般只有一個,因為仲裁者是為了絕大多數,來打破平衡的,防止偶數的情況,這是唯一的作用。
仲裁者缺點,那就是如果一個資料從節點完全掛了,無法恢復,要啟動一個新的資料節點去替換掉舊的。
那麼這個時候主節點負荷會很重,因為不僅要將資料複製為從節點,還需要處理應用程式的讀寫負荷。
下一節副本集的原理。