搜尋此網誌

2012年6月5日 星期二

[工具介紹] MongoDB Replica Set in CentOS 6.x (下)

logo-mongodb在上一篇文章中,我提到有關 MongoDB Replica Set 的基本概念,在這篇文章中,我們就來實際建置一個可以運作的 Replica Set。今天我採用的環境是 CentOS 6.2,一共準備了三部主機,其 IP 分別是 192.168.1.2 (主機名稱 mnode1)、192.168.1.3 (主機名稱 mnode2) 與 192.168.1.4 (主機名稱 mnode3),其中 192.168.1.2 與 192.168.1.3 分別是標準節點,而 192.168.1.4 則是被動節點。如果你沒有三台主機,也可以在一台機器內執行三個 mongo 資料庫程序來當成是三個節點。值得特別注意的是,雖然官方文件中說明 Replica Set 最小的節點數量是 2 個,但是如果 Replica Set 內只有 2 個節點,一旦原有 Primary 失效後將沒有辦法自動進行 Primary 的切換。原因很簡單,回想一下上一篇的網路失效案例就可以明瞭了。在實際的佈署中,如果無法同時取得三台主機來安裝 MongoDB,可以找一台提供其他服務的機器並指定為仲裁者 (Arbiter) 的節點類型。如此一來不但可以同樣享有自動切換的功能,而且可以減少一台主機的使用。因為仲裁者不會儲存資料,所以不會對原有主機造成過多的額外負擔。

現在,我們就來一步一步完成今天的目標吧。

  1. 在三台主機各自安裝 MongoDB server。 MongoDB 並沒有包含在標準的 CentOS 套件當中,但是可以透過 EPEL 加以取得。除非你有特別的原因,否則請不要自行編譯,直接透過套件方式加以安裝即可。指令為
    yum install –y mongodb-server

  2. 在三台主機設定 mongod 開機後自動啟動。
    指令為
    chkconfig mongod on

  3. 修改第一台主機 (IP 為 192.168.1.2) 的設定檔,設定檔預設位置為 /etc/mongodb.conf。

    bind_ip = 127.0.0.1
    改為
    bind_ip = 127.0.0.1,192.168.1.2
    並加上
    replSet = myrepl/192.168.1.3,192.168.1.4

  4. 修改第二台主機 (IP 為 192.168.1.3) 的設定檔,設定檔預設位置為 /etc/mongodb.conf。

    bind_ip = 127.0.0.1
    改為
    bind_ip = 127.0.0.1,192.168.1.3
    並加上
    replSet = myrepl/192.168.1.2,192.168.1.4

  5. 修改第三台主機 (IP 為 192.168.1.4) 的設定檔,設定檔預設位置為 /etc/mongodb.conf。

    bind_ip = 127.0.0.1
    改為
    bind_ip = 127.0.0.1,192.168.1.4
    並加上
    replSet = myrepl/192.168.1.2,192.168.1.3

  6. 在三台主機分別開啟防火牆。
    MongoDB 預設使用 TCP Port 27017。同樣請記得在三台主機進行相關的設定。

  7. 在三台主機分別啟動 MongoDB 服務。
    指令為
    service mongod start

    此時我們如果查看 MongoDB 的日誌 (預設為 /var/log/mongodb/mongodb.log) 應該會發現類似下面的錯誤訊息:

    Tue Jun  5 17:46:30 [startReplSets] replSet can't get local.system.replset config from self or any seed (EMPTYCONFIG)

    別擔心,這是因為我們還沒有正常的初始化 Replica Set。

  8. 進行 Replica Set 的初始化。
    在第一台主機 (192.168.1.2) 利用指令 mongo 進行 MongoDB 的管理。
    指令為
    > rs.initiate()

    如果此時看到類似下面的訊息,表示 Replica Set 已經初始成功了。

    {
             "info2" : "no configuration explicitly specified -- making one",
             "info" : "Config now saved locally.  Should come online in about a minute.",
             "ok" : 1 
    }

  9. 確認其他主機的狀態。
    此時在其他主機上執行 mongo 指令後,應該會發現提示符號變成
    myrepl:SECONDARY>
    我們可以透過指令 rs.conf() 進一步確認 Replica Set 的設定
    myrepl:SECONDARY>rs.conf() 
    {
             "_id" : "myrepl",
             "version" : 1,
             "members" : [
                     {
                             "_id" : 0,
                             "host" : "192.168.1.2:27017"
                     },
                     {
                             "_id" : 1,
                             "host" : "192.168.1.3"
                     },
                     {
                             "_id" : 2,
                             "host" : "192.168.1.4"
                     }
             ] 
    }

    除了 rs.conf() 之外,我們還可以透過 rs.isMaster() 看到相關的訊息。
    myrepl:SECONDARY> rs.isMaster() 
    {
             "setName" : "myrepl",
             "ismaster" : false,
             "secondary" : true,
             "hosts" : [
                     "192.168.1.3",
                     "192.168.1.4",
                     "192.168.1.2:27017"
             ],
             "primary" : "192.168.1.2:27017",
             "maxBsonObjectSize" : 16777216,
             "ok" : 1 
    }

  10. 將 192.168.1.4 這台主機設定為被動節點。
    回到 Primary 節點 (192.168.1.2),進行下列設定
    myrepl:PRIMARY> var c = rs.conf() 
    myrepl:PRIMARY> c.members[2].priority = 0; 
    0 
    myrepl:PRIMARY> rs.reconfig(c); 
    Tue Jun  5 18:11:54 DBClientCursor::init call() failed 
    Tue Jun  5 18:11:54 query failed : admin.$cmd { replSetReconfig: { _id: "myrepl", version: 3, members: [ { _id: 0, host: "192.168.1.2:27017" }, { _id: 1, host: "192.168.1.3" }, { _id: 2, host: "192.168.1.4", priority: 0.0 } ] } } to: 127.0.0.1 
    shell got exception during reconfig: Error: error doing query: failed 
    in some circumstances, the primary steps down and closes connections on a reconfig 
    null 
    Tue Jun  5 18:11:54 trying reconnect to 127.0.0.1 
    Tue Jun  5 18:11:54 reconnect 127.0.0.1 ok

    再次執行rs.isMaster()可以發現設定值的改變,其中 192.168.1.4 這台主機已經變成被動 (Passive) 節點:
    myrepl:PRIMARY> rs.isMaster() 
    {
             "setName" : "myrepl",
             "ismaster" : true,
             "secondary" : false,
             "hosts" : [
                     "192.168.1.2:27017",
                     "192.168.1.3"
             ],
             "passives" : [
                     "192.168.1.4"
             ],
             "maxBsonObjectSize" : 16777216,
             "ok" : 1 
    }

  11. 測試 Replication 的功能。
    在 Primary 節點 (192.168.1.2) 新增資料
    myrepl:PRIMARY> use test; 
    switched to db test 
    myrepl:PRIMARY> db.test.games.insert({"title":"diablo III"}); 
    myrepl:PRIMARY> db.test.games.find() 
    { "_id" : ObjectId("4fcddcc83e54d133f505ee14"), "title" : "diablo III" }

    到 Secondary 節點查詢資料
    myrepl:SECONDARY> use test 
    switched to db test 
    myrepl:SECONDARY> rs.slaveOk() 
    not master and slaveok=false 
    myrepl:SECONDARY> db.test.games.find() 
    { "_id" : ObjectId("4fcddcc83e54d133f505ee14"), "title" : "diablo III" }

    請注意在預設的情況下,即使連結到 Secondary 節點也無法進行查詢的指令,必須透過 rs.slaveOk() 的指令才能宣告此一連線可用來進行查詢,此一設計的目的是為了避免程式誤連到 Secondary 後而不自知。因為 Secondary 節點與 Primary 節點內的資料存在一段的時間差,因此兩邊的資料可能不完全一致,這個現象對於某些應用來說將導致嚴重的不良後果,所以才需透過這種方式來確認程式的意圖。

  12. 測試自動切換的功能。
    我們故意將 Primary (192.168.1.2) 節點的 MongoDB 服務加以關閉,指令為
    service mongod stop

    此時我們在 192.168.1.3 這台主機執行 mongo 這個指定後,可以發現已經變成了 Primary。
    [root@mnode2 lib]# mongo 
    MongoDB shell version: 1.8.2 
    connecting to: test 
    myrepl:PRIMARY>
恭喜你,你已經設定好一個具備 HA 能力的 MongoDB Replica Set 了。最後,還有下列幾點事項需要提醒各位:

  1. 常用的相關指令,其中 db.printReplicationInfo() 可以用來查看 oplog 的使用狀況。 oplog 記錄 Secondary 節點同步時所必須執行的指令,如果檔案過小可能導致同步失效而必須執行完整同步 (resync)。 
    • rs.conf()
    • rs.reconfig()
    • rs.isMaster()
    • rs.status()
    • db.printReplicationInfo()
    • db.printSlaveReplicationInfo()
  2. Replica Set 在初始化時除了設定初始的節點外,都不可以含有資料 (除了 local 這個特殊的資料庫除外,這個資料庫永遠不會進行同步)。如果初始化時有節點已經包含資料,將會出現下列錯誤訊息:
    {
             "info2" : "no configuration explicitly specified -- making one",
             "errmsg" : "couldn't initiate : member 192.168.1.4 has data already, cannot initiate set.  All members except initiator must be empty.",
             "ok" : 0 
    }
  3. 前面提到 Primary 與 Secondary 之間存在時間差,所以資料並不會完全一致。因此對資料一致性有高需求程式,最好還是都從 Primary 節點進行資料的讀取。此外,還可以透過 write concern 來確保資料以被寫入”大部分”的節點。
  4. 節點還可以設定 hidden 屬性。Hidden 屬性為真的節點不會出現在 rs.isMaster() 的結果內,因此也不會被連結到 Replica Set 的驅動程式看到。這類 Hidden 節點可以專門作為備份、報表或分析之用。
  5. 雖然 MongoDB 至此已經具備自動切換的 HA 功能,但是程式本身如何連結 Replica Set 並處理失效的情況,仍是必須小心地加以設計。更重要的是,記得要做好足夠的測試才能確保不會在最不該發生意外的時候發生了令人遺憾的意外。 
  6. 前面我們透過 rs.reconfig() 這個指令將 192.168.1.4 這個節點重新設定為被動節點。除了可以改變現有節點的設定,rs.reconfig() 也可以用來動態地新增或刪除節點。不過在新增節點時要特別注意的是,新增後的節點會進行完整的同步動作。所以如果 Primary 節點已經存在大量的資料,同步動作可能會造成 Primary 節點過大的負載,因此建議還是選擇一個比較少人使用的時段再來進行比較恰當。
  7. 當發生 Primary 節點切換的動作時,原先的 Primary 節點與新的 Primary 節點之間如果存在任何資料的差異,那麼這些差異就會因此消失不見。舉例來說,如果我們新增一筆資料到原有的 Primary 節點,但是 Primary 節點卻還不及將這筆資料同步到其他節點前就失效了,那麼這筆新增的資料就不會被寫入其他節點。我們可以透過 write concern 來減少這類問題的發生。
  8. MongoDB 支援延遲同步的 Slave 節點,這個功能可以用來避免操作失誤 (例如誤刪資料) 所造成的不良後果。不過不管採用哪種 HA 方式,一個完整的備份計畫仍然是絕對必要的。HA 保持資料的即時性,而備份則可以確保資料的持久性。兩者的用途不盡相同,也各有運用的時機。
  9. 使用 Replica Set 後對整體系統效能的影響,是必須仔細地加以模擬並評估的。並不是所有的情況使用 Replica Set 後都可以獲得大量的效能改進。除了網路架構、硬體、系統架構、甚至是資料的讀寫比例等因素,都會對效能產生莫大的影響。同樣的,HA 也是必須審慎地加以模擬並評估。

沒有留言:

張貼留言

About