polaris912 发表于 2013-1-12 13:26:45

Concurrent Test of Mongo DB zz

之前做的一些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 X4Raid10)。


测试程序在之前的java测试程序基础上做了修改,增加了多线性的并发测试功能,程序源代码可从下面的链接下载:

程序用法:
Usage:
mysql test:
java -jar mongotest.jar < mysql > < > < rows > <concurrent> < host > < username > < password><database>
mongo test:
java -jar mongotest.jar < mongo > < > < rows > <concurrent> < host > <database>

只测试了mongodb。

后期准备再找机器在linux系统上好好的测一下mongodb和mysql的并发性能(因为我发现虽然mongodb在单用户测试的情况下性能很好,但随着并发用户的增加,性能都是往下掉的,而我跑了一次50个并发的mysql,总的吞吐量反而是往上走的,所以很有必要同时测一次mysql和mongodb的并发性能)。

先看一下测试结果,因为这里不好贴表,只能上图了:

http://dl.iteye.com/upload/attachment/545951/2060f522-2eba-377a-a62c-940ebc2adc7c.jpg

有一点需要说明一下,因为并发数为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的内存做缓存使用了。

或许看线性图表更为直观一些:

http://dl.iteye.com/upload/attachment/545955/499fd9f3-a86a-3e81-8e42-26bcc171053e.jpg

另外也测试了一下大数据量的并发测试

内网测试,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.equals("mysql") && args.length < 7 ) {            usage(2);      }      rows = Long.parseLong(args);      int tnum = Integer.parseInt(args);      if ( args.equals("mysql") ) {            mysqldb[] mysqldbthread = new mysqldb;            for ( int k = 0; k < tnum; k++ ) {                mysqldbthread = new mysqldb(args,rows,args,args,args,args,k);                mysqldbthread.start();            }            for ( int k = 0; k < tnum; k++ ) {//                System.out.println("mongothread["+k+"].isAlive()=" + mongothread.isAlive());                if ( mysqldbthread.isAlive() ) {                  try {                        mysqldbthread.join();                  } catch (InterruptedException ex) {                        System.out.println(ex.getMessage());                        System.out.println(ex.toString());                  }                }            }      } else if ( args.equals("mongo") ) {            mongodb[] mongothread = new mongodb;            for ( int k = 0; k < tnum; k++ ) {                mongothread = new mongodb(args,rows,args,args,k);                mongothread.start();            }            for ( int k = 0; k < tnum; k++ ) {//                System.out.println("mongothread["+k+"].isAlive()=" + mongothread.isAlive());                if ( mongothread.isAlive() ) {                  try {                        mongothread.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 + " " + args + " Result:" + result + "row/sec\n");      System.out.print("Total rows:" + rows * tnum + "\n");      System.out.print("Total " + args + " " + args + " 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 > < > < rows > <concurrent> < host > < username > < password><database> \n");      System.out.print("mongo test:\n");      System.out.print("java -jar mongotest.jar < mongo > < > < 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=InnoDBDEFAULT 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();    }    publicvoid 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();    }    publicvoid 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
页: [1]
查看完整版本: Concurrent Test of Mongo DB zz