複製策略與複製的方式 【已翻譯100%】(1/2)
注意: 我寫一些短篇的博客已經有些日子了. 我認為是時候改變下了.我不確定這篇博客會帶來什麼影響,但應該會是很大的影響.請讓我知道你覺得更好的方法和理由。
這個問題我已經想了幾天了. 我試圖想找出通用的複製解決方案能夠應用到其他解決方案上去. 如果這個問題解決了,我們就能提供更多的功能組到更多的場景上.
但在之前,我們要談談怎麼去實現複製, 哪些類型的複製. 我們假設有一個單獨的數據庫 (沒分片,在不同的節點). 普通情況下, 將有下麵的選項:
主/從 (Master/slaves)
主/次 (Primary/secondaries)
多重可寫組合(Multi write partners)
多重主(Multi master)
上麵的是我接下來博客要談的內容.對於這些內容的目的,他們是完全不想關的。
主/從模式是指這樣一種情況,你隻有一個主寫節點和一個或多個從節點。這一模式的一大特點是你永遠無法(至少在正常操作的情況下)對從節點做任何形式的更改。它們純粹是用來讀的,即使冒著損壞數據的風險切斷它們與主節點的聯係它們也不能變成可寫的。
這種方法的一個常見的例子是日誌傳送。我將在後麵詳細討論它,但是你可以看看其他類似係統的文檔,將一個從節點變更為可寫是一個絕對不凡的經曆。得有一個很好的理由。
主/次級模式與主/從模式很類似,但在這種模式裏我們可以選擇一個次級節點成為主節點。隻能有一個主服務器,但好處是允許有一種簡單的方法來變更主節點。MongoDB就使用這樣一個係統。
多重可寫組合係統允許任何節點接受寫操作,並且它會留意將變更分發到其他節點。它也需要處理衝突,不像目前提到的其他選擇。擁有使兩個用戶在多個節點同時寫入相同值的能力。然而,多重可寫組合通常會對同伴節點進行假設。例如,它們會在同步時比較,並有一個單獨的協議用來將新的在線節點加入到常規複製組合中。
多重主係統允許、接受並鼓勵節點按需添加刪除,它們假設會有寫衝突,並有在運行的基礎上解決衝突的需求。其他節點間沒有相互同步的需求,它們通常“重新找主”到一個新的節點並開始複製它,這意味著你需要從一開始就複製所有數據。通常很希望到一個節點掛掉了,希望它在掛掉時已經完成所有變更,然後將它摘除。
讓我們看看每一個實際執行的細節,包括一些例子,希望這能讓我說得更清楚。
日誌泊運(Log Shipping)
主/從通常是通過日誌泊運來實現的。理解日誌泊運的最簡單方法是,主數據庫會發送(很神奇,我們真的不太在乎這一點是怎樣的)如何直接修改數據庫文件的指令給從數據庫。換句話說,從概念上講,它會發送類似以下內容:
1: writep(fd1, 1024, new[]{ 17,85,124,13,86}, 5);
2: writep(fd1, 18432, new[]{ 12,95,34,83,76,32,59}, 7);b
如你所想,這些是非常底層的修改。這個好處是非常易於捕捉和回放那些改變,而劣勢是,你真地不能做任何其它事情。因為改變發生在堆棧的最底部,沒有機會去運行任何各類的邏輯。我們隻是寫入到文件,如同主服務器所做的。
這就是為什麼允許從節點寫很難的最關鍵原因。當它產生任何獨立的寫操作時,它便冒著主節點也在寫的風險,這樣會產生數據衝突。這就是為什麼如果你想切換主從你必需做一係列事情。你必須處理完這些麻煩來保證你不會有兩端都寫的場景發生。
一旦發生這種情況,你永遠不不能再使兩端同步了。這發生的幾率很低。
增加一個新節點,反過來,這很容易。請務必保留過程,做一個完整的數據庫備份並將它移動到另一個節點。然後開始傳遞日誌。因為隻有它們在同一個點開始時一切才能安全實施。
請注意,備份的這個版本在處理版本問題上很敏感。你不能在使用最底層存儲的版本上做一丁點改變,因為那樣可能會丟失一切。這個方法用於生成讀複製很好用。事實上,這是大多數情況下使用的方法。
理論上你甚至可以用它來做故障轉移,因為如果主節點宕掉了,從節點可以接受寫。問題是你如何處理從節點以為主節點宕掉了,而主節點以為一切都正常這種情形。這種情況下你可能讓兩端都可寫,而這將導致不能合並地情況。
理論上講,因為它們有一個共同的根節點,你或許會覺得有一個引領者,並這樣做了,但是這樣會導致掉線服務器數據的丟失,或者你會沒有可行方法取回的數據。這裏我們記錄的變化非常小,並且粒度太小以至於不允許你在提取變化信息方麵做什麼有用工作。
Oplog
這實際上與日誌傳送方法非常類似,隻是不發送底層的文件I/O操作,我們實際上發送的是更高層的命令。這就我們而言有相當多的好處。主服務器可以像如下一樣發送日誌:
set("users/1", {"name": "oren" });
set("users/2", {"name": "ayende" });
del("users/1");
在次級節點上執行這套指令將導致次級上結果相同的狀態。不像日誌傳送那樣,這實際上需要次級服務器進行工作,所以相比應用已經計算過的文件更新這樣的代價更昂貴。
然而,這樣做的好處是,你可以有一個更可讀的日誌。它也使把副服務器轉為主服務器變得容易很多。最主要的是,如果你不傻的話:它們實際的操作完全是相同的一回事,但因為你是在協議層上工作,而不是文件級,所以你可以得到一些感興趣的好處。
讓我們假設你有相同的“大腦分裂”問題,即主副服務器都認為它自己是主服務器。在用 Log Shipping 的情況下,我們無法調和這個分歧。而在用 Oplog 的情況下,我們卻可以做到。這裏的關鍵是,我們可以:
對拒絕操作的服務器之一dump為可恢複狀態。
嚐試應用兩個服務器上的日誌,希望它們不是同時工作在同一個文檔上。
這就是MongoDB采用的複製模式。並且它采取的處理這種衝突的第一種方法。事實上,這幾乎是能夠安全解決的唯一選擇。當然,當兩台服務器上更改相同對象是總是需要手動解決。而且最好是提前預防而不是認為“這樣有時奏效”。
你可以在這裏看到一些MongoDB如何合並交叉寫的討論。事實上,如果繼續使用相同的源數據,你可以在這裏看到MongoDB內部的oplog :
1: // Operations
2:
3: > use test
4: switched to db test
5: > db.foo.insert({x:1})
6: > db.foo.update({x:1}, {$set : {y:1}})
7: > db.foo.update({x:2}, {$set : {y:1}}, true)
8: > db.foo.remove({x:1})
9:
10: // Op log view
11:
12: > use local
13: switched to db local
14: > db.oplog.rs.find()
15: { "ts" : { "t" : 1286821527000, "i" : 1 }, "h" : NumberLong(0), "op" : "n", "ns" : "", "o" : { "msg" : "initiating set" } }
16: { "ts" : { "t" : 1286821977000, "i" : 1 }, "h" : NumberLong("1722870850266333201"), "op" : "i", "ns" : "test.foo", "o" : { "_id" : ObjectId("4cb35859007cc1f4f9f7f85d"), "x" : 1 } }
17: { "ts" : { "t" : 1286821984000, "i" : 1 }, "h" : NumberLong("1633487572904743924"), "op" : "u", "ns" : "test.foo", "o2" : { "_id" : ObjectId("4cb35859007cc1f4f9f7f85d") }, "o" : { "$set" : { "y" : 1 } } }
18: { "ts" : { "t" : 1286821993000, "i" : 1 }, "h" : NumberLong("5491114356580488109"), "op" : "i", "ns" : "test.foo", "o" : { "_id" : ObjectId("4cb3586928ce78a2245fbd57"), "x" : 2, "y" : 1 } }
19: { "ts" : { "t" : 1286821996000, "i" : 1 }, "h" : NumberLong("243223472855067144"), "op" : "d", "ns" : "test.foo", "b" : true, "o" : { "_id" : ObjectId("4cb35859007cc1f4f9f7f85d") } }
你可以通過在oplog實體上的命令查看鏈。例如,上麵命令的第7行被變成18行的一個插入。這樣似乎也要做很多的工作來避免做任何計算工作,更傾向於用一個簡單操作來解決問題。
比如,你有一個看起來像{counter:1}的文檔,你在主節點上做了一個類似{$inc:{counter:1}}的更新,結果是{counter:2},而oplog將儲存{$set:{counter:2}}。次級節點將這樣複製而不是使用$inc。
這是一個非常不錯的特性,因為你可以多次操作這樣的變更,但是返回的結果是一樣的。但是這樣會導致一個惡劣的結果,那就是你不能將多次的變更合並處理,當然,你可以采用更好的一些方式來處理這個問題,但是。。我並不喜歡這種方案。
Multi write partners
在這種模式下,我們存在一個服務器的集群,每一個服務器都很類似。所有的寫操作都被處理並記錄下來。當從源服務器複製到所有的目標服務器的時候,就會問目標服務器:你上次從我這兒操作了多少啦,這裏就是從上次到現在的所有的變更哦。在這一點,我們可以從已經複製到所有的目標服務器的日誌來看看。
最後更新:2017-07-03 12:02:05