1.相對於傳統主從模式的優勢
傳統的主從模式,需要手工指定集群中的Master。
如果Master發生故障,一般都是人工介入,指定新的Master。
這個過程對於應用一般不是透明的,往往伴隨着應用重新修改配置文件,重啟應用服務器等。
而MongoDB副本集,集群中的任何節點都可能成為Master節點。
一旦Master節點故障,則會在其余節點中選舉出一個新的Master節點。
並引導剩余節點連接到新的Master節點。這個過程對於應用是透明的。
2. Bully選舉算法
Bully算法是一種協調者(主節點)競選算法,主要思想是集群的每個成員都可以聲明它是主節點並通知其他節點。
別的節點可以選擇接受這個聲稱或是拒絕並進入主節點競爭。被其他所有節點接受的節點才能成為主節點。
節點按照一些屬性來判斷誰應該勝出。這個屬性可以是一個靜態ID,也可以是更新的度量像最近一次事務ID(最新的節點會勝出)
選舉過程大致如下:
1.得到每個服務器節點的最后操作時間戳。每個mongodb都有oplog機制會記錄本機的操作,方便和主服務器進行對比數據是否同步還可以用於錯誤恢復。
2.如果集群中大部分服務器down機了,保留活着的節點都為 secondary狀態並停止,不選舉了。
3.如果集群中選舉出來的主節點或者所有從節點最后一次同步時間看起來很舊了,停止選舉等待人來操作。
4.如果上面都沒有問題就選擇最后操作時間戳最新(保證數據是最新的)的服務器節點作為主節點。
選舉的觸發條件:
1.初始化一個副本集時。
2.副本集和主節點斷開連接,可能是網絡問題。
3.主節點掛掉。
4.人為介入,比如修改節點優先級等
5.選舉還有個前提條件,參與選舉的節點數量必須大於副本集總節點數量的一半,如果已經小於一半了所有節點保持只讀狀態。
3 .搭建副本集集群
每個虛擬機都使用如下的配置文件啟動實例:
./mongod --dbpath=/data0/mongodbtest/replset/data/ --logpath=/data0/mongodbtest/replset/log/mongodb.log --port 27017 --replSet repset --maxConns=1000 --logappend --fork
介紹一下mongod啟動涉及到的參數 --dbpath 數據文件路徑 --logpath 日志文件路徑 --port 端口號,默認是27017.我這里使用的也是這個端口號. --replSet 復制集的名字,一個replica sets中的每個節點的這個參 數都要用一個復制集名字,這里是mvbox. --maxConns 最大連接數 --fork 后台運行 --logappend 日志文件循環使用,如果日志文件已滿,那么新日志覆蓋最久日志。
然后在任意一台虛擬機登陸mongo,輸入如下設置
三個副本集組成的集群
config = { _id:"mvbox", members:[ {_id:0,host:"192.168.1.1:27017"}, {_id:1,host:"192.168.1.2:27017"}, {_id:2,host:"192.168.1.3:27017"}] } rs.initiate(config);
帶有延遲復制的集群(設置優先級可以指定主輔副本集)
config = { _id:"repset", members:[
{_id:0,host:"192.168.4.135:27017",priority:2},
{_id:1,host:"192.168.4.136:27017",priority:0,slaveDelay:3600},
{_id:2,host:"192.168.4.138:27017",priority:10}]
}
rs.initiate(config);
初始化集群時,設置仲裁者的配置如下
config = { _id:"mvbox", members:[
{_id:0,host:"192.168.1.1:27017"},
{_id:1,host:"192.168.1.2:27017",arbiterOnly:true},
{_id:2,host:"192.168.1.3:27017"}]
}
rs.initiate(config);
注意:mvbox為集群名
可以看到副本集已經生效
可以使用rs.status()查看集群狀態,或者rs.isMaster()
4. 更改節點優先級
修改節點的優先級可以觸發重新選舉,這樣可以人工指定主節點。
使用如下命令,在主節點登錄,將192.168.1.3提升為Master。
rs.conf(); cfg=rs.conf(); cfg.members[0].priority=1 cfg.members[1].priority=1 cfg.members[2].priority=10 rs.reconfig(cfg);
需要注意的是,修改節點優先級需要登錄Master節點運行。否則報錯。
再次查看集群狀態,可以看到192.168.1.3已經作為Master運行
5 .節點類型
MongoDB的節點類型有主節點(Master),副本節點(Slave或者稱為Secondary),仲裁節點,Secondary-Only節點,Hidden節點,Delayed節點和Non-Voting節點。
仲裁節點不存儲數據,只是負責故障轉移的群體投票,這樣就少了數據復制的壓力。
Secondary-Only:不能成為primary節點,只能作為secondary副本節點,防止一些性能不高的節點成為主節點。
Hidden:這類節點是不能夠被客戶端制定IP引用,也不能被設置為主節點,但是可以投票,一般用於備份數據。
Delayed:可以指定一個時間延遲從primary節點同步數據。主要用於備份數據,如果實時同步,誤刪除數據馬上同步到從節點。所以延遲復制主要用於避免用戶錯誤。
Non-Voting:沒有選舉權的secondary節點,純粹的備份數據節點。
6 .設置隱藏節點(Hidden)
隱藏節點可以在選舉中投票,但是不能被客戶端引用,也不能成為主節點。也就是說這個節點不能用於讀寫分離的場景。
將192.168.1.3設置為隱藏節點。
注意,只有優先級為0的成員才能設置為隱藏節點。
如果設置優先級不為0的節點為隱藏節點,則報錯如下
使用如下命令設置隱藏節點
cfg=rs.conf(); cfg.members[0].priority=10 cfg.members[1].priority=1 cfg.members[2].priority=0 cfg.members[2].hidden=1 rs.reconfig(cfg);
設置完成之后,使用rs.status()查看該節點還是SECONDARY狀態。
但是通過rs.isMaster()和rs.conf()可以看到這個節點的變化。
rs.isMaster()的hosts中192.168.1.3節點已經不可見
並且rs.conf()顯示該節點狀態為hidden
7 .設置仲裁節點
仲裁節點不存儲數據,只是用於投票。所以仲裁節點對於服務器負載很低。
節點一旦以仲裁者的身份加入集群,他就只能是仲裁者,無法將仲裁者配置為非仲裁者,反之也是一樣。
另外一個集群最多只能使用一個仲裁者,額外的仲裁者拖累選舉新Master節點的速度,同時也不能提供更好的數據安全性。
初始化集群時,設置仲裁者的配置如下
config = { _id:"mvbox", members:[ {_id:0,host:"192.168.1.1:27017"}, {_id:1,host:"192.168.1.2:27017",arbiterOnly:true}, {_id:2,host:"192.168.1.3:27017"}] }
使用仲裁者主要是因為MongoDB副本集需要奇數成員,而又沒有足夠服務器的情況。在服務器充足的情況下,不應該使用仲裁者節點。
8 .設置延遲復制節點(延遲節點)
MongoDB官方沒有增量備份方案,只有一個導出的工具mongodump。
他不能像數據庫一樣,通過binlog或者歸檔日志將數據推到事故發生的前一刻。
假設每天凌晨2點使用mongodump備份,而下午5點發生事故,數據庫損毀,則凌晨2點到下午5點的數據全部都會丟失。
雖然副本集可以一定程度避免這個問題,但是默認情況下不能避免人為的失誤。
比如沒有指定篩選條件刪除了全部的數據。副本節點會應用這個命令,刪除所有副本節點的數據。
在這個場景下,可以使用延遲節點,它會延遲應用復制。
如果主節點發生了人為的失誤,而這個操作因為延遲的原因,還沒有應用在延遲節點。
這個時候,修改延遲節點的優先級為最高級,使他成為新的Master服務器。
延遲節點的優先級必須為0.這個和hidden節點是一樣的。
設置192.168.1.2為延遲節點
cfg=rs.conf(); cfg.members[1].priority=0 cfg.members[1].slaveDelay=3600 rs.reconfig(cfg);
slaveDelay的單位是秒
在192.168.1.1主節點刪除一個集合所有數據,模擬人為失誤。
db.users.remove({});
在192.168.1.3查看,發現數據已經全部丟失。
db.users.find();
而在192.168.1.2延遲節點,可以看到因為延遲復制的緣故,數據還在。
這個時候千萬不要提升延遲節點的優先級。因為這樣他會立即應用原主節點的所有操作,並成為新的主節點。這樣誤操作就同步到了延遲節點。
首先,關閉副本集中其他的成員,除了延遲節點。
刪除其他成員數據目錄中的所有數據。確保每個其他成員的數據目錄都是空的(除了延遲節點)
重啟其他成員,他們會自動從延遲節點中恢復數據(且並不改變之前的節點配置)。
9 .設置Secondary-Only節點
Priority為0的節點永遠不能成為主節點,所以設置Secondary-only節點只需要將其priority設置為0.
10 .設置Non-Voting節點
假設設置192.168.1.1不能投票,則使用如下命令
cfg=rs.conf(); cfg.members[0].votes=0; rs.reconfig(cfg);
11 .副本集成員狀態
副本集成員狀態指的是rs.status()的stateStr字段
STARTUP:剛加入到復制集中,配置還未加載 STARTUP2:配置已加載完,初始化狀態 RECOVERING:正在恢復,不適用讀 ARBITER: 仲裁者 DOWN:節點不可到達 UNKNOWN:未獲取其他節點狀態而不知是什么狀態,一般發生在只有兩個成員的架構,腦裂 REMOVED:移除復制集 ROLLBACK:數據回滾,在回滾結束時,轉移到RECOVERING或SECONDARY狀態 FATAL:出錯。查看日志grep “replSet FATAL”找出錯原因,重新做同步 PRIMARY:主節點 SECONDARY:備份節點
12. 讀寫分離
如果Master節點讀寫壓力過大,可以考慮讀寫分離的方案。
不過需要考慮一種場景,就是主服務器的寫入壓力非常的大,所以副本節點復制的寫入壓力同樣很大。
這時副本節點如果讀取壓力也很大的話,根據MongoDB庫級別讀寫鎖的機制,
很可能復制寫入程序拿不到寫鎖,從而導致副本節點與主節點有較大延遲。
如果進行讀寫分離,首先需要在副本節點聲明其為slave,
db.getMongo().setSlaveOk();
其中的ReadRreference有幾種設置:
primary:默認參數,只從主節點上進行讀取操作;
primaryPreferred:大部分從主節點上讀取數據,只有主節點不可用時從secondary節點讀取數據。
secondary:只從secondary節點上進行讀取操作,存在的問題是secondary節點的數據會比primary節點數據“舊”。
secondaryPreferred:優先從secondary節點進行讀取操作,secondary節點不可用時從主節點讀取數據;
nearest:不管是主節點、secondary節點,從網絡延遲最低的節點上讀取數據。
MongoDB客戶端配置,可以提出來做成spring注入,設置最大連接數什么的。
MongoClientOptions options = MongoClientOptions.builder().maxWaitTime(1000 * 60 * 2) .connectionsPerHost(500).build(); mongoClient = new MongoClient(Arrays.asList(new ServerAddress("10.205.68.57", 8700), new ServerAddress("10.205.68.15", 8700), new ServerAddress("10.205.69.13", 8700)), options); mongoClient.setReadPreference(ReadPreference.secondaryPreferred());
13.mongodb慢查詢的設置
開啟慢日志 1.查看mongodb慢日志是否開起 use BJ_Rack; db.getProfilingStatus(); 發現沒有開戶慢日志 2.開啟慢日志,設置超過200毫秒的操作為慢操作 db.setProfilingLevel(1); db.setProfilingLevel(1,200); 超過200ms的才記錄 0 不開啟 1 記錄慢查詢 2 記錄所有查詢 db.getProfilingStatus() 3.查看慢日志內容 得到50個比較慢的操作日志. db.system.profile.find().sort({$natural:-1}) #### 查看查詢時間超過某個時間的 use local db.system.profile.find({millis:{$gte:500}}).sort({"ts":-1}).limit(3) #### 查詢最慢的查詢 db.system.profile.find().sort({"millis":-1}).limit(3) 通過配置文件開啟: operationProfiling: mode: slowOp slowOpThresholdMs: 100 查看當前操作 db.currentOp(true);
查看當前節點的連接情況
db.serverStatus().connections