之前做的一些mongodb的测试都是在exsi的两台虚拟间做的,由于虚拟机的问题,性能很不稳定。这两天正好有两台服务器空下来了,就用来跑了一下mongodb的并发测试。
服务器软硬件配置:
服务器:Dell PowerEdge R710
CPU: Intel Xeon E5530 2.4G X 2
硬盘:SAS 300G X 4 建立 Raid10
内存:16G
windows 2003 sp2 64位
mongodb 1.40 x64 for windows
mongodb在这台上跑,测试程序在另一台服务器上跑,两台服务器配置基本一样,除了硬盘(另一台是SAS 147G X4 Raid10)。
测试程序在之前的java测试程序基础上做了修改,增加了多线性的并发测试功能,程序源代码可从下面的链接下载:
程序用法:
Usage:
mysql test:
java -jar mongotest.jar < mysql > < [select | update | insert] > < rows > <concurrent> < host > < username > < password> <database>
mongo test:
java -jar mongotest.jar < mongo > < [select | update | insert] > < rows > <concurrent> < host > <database>
只测试了mongodb。
后期准备再找机器在linux系统上好好的测一下mongodb和mysql的并发性能(因为我发现虽然mongodb在单用户测试的情况下性能很好,但随着并发用户的增加,性能都是往下掉的,而我跑了一次50个并发的mysql,总的吞吐量反而是往上走的,所以很有必要同时测一次mysql和mongodb的并发性能)。
先看一下测试结果,因为这里不好贴表,只能上图了:
有一点需要说明一下,因为并发数为1的时候,select很慢,跑1000万花的时间过长,所以我这边只测试了10万条记录,我看看了数据,差别并不大。
测试的总记录数都是1000万,可以看到,随着并发数的增加,mongdb的insert吞吐量掉得很快。而update稍为平稳一些,总得也在往下走的,从查询来看,随着并发的增加,吞吐量也跟着增加,符合我们的预期。
insert的时候,mongodb会创建多个数据库文件,他会先创建一个64M的文件,不够用了再创建一个128M,然后是256M,512M,1G,2G,然后就是一直2G的创建下去。
测试程序写入的记录是每条1k多一点(4 X 256字符,再加一个int,一个默认的_id),所以1000万记录算出来差不多是10G(除了默认的索引_id,没有另外加其它索引)。而实际上,mongodb总共创建了147G的数据库文件。这样的存储容量对于想用固定硬盘的用户来说是个考验。
insert并发吞往下掉的原因,我怀疑是因为在多并发时mongodb同时操作IO有关,其实通过mongodb服务器上的监控程序我们看到,在mongodb创建1G的数据库文件之前,每秒insert有20000左右,后来就开始低了。我们知道,当写一个1G文件花的时间,总是要比同时写10个100M的文件来得要快。但是这块我想mongodb还有优化的空间,官网上也承认之前的版本并发性能并不好,所以在1.4版本上做了改进。
200并发时update反而比100并发性能要好,或许和单并发测试的数据比较少有关,在200并发时,单并发只测试了5万数据。
另外在高并发时,mongodb的稳定性和健状性也是非常值得关注的。我们在测试200并发时,跑了一段时间后,从mongodb的监控程序发现突然没有了insert的数据,然而java的测试程序也并没有报错,就一直停在那里不动。重启并做一些优化后才测试完200并发。
资源占用方面:cpu 20%上下,任务管理器内显示PF内存占用1G,但mongod进程显示占用了12G的内存做缓存使用了。
或许看线性图表更为直观一些:
另外也测试了一下大数据量的并发测试
内网测试,100线程,每线程50万记录:
D:\dist>java -Xms256M -Xmx1024M -jar mongotest.jar mongo insert 500000 100 192.168.1.12 dbtest
Total thread:100
Total run time:10404 sec
Per-thread rows:500000
Per-thread mongo insert Result:48row/sec
Total rows:50000000
Total mongo insert Result:4805row/sec
D:\dist>java -Xms256M -Xmx1024M -jar mongotest.jar mongo update 500000 100 192.168.1.12 dbtest
Total thread:100
Total run time:13103 sec
Per-thread rows:500000
Per-thread mongo update Result:38row/sec
Total rows:50000000
Total mongo update Result:3815row/sec
D:\dist>java -Xms256M -Xmx1024M -jar mongotest.jar mongo select 500000 100 192.168.1.12 dbtest
Total thread:100
Total run time:1869 sec
Per-thread rows:500000
Per-thread mongo select Result:267row/sec
Total rows:50000000
Total mongo select Result:26752row/sec
用这个数据对比表中100并发的数据,我们发现,虽然性能也在往下掉,但相比因并发增加而下降的性能来说并不夸张,在预期范围之内。由此可见,mongodb对于大数据量来说性能还是不错的。
Test Code :
Main.Java
package mongotest;
/**
*
* @author FarmerLuo
* @version 0.3
*
* Add:
* 1) mysql并发测试
* 2)mongodb并发测试
*
* Last Date: 2010.04.15
*
*/
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
long rows = 0;
long start = System.currentTimeMillis();
if ( args.length < 6 ) {
usage(1);
}
if ( args[0].equals("mysql") && args.length < 7 ) {
usage(2);
}
rows = Long.parseLong(args[2]);
int tnum = Integer.parseInt(args[3]);
if ( args[0].equals("mysql") ) {
mysqldb[] mysqldbthread = new mysqldb[tnum];
for ( int k = 0; k < tnum; k++ ) {
mysqldbthread[k] = new mysqldb(args[1],rows,args[4],args[5],args[6],args[7],k);
mysqldbthread[k].start();
}
for ( int k = 0; k < tnum; k++ ) {
// System.out.println("mongothread["+k+"].isAlive()=" + mongothread[k].isAlive());
if ( mysqldbthread[k].isAlive() ) {
try {
mysqldbthread[k].join();
} catch (InterruptedException ex) {
System.out.println(ex.getMessage());
System.out.println(ex.toString());
}
}
}
} else if ( args[0].equals("mongo") ) {
mongodb[] mongothread = new mongodb[tnum];
for ( int k = 0; k < tnum; k++ ) {
mongothread[k] = new mongodb(args[1],rows,args[4],args[5],k);
mongothread[k].start();
}
for ( int k = 0; k < tnum; k++ ) {
// System.out.println("mongothread["+k+"].isAlive()=" + mongothread[k].isAlive());
if ( mongothread[k].isAlive() ) {
try {
mongothread[k].join();
} catch (InterruptedException ex) {
System.out.println(ex.getMessage());
System.out.println(ex.toString());
}
}
}
} else {
usage(3);
}
long stop = System.currentTimeMillis();
long endtime = (stop - start)/1000;
if ( endtime == 0 ) endtime = 1;
long result = rows/endtime;
long tresult = rows*tnum/endtime;
System.out.print("Total thread:" + tnum + "\n");
System.out.print("Total run time:" + endtime + " sec\n");
System.out.print("Per-thread rows:" + rows + "\n");
System.out.print("Per-thread " + args[0] + " " + args[1] + " Result:" + result + "row/sec\n");
System.out.print("Total rows:" + rows * tnum + "\n");
System.out.print("Total " + args[0] + " " + args[1] + " Result:" + tresult + "row/sec\n");
}
public static void usage(int errorno){
System.out.print("Usage:\n");
System.out.print("mysql test:\n");
System.out.print("java -jar mongotest.jar < mysql > < [select | update | insert] > < rows > <concurrent> < host > < username > < password> <database> \n");
System.out.print("mongo test:\n");
System.out.print("java -jar mongotest.jar < mongo > < [select | update | insert] > < rows > <concurrent> < host > <database> \n");
System.exit( errorno );
}
}
MongoDB.Java
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package mongotest;
import com.mongodb.*;
import java.net.UnknownHostException;
import java.util.List;
/**
*
* @author FarmerLuo
*
* Mongodb 测试类
*
* 参数:
* @param public int threadnum:线程号
* @param public int len:每线程测试的记录数
* @param public String operation:测试模式 select insert update
* @param public String dbname:mongodb数据库名
* @param public String host:mongodb主机
* @param public int port:mongodb端口
*
*/
public class mongodb extends Thread {
public Mongo mongo_conn;
public String dbname;
public String operation;
public long len;
public int threadnum;
public mongodb(String operation,long len,int threadnum){
this.mongo_conn = null;
this.dbname = null;
this.operation = operation;
this.len = len;
this.threadnum = threadnum;
}
public mongodb(String operation,long len,String host,String dbname,int threadnum){
this.mongo_conn = null;
this.dbname = dbname;
this.operation = operation;
this.len = len;
this.threadnum = threadnum;
this.mongodb_connect(host,27017);
}
public mongodb(String operation,long len,String host,String dbname,int port,int threadnum){
this.mongo_conn = null;
this.dbname = dbname;
this.operation = operation;
this.len = len;
this.threadnum = threadnum;
this.mongodb_connect(host,port);
}
public Mongo mongodb_connect(String host,int port){
try {
this.mongo_conn = new Mongo(host, port);
} catch (UnknownHostException ex) {
System.out.println("UnknownHostException:" + ex.getMessage());
} catch (MongoException ex) {
System.out.println("Mongo Exception:" + ex.getMessage());
System.out.println("Mongo error code:" + ex.getCode());
}
return this.mongo_conn;
}
@Override
public void run(){
if ( this.operation.equals("insert") ) {
this.mongodb_insert();
} else if ( this.operation.equals("update") ) {
this.mongodb_update();
} else {
this.mongodb_select();
}
}
public void mongodb_insert(){
DB db = this.mongo_conn.getDB( this.dbname );
DBCollection dbcoll = db.getCollection("test" + String.valueOf(this.threadnum) );
String str = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456";
for (long j = 0; j < this.len; j++) {
DBObject dblist = new BasicDBObject();
dblist.put("_id", j);
dblist.put("count", j);
dblist.put("test1", str);
dblist.put("test2", str);
dblist.put("test3", str);
dblist.put("test4", str);
try {
dbcoll.insert(dblist);
} catch (MongoException ex) {
System.out.println("Mongo Exception:" + ex.getMessage());
System.out.println("Mongo error code:" + ex.getCode());
}
}
}
public void mongodb_update(){
DB db = this.mongo_conn.getDB( this.dbname );
DBCollection dbcoll = db.getCollection("test" + String.valueOf(this.threadnum) );
String str = "UPDATE7890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456";
for (long j = 0; j < this.len; j++) {
DBObject dblist = new BasicDBObject();
DBObject qlist = new BasicDBObject();
qlist.put("_id", j);
dblist.put("count", j);
dblist.put("test1", str);
dblist.put("test2", str);
dblist.put("test3", str);
dblist.put("test4", str);
try {
dbcoll.update(qlist,dblist);
} catch (MongoException ex) {
System.out.println("Mongo Exception:" + ex.getMessage());
System.out.println("Mongo error code:" + ex.getCode());
}
}
}
public void mongodb_select(){
DB db = this.mongo_conn.getDB( this.dbname );
DBCollection dbcoll = db.getCollection("test" + String.valueOf(this.threadnum) );
for (long j = 0; j < this.len; j++) {
BasicDBObject query = new BasicDBObject();
query.put("_id", j);
try {
List objre = dbcoll.find(query).toArray();
//打印查询结果
// for ( Object x : objre ) {
// System.out.println(x);
// }
} catch (MongoException ex) {
System.out.println("Mongo Exception:" + ex.getMessage());
System.out.println("Mongo error code:" + ex.getCode());
}
}
}
}
MySQL.Java
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package mongotest;
import java.sql.*;
/**
*
* @author FarmerLuo
*
* Mysql 测试类
*
* 参数:
* @param public int threadnum:线程号
* @param public int len:每线程测试的记录数
* @param public String operation:测试模式 select insert update
* @param public String dbname:mysql数据库名
* @param public String host:mysql主机
* @param public String username:mysql用户名
* @param public String password:mysql密码
*
*/
public class mysqldb extends Thread {
public Connection mysql_conn;
public String operation;
public long len;
public int threadnum;
public mysqldb(String operation,long len,String host,String username,String passwd,String dbname,int threadnum){
this.mysql_conn = null;
this.operation = operation;
this.len = len;
this.threadnum = threadnum;
this.mysqldb_connect(host, username, passwd, dbname);
}
public Connection mysqldb_connect(String host,String username,String passwd,String dbname){
try {
this.mysql_conn = DriverManager.getConnection("jdbc:mysql://" + host + "/" + dbname + "?user=" + username + "&password=" + passwd + "&characterEncoding=UTF8");
} catch (SQLException ex) {
ex.printStackTrace();
}
return this.mysql_conn;
}
@Override
public void run(){
if ( this.operation.equals("insert") ) {
try {
this.mysqldb_create();
this.mysqldb_insert();
} catch (SQLException ex) {
ex.printStackTrace();
}
} else if ( this.operation.equals("update") ) {
try {
this.mysqldb_update();
} catch (SQLException ex) {
ex.printStackTrace();
}
} else {
try {
this.mysqldb_select();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
//创建测试表,如果表已存在,将删除重建
public void mysqldb_create() throws SQLException {
Statement stmt = null;
stmt = this.mysql_conn.createStatement();
String sql = "DROP TABLE IF EXISTS `test" + this.threadnum + "`;";
stmt.executeUpdate(sql);
sql = "CREATE TABLE test" + this.threadnum + " ( id int(11) NOT NULL auto_increment, count int(11) NOT NULL default 0, test1 varchar(256) NOT NULL, test2 varchar(256) NOT NULL, test3 varchar(256) NOT NULL, test4 varchar(256) NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ";
stmt.executeUpdate(sql);
stmt.close();
//this.mysql_conn.close();
}
public void mysqldb_insert() throws SQLException {
Statement stmt = null;
stmt = this.mysql_conn.createStatement();
String str = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456";
for( int j = 0; j < this.len; j++ ){
String sql = "insert into test" + this.threadnum + " (count, test1, test2, test3, test4) values (" + j + ",'" + str + "','" + str + "','" + str + "','" + str + "')";
stmt.executeUpdate(sql);
}
stmt.close();
this.mysql_conn.close();
}
public void mysqldb_update() throws SQLException {
Statement stmt = null;
stmt = this.mysql_conn.createStatement();
String str = "UPDATE7890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456";
//System.out.print(sql);
for( int j = 0; j < this.len; j++ ){
String sql = "update test" + this.threadnum + " set test1 = '" + str + "',test2 = '" + str + "' , test3 = '" + str + "', test4 = '" + str + "' where id = "+ j;
stmt.executeUpdate(sql);
}
stmt.close();
this.mysql_conn.close();
}
public void mysqldb_select() throws SQLException {
Statement stmt = null;
stmt = this.mysql_conn.createStatement();
for( int j = 1; j <= len; j++ ){
String sql = "select * from test" + this.threadnum + " where id = " + j;
ResultSet sqlRst = stmt.executeQuery(sql);
sqlRst.next();
int id = sqlRst.getInt("id");
int count = sqlRst.getInt("count");
String test1 = sqlRst.getString("test1");
String test2 = sqlRst.getString("test2");
String test3 = sqlRst.getString("test3");
String test4 = sqlRst.getString("test4");
//System.out.println(id + " " + count + " " + test1 + " " + test2 + " " + test3 + " " + test4 + "\n");
sqlRst.close();
}
stmt.close();
this.mysql_conn.close();
}
}
【附录】
几种数据库的存储实现的比较:
* 内存文件映像(Memory-File Mapping) Redis, MongoDB
* 文件 + Cache Tokyo Tyrant
* 内存: Redis, Tokyo Tyrant
Key/Value索引形式:
* B+ Tree : MongoDB, Tokyo Tyrant
* Hash Table: Redis, Tokyo Tyrant
* Fixed Length: Tokyo Tyrant
从上面的比较可以看出,Redis和MongoDB是基于系统内存映像文件,数据能命中在内存的时候读写操作性能应该是非常强的,当然,反过来,如果数据十分分散不能在内存命中,那么内存页的切换开销将是非常可怕的,MongoDB和Redis数据文件不同的是将数据存放在多个文件中,每当上一个存满的时候就会创建新的数据空间文件。鉴于MongoDB 是主要比较对象,而其采用B+Tree进行存储,故TT也使用B+Tree引擎进行比较。
那么该测试什么自然就可以得知:尽管使用内存映像文件读写操作会很快(快到什么程度),但是当写满内存以后呢?
文件大小限制:
32bit: MongoDB <= 2G
TT no limits if u ./configure –enable-off
64bit: MongoDB和TT均无限制。
usagefull links
1. http://www.taobaodba.com/html/560_mongodb_random_insert.html
2. http://www.taobaodba.com/html/551_mongodb_flush_data.html