閱讀872 返回首頁    go 阿裏雲 go 技術社區[雲棲]


MongoDB Replica Set使用幾點總結

本文會涉及到MongoDB副本集的初始化,讀寫性能,scala driver,簡單運維等內容。


副本集初始化

在各個節點上replica set進程,

nohup numactl --interleave=all ./mongod --dbpath /home/mongodb/data/  --logpath /home/mongodb/mongodb-linux-x86_64-2.4.7/run.log --port 8017 --rest --journal --replSet smartq --oplogSize 500 --profile=1 --slowms=5 --fork &
我的啟動中,開啟了rest接口,journal log,設置了oplog size大小500M(因為之後再修改oplog大小會比較麻煩),還開啟了慢查詢profile,如果有不熟悉這三塊日誌的,可以參考下麵的簡單描述:

  1. Journal日誌。Journal日誌通過預寫式的redo日誌為MongoDB增加了額外的可靠性,開啟該功能時,數據的更新會先寫入Journal日誌,定期提交,然後在真實數據上執行這些變更,如果服務器安全關閉,日誌會被清除在服務器啟動時,如果存在Journal日誌,則會執行提交.啟動Journal功能隻需要在啟動mongod時指定-journal參數即可,這樣,係統的Journal信息都會被放到數據庫目錄(默認是/data/db)的journal文件夾中
  2. oplog日誌。MongoDB的高可複用策略中有一種叫做Replica Sets,Replica Sets複製過程中一個服務器充當主服務器,而一個或多個服務器充當從服務器,主服務器將更新寫入一個本地的collection中,這個collection記錄著發生在主服務器的更新操作,並將這些操作分發到從服務器上。這個日誌是一個capped Collection,且有大小之分,所以最好在啟動mongod服務時配置好大小(單位:MB). mongd -oplogsize=1024
  3. 慢查詢日誌。慢查詢日誌記錄了執行時間超過所設定時間閾值的操作語句,慢查詢日誌對於發現性能有問題的語句很有幫助,建議開啟此功能經常分析該日誌的內容.要配置這個功能值需要在mongod啟動時指定profile參數即可。Eg.將超過5s的操作記錄都記錄下來 mongod --profile=1 --slowms=5 運行一段時間後,可以通過查看db.system.profile 這個collection來獲取慢日誌信息
要注意的是

numactl --interleave=all
這塊設置。NUMA和UMA(SMP)多核CPU架構的不同實現方式,推薦閱讀下Introduction to Parallel Computing的章節內容。如果不設置這個參數,進入mongod後會有相應提示,可能帶來的問題可以參考記一次MongoDB性能問題,附原理解析這篇文章。


起了各個節點後,連接到某一台mongod上,進行副本集初始化工作:

var config = { 
  _id: "smartq", 
  members: [ 
    {
      _id:0, 
      host:"host0-ip:8017"
    }, 
    {
      _id:1, 
      host:"host1-ip:8017"
    }, 
    {
      _id:2, 
      host:"host2-ip:8017"
    } 
  ] 
}
rs.initiate(config)
輸入rs.status()可以查看primary和secondary節點情況,剛初始化的時候節點的狀態會經曆一些變化,之後選舉出primary。更多指令可以參考 rs.help() 。更多細節可以參考文章mongodb副本集架構搭建


讀寫性能

我的副本集的寫性能,在java driver環境下,差不多是1W-2W+ 行每秒,吞吐量大約2M+ 每秒。寫隻能在primary節點上進行。

我的副本集的讀性能,在不帶索引的情況下,DBCursor的掃描速度是4K~7K 行 每秒。輸入一個查詢,執行後,返回一個DBCursor是很快的,但是遊標的順序fetch行數還是比較慢的。我嚐試了DBCursor提供的一些方式(我使用的是mongo-java-driver-2.10.1的包),對比了下以下幾種獲取速度,

rs = coll.find().sort(new BasicDBObject("_id", 1)).toArray();
rs = coll.find().batchSize(100).limit(l).toArray();
rs = coll.find().toArray(); // toArray()開銷
Iterator it = coll.find().iterator(); // little faster than toArray()
while (it.hasNext()) {
    it.next();
}
剛開始一直使用toArray()的方式把DBCursor能指向的數據全部吐到內存裏來,但其實toArray()的開銷稍稍大於返回一個iterator之後逐個掃描一次。而基於_id字段進行排序之後再toArray(),帶來的額外開銷很少,側麵說明_id字段因為有索引,做排序很快很方便,值得好好利用。batchSize這個設置,我嚐試設置了100,1000,感受是對於幾萬到幾十萬的數據吞吐沒有多大影響。

總結是讀性能在速度上還是需要索引支撐,且在能承受最終一致性的前提下,將讀分布到secondary上緩解。


Scala Driver
嚐試了下Scala Driver來進行讀性能的測試,速度和java driver是一致的,而scala driver本身也是對java driver的簡單封裝,且目前支持的api也不全。

Scala Driver項目叫Casbah,是10gen官方的Toolkit。在build.sbt下的配置如下:

name := "hi-scala"

organization := "xx.xxx.xxx"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.9.3"

libraryDependencies ++= Seq(
  "org.mongodb" %% "casbah" % "2.6.3"
)
簡單使用:

import com.mongodb.casbah.Imports._

object CasbahTest extends Logging {
  
  def main(args: Array[String]): Unit = {

    val mongoClient = MongoClient("host-ip", 8017)
	
    val db = mongoClient("db")

    val coll = db("collection")

    val start = System.currentTimeMillis()

    val rs = coll.find()

    // for (doc <- rs) {
    //   doc
    // }

    logInfo("Result: " + rs.size) // no toArray()

    val end = System.currentTimeMillis()

    logInfo("Time: " + (end-start))
    
  }
}
更多內容參看 Casbah Tutorial

Copy Collection

在同個db下拷貝collection似乎沒有快速的方法,那就寫簡單的js,進行insert操作,

 var i = 0;
 while(i < 10000) {
   var cname = 'copy' + i;
   db.copy1.find().forEach( function(x){
     db.getCollection(cname).insert(x);
   });
   i++
 }
然後讓mongod在後台執行,效率也很慢,

nohup  ./mongo localhost:8017/hdfs ../../jscript/copy_collection.js &
執行一段時間後,secondary的status可能會顯示RECOVERING,原因是oplog記錄的內容過多,primary的oplog可能已經重新刷過一次了,導致secondary與primary脫節,無法再持續進行本身的同步數據的操作。解決方法是把secondary kill掉,刪掉data數據,重新起mongod加入副本集。這利用的是副本集同步機製中的初始化同步,即對於新的沒有數據的member,拷貝現有副本集內某個member的整份數據,整個過程流程為先clone數據,然後apply all changes,最後建索引。啟動之後,secondary會出於STARTUP狀態,開始比較快速地進行數據的同步,這裏比較快也就是上百M每秒的樣子。

(全文完)


最後更新:2017-04-03 14:54:23

  上一篇:go Javax.swing中JFrame.getContentPane().add(pane)與JFrame.add(pane)有何區別?
  下一篇:go 編程的視頻