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 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的并发性能)。

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

Concurrent Test of Mongo DB zz_第1张图片

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

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

Concurrent Test of Mongo DB zz_第2张图片

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

内网测试,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

你可能感兴趣的:(mongodb)