【六】MongoDB管理之副本集
一、復制介紹
所謂的復制就是在多個主機之間同步數據的過程。
1、數據冗余及可用性
復制技術提供數據冗余及可用性,在不同的數據庫服務器上使用多個數據副本,復制技術防止單個數據庫服務器出現數據故障而出現數據丟失。通過設置從庫,你能在上面進行災難切換、數據備份、報表服務等。在某些應用場景下,你還能提高讀的能力,客戶端通過將讀和寫請求分發到不同的服務器上面。
2、MongoDB復制技術
副本集是一組共享相同數據集的mongod實例。當所有寫請求發向主庫,而其他從庫從主庫上應用這些操作,以保證所有成員數據的一致性。
副本集中只能有一個主庫接收客戶端的寫請求。當主庫接受寫請求後,進行數據操作,這些操作都記錄到操作日誌中,稱為oplog。從庫復制這些oplog並應用這些操作以保證與主庫的數據集一致。如果這時主庫不可用,集群中一個合格的從庫通過競選接管成為新的主庫。你也可以在復制集群中添加一個mongod實例,作為仲裁者存在,它並不維護任何數據,它存在的意義在於對集群中其他成員發來的競選請求和心跳檢測做出響應。它對服務器硬件要求並不高。它永遠都作為仲裁者存在,無論主庫down了,還是某個從庫成為新主庫。
1)異步復制
從庫應用日誌中數據操作,是一種異步的方式。
2)自動接管
當主庫和其他成員通信過程中,超10秒還無法聯通那麽一個符合條件的從庫通過競選將成為新的主庫。在mongodb3.2版本中,引入了新版本的復制協議,以減少接管的時間。
3)讀操作
默認情況下,客戶端的讀請求是發往主庫上的,但是可以設置將客戶端的讀請求發送到從庫上,但是由於復制技術采用的異步方式,這意味著在從庫上讀到的數據有時會產生延遲,與主庫數據出現不一致。
二、復制實踐
1、副本集中成員
主要包括三個:主庫、從庫、仲裁者。在一個副本集群中,對成員個數的最低要求是:一個主庫、一個從庫、一個仲裁者。但是大多數實際應用中是一個主庫、兩個從庫。在3.0版本中一個集群中最多可以達到50個成員,在3.2版本中可以有12個成員。
1)主庫
在一個副本集群中,只能存在一個主庫,接收所有寫請求。MongoDB應用寫操作到數據文件中並記錄操作到日誌文件oplog中。從庫成員復制這些oplog日誌並應用操作到他們的數據集中。在集群中,所有成員都能接收讀請求,但是默認上應用程序的讀請求直接被發送到主庫上。當主庫不可用了,這就觸發了競選,會在剩下的從庫中選擇一個新主庫。
在某些情景下,會有兩個節點有那麽一瞬間認為他們自己是主庫,但是他們最多只有一個能夠完成寫操作,它就是目前的主庫,並且另外一個是前主庫還沒有覺察它被降級了,典型的由於是網絡分區。當這種情況出現時,連接到前主庫的客戶端也許會察覺到過期數據,最後進行回滾。
2)從庫
為了復制數據,從庫采用異步的方式,復制主庫上的oplog並應用日誌中操作到自己的數據集。一個主從集群環境中可以存在多個從庫。
- Priority 0 Replica Set Members
當從庫設置優先級為0,表示這個從庫不能成為主庫,因為優先級為0的成員無法參加競選。
- Hidden Replica Set Members
隱藏從庫是對應用程序不可見的。前提必須是不能變成主庫,也就是優先級為0
- Delayed Replica Set Members
設置某個從庫的數據延遲主庫多久,主要用來防止人為錯誤時進行恢復使用的,也就是當做備份。前提是延遲從庫必須優先級為0,且為隱藏從庫。
3)仲裁者
它沒有數據集並且不能成為主庫,它的存在可以允許主從復制集群中成員數為奇數,因為它總有一個投票權。
2、副本集部署架構
復制集群的架構能夠影響集群的容量及性能。標準的生產環境部署架構是一個具有三個成員的復制集群,能夠很好的提供容錯和冗余能力。一般而言,我們要避免復雜,凡事根據實際的應用需求設計集群架構。
下面介紹幾種常用的架構:
1)具有三成員復制集群
復制集群最低要求需要有三個成員,在三成員架構中,分為一主兩從和一主一從一仲裁者。
- 一主兩從模式:當主庫不可用,兩個從庫通過競選成為新主庫
- 一主一從一仲裁:當主庫不可用,這個唯一從庫將會成為新主庫
下面我們就對常用的一主兩從進行配置部署:
【實驗環境】:
主機IP 主機名 端口 功能說明
192.168.245.129 node1 27017 primary
192.168.245.131 node2 27017 secodary
192.168.245.132 node3 27017 secodary
1.1)在三臺獨立的主機上安裝mongodb,請參考:http://www.cnblogs.com/mysql-dba/p/5033242.html
1.2)確保三臺主機上的mongodb實例相互能夠連接上。具體方法如下:
- 在node1主機上進行:
mongo --host 192.168.245.131 --port 27017 mongo --host 192.168.245.132 --port 27017
- 在node2主機上進行:
mongo --host 192.168.245.129 --port 27017 mongo --host 192.168.245.132 --port 27017
- 在node3主機上進行:
mongo --host 192.168.245.129 --port 27017 mongo --host 192.168.245.131 --port 27017
1.3)啟動所有主機上mongod服務,特別的需要加上--replSet "rs0" 參數,指定復制集群名稱。
mongod --dbpath=/data/db --fork --logpath=/data/log/mongodb.log --replSet "rs0" #一個集群上成員replSet名字必須一樣
1.4)連接到mongo shell環境中,進行復制配置文件初始化:
rs.initiate() #這個操作只需要在一臺上進行,一般都是在主庫上進行
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "node1:27017",
"ok" : 1
}
1.5)確認復制集群的配置初始化
rs0:SECONDARY> rs.conf() { "_id" : "rs0", "version" : 1, "protocolVersion" : NumberLong(1), "members" : [ { "_id" : 0, "host" : "node1:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 } ], "settings" : { "chainingAllowed" : true, "heartbeatIntervalMillis" : 2000, "heartbeatTimeoutSecs" : 10, "electionTimeoutMillis" : 10000, "getLastErrorModes" : { }, "getLastErrorDefaults" : { "w" : 1, "wtimeout" : 0 } } }
1.6)在主庫上添加其他成員:
rs0:PRIMARY> rs.add("192.168.245.131") { "ok" : 1 } rs0:PRIMARY> rs.add("192.168.245.132") { "ok" : 1 }
1.7)使用rs.status()方法查看這時集群的狀態:
View Code1.8)進行測試驗證主從復制功能:
#在主庫上插入一個文檔 rs0:PRIMARY> db.testrp.insert({"name":"test replication"}) WriteResult({ "nInserted" : 1 }) #到從庫上查看,已經有了主庫上的數據 rs0:SECONDARY> db.testrp.find({"name":"test replication"}) { "_id" : ObjectId("5663906284c32afdaa84c21f"), "name" : "test replication" }
#將主庫kill掉,看看從庫能否會接管成新主庫
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2015-10-23T01:03:10.811Z"),
"myState" : 1,
"term" : NumberLong(2),
"heartbeatIntervalMillis" : NumberLong(2000),
"members" : [
{
"_id" : 0,
"name" : "node1:27017",
"health" : 0, #這時主庫被kill了,所以為0
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2015-10-23T01:03:10.475Z"),
"lastHeartbeatRecv" : ISODate("2015-10-23T01:02:30.456Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "Connection refused",
"configVersion" : -1
},
{
"_id" : 1,
"name" : "192.168.245.131:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY", #這臺通過競選成為了新主庫
"uptime" : 6590,
"optime" : {
"ts" : Timestamp(1449365602, 4),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2015-12-06T01:33:22Z"),
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1449365602, 3),
"electionDate" : ISODate("2015-12-06T01:33:22Z"),
"configVersion" : 3,
"self" : true
},
{
"_id" : 2,
"name" : "192.168.245.132:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 2879,
"optime" : {
"ts" : Timestamp(1449365602, 4),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2015-12-06T01:33:22Z"),
"lastHeartbeat" : ISODate("2015-10-23T01:03:10.463Z"),
"lastHeartbeatRecv" : ISODate("2015-10-23T01:03:09.290Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "192.168.245.131:27017",
"configVersion" : 3
}
],
"ok" : 1
}
2)四成員及以上的復制集群
mongodb允許在復制集群中添加更多的主機,當然需要考慮下面的問題:
- 確保集群中有奇數個選舉投票成員,如果有偶數個成員,那麽可以部署一個仲裁成員,保證選舉投票成員數量是奇數。
- mongodb復制集群最多可以有50個成員,7個投票成員,如果已經達到7個投票成員了,如果再加入的主機必須為non-voting。
- 集群中成員的位置。必須要保證集群成員數的絕大多數在一個數據中心,比如一共有5個成員,那麽需要3個在同一數據中心。
3)基於地理位置的分布式復制架構
將一個復制集群部署到多個數據中心,能夠保證系統的冗余並提高容錯能力。即使有一個數據中心不可用了,另一個數據中心也可以繼續提供服務。但是在另外的數據中心(這裏我就稱為第二數據中心)中的mongodb實例優先級必須設置為0,不讓它接管主庫。例如,下面是簡單的基於地理位置的架構:
如果主數據中心不可用,你可以手工從第二數據中心進行恢復數據集以最小的宕機時間。這也是為什麽要保證一個數據中心中的成員數要超過大多數,這樣才能保證投票數能夠選擇一個新主。
3、復制集群高可用
復制集群采用自動接管的方式保證系統的高可用。當主庫不可用,會通過選舉投票的方式選擇一個新主。集群中各成員持有相同的數據集,但是其他方面都是獨立的。在接管成為新主過程中,有些情況下接管進程也許會執行回滾。
- 接管時如何選舉的?
復制集群采用選舉制來決定哪個成員成為主庫。選舉一般出現在兩個時間點:一是復制集群初始化完成後;二是主庫不可用時。在集群中,選舉制是必不可少的獨立操作,但是選舉需要時間才能完成,在選舉正在進行中,這個時候集群中沒有主庫且不能接收客戶端的寫請求,其他的成員這時都是只讀狀態(所以如果設置從庫能讀的話),系統的讀服務不受影響。寫服務會短暫無法操作。除非必要,Mongodb盡量避免選舉發生。
影響投票條件:
心跳檢測:每隔兩秒,集群中成員會發送心跳(pings)到其他主機上,如果10秒內沒有回應,那麽認為該主機不可用。
優先級參數:優先級越高,越能夠成為主庫,為0時不能成為主庫。在選舉過程中,優先級低的成員有機會成為主庫,如果出現這種情況,會繼續投票選舉,直到優先級高的成為主庫。
網絡分區:保證一個數據中心中的成員數要超過大多數
- 在接管後回滾?
接管完成後,待原主庫被修復重新加入集群中需要進行回滾寫操作。只有當原主庫接收了寫操作且在宕機前從庫沒有成功應用時,回滾操作才是必須的操作。這樣當它重新加入,才能夠保證數據的一致性。
MongoDB努力避免回滾發生,因為這種情況很少出現,一般發生在網絡分區的環境中。如果原主庫在不可用之前寫操作復制到其他任意從庫上了且那個成員對於大多數成員是可訪問的,那麽將不會發生回滾。
【收集回滾數據】:
如果出現回滾,DBA必須決定是否應用這些回滾。MongoDB將回滾數據寫到BSON文件中,具體位於數據目錄的rollback/下,文件命名如下:
<database>.<collection>.<timestamp>.bson records.accounts.2011-05-09T18-10-04.0.bson #比如這個文件名
在原主庫進行了回滾並降級為從庫後,DBA必須要應用這些回滾數據到新主庫上。用bsondump可以讀回滾文件內容,然後用mongorestore工具進行應用。
【避免回滾】:
對於一個復制集群,默認采用write concern {w: 1},如采用這個參數,那麽在主庫宕機後且寫操作已復制到任一從庫上時還是會進行回滾。為了避免這樣,可以用w: majority write concern代替。
【回滾限制】:
mongd實例不會回滾超過300字節的數據,如果你需要回滾超過300字節的數據,那麽必須手工介入。
4、副本集處理讀寫請求
1) 安全寫級別(write concern)
在後臺,無論mongodb運行在單獨一臺主機上還是在復制集群中,它對前端應用程序都是透明的。對於寫操作,前端需要返回確認信息,是否真正寫入成功了呢?對於復制集群中,默認的寫確認僅僅發送到主庫上,但是我們也可以修改以發送到更多的主機上。目前有兩種方法:
- 重載writeConcern方法:
db.products.insert( { item: "envelopes", qty : 100, type: "Clasp" }, { writeConcern: { w: 2, wtimeout: 5000 } } )
在插入數據時重載writeConcern方法,將w的值改為2,表示寫確認發送到集群中兩臺主機上包括主庫。
writeConcern方法參數說明:
{ w: <value>, j: <boolean>, wtimeout: <number> }
w:表示寫操作的請求確認發送到mongod實例個數或者指定tag的mongod實例。具體有以下幾個值:
0:表示不用寫操作確認;
1:表示發送到單獨一個mongod實例,對於復制集群環境,就發送到主庫上;
大於1:表示發送到集群中實例的個數,但是不能超過集群個數,否則出現寫一直阻塞;
majority:v3.2版本中,發送到集群中大多數節點,包括主庫,並且必須寫到本地硬盤的日誌文件中,才算這次寫入是成功的。
<tag set>:表示發送到指定tag的實例上;
j:表示寫操作是否已經寫入日誌文件中,是boolean類型的。
wtimeout:確認請求的超時數,比如w設置10,但是集群一共才9個節點,那麽就一直阻塞在那,通過設置超時數,避免寫確認返回阻塞。
當寫入操作很多時,每次都需要重載writeConcern方法,太麻煩了,那麽可以參考下面的方法,修改配置。
- 修改復制集群的配置:
cfg = rs.conf() cfg.settings = {} cfg.settings.getLastErrorDefaults = { w: "majority", wtimeout: 5000 } rs.reconfig(cfg)
2)讀安全級別和讀選項
這裏有兩個概念,必須弄明白。
讀安全級別(readConcern),這個是v3.2版本新引進的,允許客戶端設置合適的讀隔離級別。如下:
readConcern: { level: <majority|local> }
默認情況下,mongodb選擇local作為讀級別,它能夠讀取主庫上最新的數據但是在沒有回滾情況下(可能主庫不可用時進行回滾)。你可以選擇majority級別,這時能夠保證數據已經寫到多個節點上並不會回滾。
為了設置安全級別,可以在啟動實例是指定--enableMajorityReadConcern參數。或者將replication.enableMajorityReadConcern配置到文件中。下面這些命令支持readConcern:
- find command
- aggregate command and the db.collection.aggregate() method
- distinct command
- count command
- parallelCollectionScan command
- geoNear command
- geoSearch command
讀選項描述了mongodb讀請求的訪問路徑,默認下客戶端的讀請求直接發送到主庫。那麽有時我想做讀寫分離或者減輕主庫的讀寫壓力時,就需要把讀請求分流到其他secondary庫上,這個時候讀選項就派上用處了。共有五種讀選項模式:
Read Preference Mode | Description |
---|---|
primary | 默認的,讀請求發送到主庫 |
primaryPreferred | 在多數情況下, 從主庫上讀,只有主庫不可用了,這時從secondary庫讀。 |
secondary | 從secondary庫讀 |
secondaryPreferred | 在多數情況下, 從secondary庫上讀,只有secondary庫都不可用了,這時從主庫讀。 |
nearest | 從副本集中網絡延時最少的庫上讀,這時不分主從庫了。 |
這些參數可以在應用程序連接mongodb時通過函數指定或在shell中通過函數指定。具體如下:
在shell中:
db.collection.find().readPref( { mode: ‘nearest‘} )
在java程序中,連接到整個副本集,它不關心具體哪一臺機器是主還是從,所以這時幾個參數就發揮作用了。
List<ServerAddress> addresses = new ArrayList<ServerAddress>(); ServerAddress address1 = new ServerAddress("192.168.245.129" , 27017); ServerAddress address2 = new ServerAddress("192.168.245.131" , 27017); ServerAddress address3 = new ServerAddress("192.168.245.132" , 27017); addresses.add(address1); addresses.add(address2); addresses.add(address3);
MongoClient client = new MongoClient(addresses); DB db = client.getDB( "test" ); DBCollection coll = db.getCollection( "testdb" ); BasicDBObject object = new BasicDBObject(); object.append( "test2" , "testval2" ); //讀操作從副本節點讀取 ReadPreference preference = ReadPreference.secondary(); DBObject dbObject = coll.findOne(object, null , preference); System. out .println(dbObject);
讀選項值為secondary的架構如下,也就是常說的讀寫分離,當然這種模式不能保證讀到的數據是最新的,因為從庫也許會有一定的延時。
以上五種參數要根據應用需求來設置,不可盲目指定,一般建議如下:
- 追求數據一致性最大化:
采用primary讀選項和majority安全讀級別,但是當主庫不可用時,如果大多數成員也不可用時,就會報錯。還可以禁用自動故障切換,這樣會犧牲系統可用性。
- 追求系統可用性最大化:
采用primaryPreferred讀選項,但是會增加主庫的讀寫壓力。
- 追求最小延時:為了總是讀到延時小的節點,可以采用nearest。
3)讀選項執行過程
當我們選擇一個非主鍵的讀選項時,mongodb驅動程序是通過如下幾個過程決定從一個成員上讀:
- 收集可用的成員,分析他們的類型(主庫,從庫等)
- 如果設置了標簽集,排除不滿足的成員
- 判斷哪些成員離客戶端最近(網絡延時短)
- 在上面最近的成員中ping距離為毫秒的成員建立列表
- 從這些列表主機中隨機選擇一個成員進行讀操作
5、復制過程
副本集中的成員是連續不斷的復制數據,首先,成員用initial sync鋪捉數據集的變化,然後連續不斷的記錄和應用這些變化的數據。每個成員都把數據變化記錄到oplog中,oplog實際上是個封頂集合。
1)副本集的oplog
oplog(operation log)是一種特別的封頂集合,當每次修改數據庫數據時,它會滾動的記錄數據變化的操作。MongoDB在主庫上應用數據操作並記錄到oplog中,然後從庫復制oplog並以單線程方式應用這些操作。所有副本集成員在local.oplog.rs集合中都持有一份rplog的副本,以此來維護數據庫的狀態。所有副本集成員都會向其他成員發送心跳檢測,任何成員都可以從其他成員那導入oplog條目。
- oplog文件大小
當你第一次啟動成員時,就默認指定了日誌文件大小,一般是操作系統硬盤可用空間的5%,1G-50G.一般而言,這個文件大小都足夠用了,但是你也可以修改這個文件大小,具體參考官方文檔。
- 需要大oplog的情況
#一次更新很多文檔,oplog為了遵循冪等性,需要將多個更新轉換為單獨的操作,這個占用空間非常多。 #刪除的數據等於插入的數量 #大量的原位更新,就是沒有改變硬盤數據的大小,但是需要記錄很多日誌操作。
- oplog狀態
rs0:PRIMARY> rs.printReplicationInfo() configured oplog size: 1527.7419919967651MB log length start to end: 6028secs (1.67hrs) oplog first event time: Sun Dec 06 2015 07:52:54 GMT+0800 (CST) oplog last event time: Sun Dec 06 2015 09:33:22 GMT+0800 (CST) now: Sat Oct 24 2015 11:30:48 GMT+0800 (CST) rs0:PRIMARY>
2)數據同步
為了保持各副本成員擁有最新的數據,副本集裏secondary成員通過sync或復制其他成員的數據。MongoDB提供兩種方式進行secondary成員間的數據同步:
- 初始化同步(Initial Sync)
初始化同步會拷貝所有的數據到另一個成員那裏,一般當這個成員是新加入的沒有數據或者有數據但是丟失了部分歷史數據會使用初始化同步。當進行初始化同步操作時,MongoDB會:
1、克隆源成員所有數據庫,為了克隆數據庫,mongod會查詢所有源成員中的集合並將這些數據插入到源成員的副本中,包括建立_id索引。克隆進程只是克隆合法的數據,忽略無效的數據 2、到目標成員上應用克隆的rplog副本 3、給所有集合建立索引
- 復制(replication)
當初始化同步完成後,成員會從初始同步源成員那連續的復制oplog,並采用異步的方式應用這些數據操作。一般情況下,都是從主庫進行同步。當然,副本成員也會自動的改變源成員,為了能同步,兩個成員之間members[n].buildIndexes參數值必須一樣。另外,副本成員不會從延遲類型和隱藏類型成員那同步數據。
6、主從復制
對於新的應用,MongoDB建議采用副本集而不是主從復制。以前都是采用主從復制的,後來引入了副本集,這裏就不介紹了,請查看官方文檔。
註意:由於本人翻譯水平有限,有時無法分開副本集和主從復制專業術語的區別,抱歉!本文中都是指副本集
【六】MongoDB管理之副本集