標籤:efault product 逾時 資料一致性 中心 near recv time 版本
一、複製介紹
所謂的複製就是在多個主機之間同步資料的過程。
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執行個體相互能夠串連上。具體方法如下:
mongo --host 192.168.245.131 --port 27017mongo --host 192.168.245.132 --port 27017
mongo --host 192.168.245.129 --port 27017mongo --host 192.168.245.132 --port 27017
mongo --host 192.168.245.129 --port 27017mongo --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 Code
1.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>.bsonrecords.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運行在單獨一台主機上還是在複製叢集中,它對前端應用程式都是透明的。對於寫操作,前端需要返回確認資訊,是否真正寫入成功了呢?對於複製叢集中,預設的寫確認僅僅發送到主庫上,但是我們也可以修改以發送到更多的主機上。目前有兩種方法:
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條目。
當你第一次啟動成員時,就預設指定了記錄檔大小,一般是作業系統硬碟可用空間的5%,1G-50G.一般而言,這個檔案大小都足夠用了,但是你也可以修改這個檔案大小,具體參考官方文檔。
#一次更新很多文檔,oplog為了遵循等冪性,需要將多個更新轉換為單獨的操作,這個佔用空間非常多。#刪除的資料等於插入的數量#大量的原位更新,就是沒有改變硬碟資料的大小,但是需要記錄很多日誌操作。
rs0:PRIMARY> rs.printReplicationInfo()configured oplog size: 1527.7419919967651MBlog 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成員間的資料同步:
初始化同步會拷貝所有的資料到另一個成員那裡,一般當這個成員是新加入的沒有資料或者有資料但是丟失了部分曆史資料會使用初始化同步。當進行初始化同步操作時,MongoDB會:
1、複製源成員所有資料庫,為了複製資料庫,mongod會查詢所有源成員中的集合并將這些資料插入到源成員的副本中,包括建立_id索引。複製進程只是複製合法的資料,忽略無效的資料2、到目標成員上應用複製的rplog副本3、給所有集合建立索引
當初始化同步完成後,成員會從首次同步處理源成員那連續的複製oplog,並採用非同步方式應用這些資料操作。一般情況下,都是從主庫進行同步。當然,副本成員也會自動的改變源成員,為了能同步,兩個成員之間members[n].buildIndexes參數值必須一樣。另外,副本成員不會從延遲類型和隱藏類型成員那同步資料。
6、主從複製
對於新的應用,MongoDB建議採用複本集而不是主從複製。以前都是採用主從複製的,後來引入了複本集,這裡就不介紹了,請查看官方文檔。
注意:由於本人翻譯水平有限,有時無法分開複本集和主從複製專業術語的區別,抱歉!本文中都是指複本集
【六】MongoDB管理之複本集