複本集的概念
複本集是一組伺服器,其中有一個是主伺服器(primary),用於處理用戶端請求;還有多個備份伺服器(secondary),用於儲存主伺服器的資料副本。如果主伺服器崩潰了,備份伺服器會自動將其中一個成員升級為新的主伺服器。
bj1-farm1:PRIMARY> rs.isMaster()
{
"setName": "bj1-farm1",
"setVersion": 4,
"ismaster": true,
"secondary": false,
"hosts": [
"172.16.0.150:27017",
"172.16.0.152:27017",
"172.16.0.151:27017"
],
"primary": "172.16.0.150:27017",
"me": "172.16.0.150:27017",
"maxBsonObjectSize": 16777216,
"maxMessageSizeBytes": 48000000,
"maxWriteBatchSize": 1000,
"localTime": ISODate("2014-12-01T08:20:34.014Z"),
"maxWireVersion": 2,
"minWireVersion": 0,
"ok": 1
}
建立測試複本集 1 啟動mongod服務時在每個機子上添加—replSet spock選項,或者寫入設定檔,其中spock是標示符 2 建立設定檔
config ={
"_id":"spock",
"members":[
{"_id":0,"host":"10.0.11.243:27017"},
{"_id":1,"host":"10.0.11.244:27017"}
]
} 3 串連到資料庫並初始化複本集
db = (newMongo("10.0.11.243:27017")).getDB("test")
rs.initiate(config)
4 master插入資料測試
for(i=0;i<1000;i++){db.coll.insert({count:i})}
db.coll.count()
5 在備份節點上測試,先修改使得備份節點可讀
db.setSlaveOk()
db.coll.count()
停止複本集
Rs.stopSet()
Rs輔助函數
Rs是一個全域變數,其中包含與複製相關的輔助函數可用rs.help(),這些函數大多隻是資料庫命令的封裝器,例如
Db.adminCommand({“replSetInitiate”:Config})和之前的是等價的
網路注意事項
需要保證複本集每個成員能夠互相到達,不應該使用localhost作為主機名稱
修改複本集 添加新成員
rs.add(“server-3:27017”) 刪除成員
rs.REMOVE(“SERVER-3:27017”) 在刪除成員時會報錯無法串連到資料庫的資訊,這是正常的,說明配置修改成功了。重新設定複本集過程中的最後一步,主節點會關閉所有串連,因此shell會短暫斷開然後重新自動建立串連
查看配置修改是否成功
spock:PRIMARY> rs.config()
{
"_id": "spock",
"version": 1,
"members": [
{
"_id": 0,
"host": "10.0.11.243:27017"
},
{
"_id": 1,
"host": "10.0.11.244:27017"
}
]
}
每次修改完,version會自增,初始值為1;
也可以建立新的配置文檔,再使用rs.reconfig重新設定複本集
>var config=rs.config()
>config.members[0].host = “server-0:27017”
>rs.reconfig(config)
設計複本集
複本集一個很重要的概念是“大部分”:選擇主節點需要大多數決定,即副本中一半以上的成員,所以如果使用只有兩個成員的複本集,一個掛了,網路的任何一端都無法達到大多數的要求,因此通常不建議使用這樣的配置 兩種推薦的配置方式
1 將“大多數”成員放在同一個資料中心
2 在兩個資料中心各自放置數量相等的成員,在第三個地方放置一個用於決定勝負的複本集成員
仲裁節點
仲裁者唯一的作用就是選舉,不儲存資料,也不會為用戶端提供服務,它只是為了滿足“大部分”的要求
添加仲裁者的兩種方式
>rs.addArb(“server-5:27017”)
>rs.add({“_id”:4,”host”:” server-5:27017”,”arbiterOnly”:true})
仲裁的缺點
比如有三個成員的複本集,其中一個是仲裁節點。當一個資料節點掛了,那麼另一個資料節點成為主節點,為了保證資料安全,就需要添加一個新的備份節點,但由於仲裁節點無資料,那麼新節點的資料轉送只能靠當前的主節點完成。那麼它不僅要處理應用程式請求,還要資料複製到備份節點,會造成伺服器壓力巨大
所以盡量配置成奇數個資料成員,而不使用仲裁者
優先順序
優先順序表示一個成員渴望成為主節點的程度,取值範圍0-100,預設1,0代表永遠不能成為主節點(passive member)
優先順序別高的會優先選舉為主節點(只要他能得到大部分的支援,並且資料是最新的)
隱藏成員
用戶端不會向隱藏成員發送請求,隱藏成員也不會成為複製源(除非其他複製源不可用),因此可以將不夠強大的伺服器或者備份伺服器隱藏起來,只有優先順序別為0的成員才能被隱藏,通過設定配置中的hidden:true來隱藏。隱藏以後執行isMater()將看不到隱藏成員,使用rs.status和rs.config()能夠看到隱藏成員。因為用戶端串連到複本集時,調用的是isMaster()來查看可用成員
延遲備份節點
用於災難保護,和mysql延遲複製功能類似,使用slaveDelay設定一個延遲的備份節點,要求成員優先順序別為0
建立索引
有時備份節點不需要與主節點一樣的索引,甚至不用索引,尤其這個備份節點用途僅僅只是處理資料備份或者是離線的批量任務,那麼可以再配置中指定buildIndex:false。要求此節點優先順序別為0,並且是個永久性行為,除非刪除這個節點並重新添加到這個複本集進行資料同步。
同步
Mongo的複製功能是通過oplog實現的,oplog包含了主節點的每一次寫操作,是主節點的local資料庫中的一個固定集合,備份節點通過查詢這個集合就可以知道需要進行複製的操作。每個備份節點有自己的oplog,這樣每個成員就可以當作同步源提供給其他成員使用。備份節點從當前同步源中擷取需要執行的操作,然後在自己的資料集上執行這些操作,最後將這些操作寫入自己的oplog。
如果備份節點掛了,當它重啟後,會自動從oplog中最後一個操作開始同步,由於複製操作的過程是先複製資料再寫入oplog,所以備份節點有可能在已經同步過的資料上再次執行複製操作。Mongo是這麼處理的:將oplog中的同一操作執行多次與只執行一次的效果是一樣。
由於oplog大小是固定的,通常它的使用空間的增長速度與系統處理寫請求的速率近乎相同。但是有些例外情況:如果單次處理能夠影響到多個文檔,那麼每個受影響的文檔都會對應oplog的一條日誌。比如執行db.coll.remove()刪除了1000000個文檔,那麼oplog中就會有1000000條動作記錄,這樣oplog很快就會被填滿。
初始化同步
複本集的成員啟動後,就會檢查自身狀態,確定是否可以從某個成員那裡進行同步,如果不行,它會嘗試從副本的另外一個成員那裡進行完整的資料複製,這就是初始化同步(initial sync),一般有如下幾個步驟
1 選擇一個成員作為同步源,在local.me中為自己建立一個標示符,刪除所有已存在的資料庫,已一個全新的狀態開始同步
2 複製,將同步源的所有記錄全部複製到本地
3 oplog同步,複製過程中的操作都會被記錄到oplog。如果有文檔在複製過程中被移動了,那麼就可能被遺漏,需要重新進行複製
4 將第一個oplog同步中的操作記錄下來,只有在沒有東西需要複製時,才會與第一個不同
5 建立索引
6 將建立索引期間的索引操作同步過來
7 完成初始化,切換到普通同步狀態
初始化同步引發的問題及解決方案
如果要跟蹤初始化同步過程,最好的方法是查詢服務器日誌
初始化同步操作簡單,但是速度太慢,遠不如從備份中恢複
複製可能損壞同步源的工作集。實際部署後會有一個頻繁使用的資料子集在記憶體中,執行初始化同步會強制將當前成員的所有資料分頁載入到記憶體中,這會導致頻繁資料不能常駐記憶體,進而導致很多請求變慢。不過對於較小的資料集和效能比較好的伺服器,初始化同步是個簡單易用的選項。
初始化同步如果耗費太長時間,新成員就會與同步源脫節,導致新成員的資料同步速度趕不上同步源的變化速度,同步源可能會將新成員需要複製的某些資料覆蓋掉。這個問題沒什麼好的方法解決,只能在不太忙的時候執行初始化同步。或者是讓主節點使用比較大的oplog儲存足夠多的動作記錄是很重要的。
心跳
每個成員每隔兩秒都會向其他成員發送一個心跳請求,用於檢查每個成員的狀態,知道自己是否符合大多數的條件。
復原
如果主節點執行了一次寫請求後掛了,但是備份節點還沒來得及複製這次操作,那麼新選舉出來的主節點就會漏掉這次寫操作。這時就會執行復原過程。
如果復原內容太多,mongo會承受不了,如果要復原的數量大於300m或者大於30分鐘,則復原失敗,需要重新同步。
應用程式串連到複本集
串連到複本集和串連到單台伺服器類似,常用的連接字串如下:
“mongodb://server-1:27017,server-2:27017”
主節點掛了後,驅動程式會儘快自動找到新的主節點。在選舉過程中,主節點可能會導致暫時不可用,這段時間內不會處理任何請求(讀或寫),但是可以選擇將讀請求路由到備份節點
等待寫入複製
使用getLastError命令檢查寫入是否成功,也可以用它確保寫入操作被複製到備份節點。參數“w”會強制要求getLastError等待,一直到給定數量的成員都執行完了最後的寫入操作。可通過majority關鍵字傳遞該值,wtimeout是逾時時間
spock:PRIMARY>db.runCommand({"getLastError":1,"w":"majority","wtimeout":1000})
{
"lastOp": Timestamp(0, 0),
"connectionId": 4776,
"n": 0,
"syncMillis": 0,
"writtenTo": null,
"err": null,
"ok": 1
}
通常“w”用來控制寫入速度,mongo寫入太快,主節點執行完寫入操作後,備份節點來不及跟上,通過定期調用getLastError,設定w值大於1可以強制這個串連上的寫操作一直等待直到複製成功,但是這會阻塞寫操作
自訂複製保證規則
1 保證複製到每個資料中心的一台伺服器上
2 保證寫操作被複製到可見節點中的大多數
3 建立其他規則
將讀請求發送到備份節點
1 出於一致性考慮
2 出於負載的考慮
適用情境
1 主節點掛了,要求備份節點能夠讀取資料(primarypreferred)
2 根據驅動程式到複本集的ping時間獲得低延遲的資料,即Nearest參數。如果應用程式需要從多個資料中心讀取到最低延遲的同一文檔,這是唯一的方法。如果文檔和位置的相關性更大,那就使用分區。如果是要求低延遲的讀和寫,那必須是分區方案。
3 如果能夠接受任何陳舊資料,那就使用secondary它會始終將讀請求發送到備份節點
4 secondary preferred:優先發送到可用的備份節點,不行再master
5 一般即時性要求高就primary,不是很高就primarypreferred
查看線上伺服器配置
spock:PRIMARY> db.serverCmdLineOpts()
{
"argv": [
"mongod",
"-f",
"/etc/mongod.conf"
],
"parsed": {
"config": "/etc/mongod.conf",
"net": {
"bindIp": "10.0.11.244"
},
"processManagement": {
"fork": true,
"pidFilePath": "/var/run/mongodb/mongod.pid"
},
"replication": {
"replSet": "spock"
},
"storage": {
"dbPath": "/var/lib/mongo"
},
"systemLog": {
"destination": "file",
"logAppend": true,
"path": "/var/log/mongodb/mongod.log"
}
},
"ok": 1
}
將主節點變為備份節點
rs.stepDown(60) //60秒內沒有其他人升為主節點就要求該節點重新進行選舉 阻止選舉
rs.freeze(1000)//在每個備份節點上執行,阻止其成為主節點,為了主節點做維護時防止其他節點篡位,rs.freeze(0)表示維護完成後可釋放
監控複製最簡單的方式就是查看日誌
監控複寫延遲,在備庫上運行以下命令
spock:SECONDARY>db.printSlaveReplicationInfo()
source: 10.0.11.243:27017
syncedTo:Mon Dec 01 2014 18:12:32 GMT+0800 (CST)
0secs (0 hrs) behind the primary
調整oplog大小的步驟
1 如果當前伺服器是主節點,讓他退位,使得其他成員能夠儘快更新到與它一致
2 關閉當前伺服器
3 將當前伺服器已單機模式啟動
4 臨時將oplog中的最後一條insert操作儲存到其他集合中
5 刪除當前的oplogdb.oplog.rs.drop()
6 建立一個新的oplog
7 將最後一條操作記錄寫回oplog
8 將當前伺服器作為複本集成員重新啟動
從延遲備份節點中恢複
方法1:可能導致這個成員過載
1 關閉所有其他成員
2 刪除其他成員資料目錄中的所有資料
3 重啟所有成員
方法2:導致每個伺服器有相同大小的oplog
1 關閉所有其他成員
2 刪除其他成員資料目錄中的所有資料
3 將延遲備份節點的資料檔案複製到其他伺服器
4 重啟所有成員