一、概述
1.1 MongoDB副本集
通俗來講,mongodb的副本集相當于具有自動故障恢複的主從叢集,主從叢集和副本集最明顯的特征為副本集沒有固定的“主節點”,整個叢集會通過一定的算法選舉出主節點,目前MongoDB官方已經不建議使用主從模式了,在主從模式下,如果主資料庫當機,從資料庫無法自動接管主資料庫,進而無法接入資料,取而代之的就是MongoDB副本集模式,主伺服器負責整個副本集的讀寫,副本集定期同步資料備份,副本集中的副本節點在主節點挂掉後通過心跳機制檢測到後副本節點就會選舉一個新的主伺服器,這一切對于應用伺服器無需關心。
1.2 架構圖
1.3 複制原理
mongodb的複制至少需要兩個節點。其中一個是主節點,負責處理用戶端請求,其餘的都是從節點,負責複制主節點上的資料。
mongodb各個節點常見的搭配方式為:一主一從、一主多從。
主節點記錄在其上的所有操作oplog,從節點定期輪詢主節點擷取這些操作,然後對自己的資料副本執行這些操作,進而保證從節點的資料與主節點一緻。
1.4 副本集特征:
N 個節點的叢集
任何節點可作為主節點
所有寫入操作都在主節點上
自動故障轉移
自動恢複
1.5 Bully算法
如果副本集中主節點宕掉後,需要使用bully算法進行選舉主節點,其主要思想為每個成員均可以聲明自己為主節點并通知其他節點,别的節點可以選擇接受這個聲明或是拒絕并進入主節點競争,隻有被其他節點接受的節點才可以當主節點,
節點按照一些屬性來判斷誰應該勝出。這個屬性可以是一個靜态ID,也可以是更新的度量像最近一次事務ID(最新的節點會勝出)
官方描述:
得到每個伺服器節點的最後操作時間戳。每個mongodb都有oplog機制會記錄本機的操作,友善和主伺服器進行對比資料是否同步還可以用于錯誤恢複。
如果叢集中大部分伺服器down機了,保留活着的節點都為 secondary狀态并停止,不選舉了。
如果叢集中選舉出來的主節點或者所有從節點最後一次同步時間看起來很舊了,停止選舉等待人來操作。
如果上面都沒有問題就選擇最後操作時間戳最新(保證資料是最新的)的伺服器節點作為主節點。
1.6 Replica Set成員
一個Replica Set中的成員角色有三種:Primary,Secondary和Arbiter。
Primary:接收來自用戶端的所有的寫操作,一個Replica Set中有且隻有一個Primary。Primary如果宕掉,Replica Set會自動選舉一個Secondary成為Primary。Primary将它data sets的所有操作都記錄到oplog中。
Secondary:Secondary從Primary複制oplog,然後将oplog中的操作應用到自己的data sets。Secondary和Primary之間是異步複制,也就是Secondary中的資料可能不是最新的。預設情況下,Secondary不可讀不可寫,但是可以通過設定運作用戶端從Secondary讀。
Secondary配置的三種用途:
1.在選舉中阻止其成為Primary,隻用作備份資料。通過設定優先級priority為0來實作
2.阻止應用程式從它讀,通過設定優先級priority為0和設定hidden為true來實作。(一個隐藏的成員同樣複制Primary的資料,但是對于用戶端應用程式來講,它不可見。)
3.保留曆史鏡像資料用于資料回檔,比如如果誤删除資料,可以使用Delayed Replica Set成員中的資料恢複。
Arbiter:Arbiter不需要維護自己的data sets,隻是當Primary挂掉之後參與投票選擇哪個Secondary可以更新為Primary
Replica Set中的成員個數為偶數個時,就需要添加一個Arbiter用于投票選舉哪個可以更新為Primary,不能在Primary或者Secondary主機上運作Arbiter
一個Replica Set可以最多擁有12個成員,但是隻有7個成員可以同時參與投票選舉成為Primary,如果成員數量超過12,就需要使用Master-Slave主從複制方式。
部署一個Replica Set至少需要三個成員,一個Arbiter,一個Secondary和一個Primary或者一個Primary,兩個Secondary。
二、搭建部署
2.1 基礎環境
主機名
IP位址
系統
mongodb-1
172.20.6.10
CentOS release 6.9
mongodb-2
172.20.6.11
mongodb-3
2.2 軟體安裝
在三台伺服器上依次安裝mongodb
1
2
3
4
5
6
7
<code>wget -c https:</code><code>//fastdl</code><code>.mongodb.org</code><code>/linux/mongodb-linux-x86_64-rhel62-3</code><code>.4.10.tgz</code>
<code>tar</code> <code>-zxvf mongodb-linux-x86_64-rhel62-3.4.10.tgz</code>
<code>ln</code> <code>-sv mongodb-linux-x86_64-rhel62-3.4.10 mongodb</code>
<code>mkdir</code> <code>/usr/local/mongodb/</code><code>{conf,mongoData,mongoLog}</code>
<code>touch</code> <code>/usr/local/mongodb/mongoLog/mongodb</code><code>.log</code>
<code>echo</code> <code>"export PATH=$PAHT:/usr/local/mongodb/bin"</code><code>></code><code>/etc/profile</code><code>.d</code><code>/mongodb</code><code>.sh</code>
<code>source</code> <code>etc</code><code>/profile</code><code>.d</code><code>/mongodb</code><code>.sh</code>
定義配置檔案
8
9
10
11
12
<code>cat</code> <code>></code><code>/usr/local/mongodb/conf/mongodb</code><code>.conf<<EOF</code>
<code>dbpath=</code><code>/usr/local/mongodb/mongoData</code>
<code>logpath=</code><code>/usr/local/mongodb/mongoLog/mongodb</code><code>.log</code>
<code>logappend=</code><code>true</code>
<code>journal=</code><code>true</code>
<code>quiet=</code><code>true</code>
<code>port=27017</code>
<code>replSet=RS </code><code>#副本集名稱</code>
<code>maxConns=20000</code>
<code>httpinterface=</code><code>true</code>
<code>fork=</code><code>true</code>
<code>EOF</code>
依次啟動三個mongodb
<code>mongodb -f </code><code>/usr/local/mongodb/conf/mongodb</code><code>.conf</code>
2.3 副本集部署
挑選任意一台mongodb進行登入
<code>use admin </code><code>#切換到admin資料庫</code>
<code>config = {_id:</code><code>"RS"</code><code>,members:[ </code><code>#定義副本集配置</code>
<code>{_id:0,host:</code><code>"172.20.6.10:27017"</code><code>},</code>
<code>{_id:1,host:</code><code>"172.20.6.11:27017"</code><code>},</code>
<code>{_id:2,host:</code><code>"172.20.6.12:27017"</code><code>},]</code>
<code>}</code>
<code>rs.initiate(config); </code><code>#初始化副本集配置</code>
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<code>RS:PRIMARY> rs.status(); </code><code>#檢視副本集狀态</code>
<code>{</code>
<code> </code><code>"set"</code> <code>: </code><code>"RS"</code><code>,</code>
<code> </code><code>"date"</code> <code>: ISODate(</code><code>"2017-11-26T14:09:00.054Z"</code><code>),</code>
<code> </code><code>"myState"</code> <code>: 1,</code>
<code> </code><code>"term"</code> <code>: NumberLong(1),</code>
<code> </code><code>"heartbeatIntervalMillis"</code> <code>: NumberLong(2000),</code>
<code> </code><code>"optimes"</code> <code>: {</code>
<code> </code><code>"lastCommittedOpTime"</code> <code>: {</code>
<code> </code><code>"ts"</code> <code>: Timestamp(1511705333, 1),</code>
<code> </code><code>"t"</code> <code>: NumberLong(1)</code>
<code> </code><code>},</code>
<code> </code><code>"appliedOpTime"</code> <code>: {</code>
<code> </code><code>"durableOpTime"</code> <code>: {</code>
<code> </code><code>}</code>
<code> </code><code>},</code>
<code> </code><code>"members"</code> <code>: [</code>
<code> </code><code>{</code>
<code> </code><code>"_id"</code> <code>: 0,</code>
<code> </code><code>"name"</code> <code>: </code><code>"172.20.6.10:27017"</code><code>,</code>
<code> </code><code>"health"</code> <code>: 1,</code>
<code> </code><code>"state"</code> <code>: 1,</code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"PRIMARY"</code><code>, </code><code>#主節點</code>
<code> </code><code>"uptime"</code> <code>: 377,</code>
<code> </code><code>"optime"</code> <code>: {</code>
<code> </code><code>"ts"</code> <code>: Timestamp(1511705333, 1),</code>
<code> </code><code>"t"</code> <code>: NumberLong(1)</code>
<code> </code><code>},</code>
<code> </code><code>"optimeDate"</code> <code>: ISODate(</code><code>"2017-11-26T14:08:53Z"</code><code>),</code>
<code> </code><code>"infoMessage"</code> <code>: </code><code>"could not find member to sync from"</code><code>,</code>
<code> </code><code>"electionTime"</code> <code>: Timestamp(1511705241, 1),</code>
<code> </code><code>"electionDate"</code> <code>: ISODate(</code><code>"2017-11-26T14:07:21Z"</code><code>),</code>
<code> </code><code>"configVersion"</code> <code>: 1,</code>
<code> </code><code>"self"</code> <code>: </code><code>true</code>
<code> </code><code>"_id"</code> <code>: 1,</code>
<code> </code><code>"name"</code> <code>: </code><code>"172.20.6.11:27017"</code><code>,</code>
<code> </code><code>"state"</code> <code>: 2,</code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"SECONDARY"</code><code>, </code><code>#secondary節點</code>
<code> </code><code>"uptime"</code> <code>: 109,</code>
<code> </code><code>"optimeDurable"</code> <code>: {</code>
<code> </code><code>"optimeDurableDate"</code> <code>: ISODate(</code><code>"2017-11-26T14:08:53Z"</code><code>),</code>
<code> </code><code>"lastHeartbeat"</code> <code>: ISODate(</code><code>"2017-11-26T14:09:00.053Z"</code><code>),</code>
<code> </code><code>"lastHeartbeatRecv"</code> <code>: ISODate(</code><code>"2017-11-26T14:08:59.072Z"</code><code>),</code>
<code> </code><code>"pingMs"</code> <code>: NumberLong(0),</code>
<code> </code><code>"syncingTo"</code> <code>: </code><code>"172.20.6.10:27017"</code><code>,</code>
<code> </code><code>"configVersion"</code> <code>: 1</code>
<code> </code><code>"_id"</code> <code>: 2,</code>
<code> </code><code>"name"</code> <code>: </code><code>"172.20.6.12:27017"</code><code>, </code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"SECONDARY"</code><code>, </code><code>#secondary節點</code>
<code> </code><code>"lastHeartbeatRecv"</code> <code>: ISODate(</code><code>"2017-11-26T14:08:59.054Z"</code><code>),</code>
<code> </code><code>],</code>
<code> </code><code>"ok"</code> <code>: 1</code>
此時replica set叢集已結搭建成功
三、副本集測試
3.1 資料複制測試
在主節點建立資料庫,并建立集合,插入文檔,在secondary檢視文檔
此時已經完成在主節點建立資料,接下來在secondary檢視資料是否已經同步過去。
mongodb預設是從主節點讀寫資料的,副本節點上不允許讀,需要設定副本節點可以讀。
<code>db.getMongo().setSlaveOk(); </code><code>#設定副本節點可讀</code>
此時我們可以測試得到資料,資料已經同步到secondary上,但是無法在secondary上進行資料的增删改操作。
3.2 故障轉移測試
目前mongodb-1為主節點,mongdb-2、mongodb-3為副本集節點,此時停掉主節點的mongod服務,進行故障轉移測試。
宕掉主節點mongodb-1的服務後,我們登入mongodb-2,檢視副本集狀态:
<code>RS:PRIMARY> rs.status()</code>
<code> </code><code>"date"</code> <code>: ISODate(</code><code>"2017-11-26T14:35:03.422Z"</code><code>),</code>
<code> </code><code>"term"</code> <code>: NumberLong(2),</code>
<code> </code><code>"ts"</code> <code>: Timestamp(1511706901, 1),</code>
<code> </code><code>"t"</code> <code>: NumberLong(2)</code>
<code> </code><code>"health"</code> <code>: 0,</code>
<code> </code><code>"state"</code> <code>: 8,</code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"(not reachable/healthy)"</code><code>, </code><code>#mongodb-1已經失去連接配接</code>
<code> </code><code>"uptime"</code> <code>: 0,</code>
<code> </code><code>"ts"</code> <code>: Timestamp(0, 0),</code>
<code> </code><code>"t"</code> <code>: NumberLong(-1)</code>
<code> </code><code>"optimeDate"</code> <code>: ISODate(</code><code>"1970-01-01T00:00:00Z"</code><code>),</code>
<code> </code><code>"optimeDurableDate"</code> <code>: ISODate(</code><code>"1970-01-01T00:00:00Z"</code><code>),</code>
<code> </code><code>"lastHeartbeat"</code> <code>: ISODate(</code><code>"2017-11-26T14:35:02.502Z"</code><code>),</code>
<code> </code><code>"lastHeartbeatRecv"</code> <code>: ISODate(</code><code>"2017-11-26T14:32:20.434Z"</code><code>),</code>
<code> </code><code>"lastHeartbeatMessage"</code> <code>: </code><code>"Connection refused"</code><code>,</code>
<code> </code><code>"configVersion"</code> <code>: -1</code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"PRIMARY"</code><code>, </code><code>#mongodb-2為新的主節點</code>
<code> </code><code>"uptime"</code> <code>: 1842,</code>
<code> </code><code>"ts"</code> <code>: Timestamp(1511706901, 1),</code>
<code> </code><code>"t"</code> <code>: NumberLong(2)</code>
<code> </code><code>"optimeDate"</code> <code>: ISODate(</code><code>"2017-11-26T14:35:01Z"</code><code>),</code>
<code> </code><code>"electionTime"</code> <code>: Timestamp(1511706750, 1),</code>
<code> </code><code>"electionDate"</code> <code>: ISODate(</code><code>"2017-11-26T14:32:30Z"</code><code>),</code>
<code> </code><code>"name"</code> <code>: </code><code>"172.20.6.12:27017"</code><code>,</code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"SECONDARY"</code><code>, </code><code>#mongodb-3為secondary節點</code>
<code> </code><code>"uptime"</code> <code>: 1671,</code>
<code> </code><code>"optimeDurableDate"</code> <code>: ISODate(</code><code>"2017-11-26T14:35:01Z"</code><code>),</code>
<code> </code><code>"lastHeartbeat"</code> <code>: ISODate(</code><code>"2017-11-26T14:35:02.354Z"</code><code>),</code>
<code> </code><code>"lastHeartbeatRecv"</code> <code>: ISODate(</code><code>"2017-11-26T14:35:02.730Z"</code><code>),</code>
<code> </code><code>"syncingTo"</code> <code>: </code><code>"172.20.6.11:27017"</code><code>,</code>
檢視mongodb-2的日志,發現mongodb-1心跳檢查已經失去連接配接,重新進行了主節點選舉
此時在新節點mongodb-2進行文檔插入操作
此時上線mongodb-1,檢視叢集狀态與資料是否正常同步到mongodb-1上。
啟動mongodb-1的服務,檢視叢集狀态,此時mongodb-1已結成為新的secondary節點。
<code> </code><code>"date"</code> <code>: ISODate(</code><code>"2017-11-27T02:13:41.683Z"</code><code>),</code>
<code> </code><code>"ts"</code> <code>: Timestamp(1511748812, 1),</code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"SECONDARY"</code><code>, </code><code>#mongodb-1為secondary節點</code>
<code> </code><code>"uptime"</code> <code>: 1945,</code>
<code> </code><code>"ts"</code> <code>: Timestamp(1511748812, 1),</code>
<code> </code><code>"optimeDate"</code> <code>: ISODate(</code><code>"2017-11-27T02:13:32Z"</code><code>),</code>
<code> </code><code>"optimeDurableDate"</code> <code>: ISODate(</code><code>"2017-11-27T02:13:32Z"</code><code>),</code>
<code> </code><code>"lastHeartbeat"</code> <code>: ISODate(</code><code>"2017-11-27T02:13:41.373Z"</code><code>),</code>
<code> </code><code>"lastHeartbeatRecv"</code> <code>: ISODate(</code><code>"2017-11-27T02:13:40.854Z"</code><code>),</code>
<code> </code><code>"syncingTo"</code> <code>: </code><code>"172.20.6.12:27017"</code><code>,</code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"PRIMARY"</code><code>, </code><code>#mongodb-2為主節點</code>
<code> </code><code>"uptime"</code> <code>: 43760,</code>
<code> </code><code>"name"</code> <code>: </code><code>"172.20.6.12:27017"</code><code>, </code><code>#mongodb-3為secondary節點</code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"SECONDARY"</code><code>,</code>
<code> </code><code>"uptime"</code> <code>: 43589,</code>
<code> </code><code>"lastHeartbeat"</code> <code>: ISODate(</code><code>"2017-11-27T02:13:41.220Z"</code><code>),</code>
<code> </code><code>"lastHeartbeatRecv"</code> <code>: ISODate(</code><code>"2017-11-27T02:13:41.209Z"</code><code>),</code>
檢視mongodb-1資料已經正常同步。
四、其他
如果考慮到主伺服器的複制壓力過大,可以制作仲裁節點,其中的仲裁節點不存儲資料,隻是負責故障轉移的群體投票,這樣就少了資料複制的壓力。
删除節點:
<code>rs.remove(</code><code>"172.20.6.12:27017"</code><code>) </code><code>#删除節點</code>
添加節點
<code>rs.add(</code><code>"172.20.6.12:27017"</code><code>) </code><code>#添加節點</code>
<code>rs.addArb(</code><code>"172.20.6.12:27017"</code><code>) </code><code>#添加arbiter節點</code>
<code> </code><code>"state"</code> <code>: 7,</code>
<code> </code><code>"stateStr"</code> <code>: </code><code>"ARBITER"</code><code>, </code><code>#arbiter節點</code>
<code> </code><code>"uptime"</code> <code>: 4,</code>
<code> </code><code>"lastHeartbeat"</code> <code>: ISODate(</code><code>"2017-11-27T02:35:01.634Z"</code><code>),</code>
<code> </code><code>"lastHeartbeatRecv"</code> <code>: ISODate(</code><code>"2017-11-27T02:35:00.637Z"</code><code>),</code>
<code> </code><code>"configVersion"</code> <code>: 9</code>
<code></code>
本文轉自 KaliArch 51CTO部落格,原文連結:http://blog.51cto.com/kaliarch/2044618,如需轉載請自行聯系原作者