本文会涉及到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,如果有不熟悉这三块日志的,可以参考下面的简单描述:
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每秒的样子。