手记

用JFreeChart 来分析Cassandra/Oracle插入海量数据的性能


为了分析在插入海量数据到Cassandra集群或者Oracle时的表现,也就是插入速率,我们用java程序对插入数据的用时进行了采样,最终用JFreeChart把采样结果绘制出来了。

 

为了公平起见,我们做了以下处理:

1.所有的循环变量都放在了循环外面

2.对于Cassandra的replication-factor设置为1,这样插入数据不需要插入额外的备份。

3.对于Oracle我们用预编译语句,这样插入操作的执行计划可以重用。

4.所有的测试都在周末进行,这样不可能有其他人去干扰这些服务器。

5.这些机器上运行的其他进程都被我kill掉了,这样保证CPU,内存的专用性。

6.在用java代码插入Cassandra记录时候,我采用了thrift API, 因为它的效率比Hector API高。

 

 

以下是实验(分两部分,一是采样部分,二是数据分析部分)

Part 1:采样:

Cassandra的采样:

我们这里依然用循环插入50W条记录,不同的是,在循环的开始和循环每10000条记录时,我们把时间戳记录在List中,最终把这个List写入文本文件(cassandra_input_sample_data.txt):

package com.charles.cassandra.demo; 

 

import java.io.File; 

import java.io.FileWriter; 

 

import java.util.ArrayList; 

import java.util.List; 

 

import org.apache.cassandra.thrift.Cassandra; 

import org.apache.cassandra.thrift.Column; 

 

import org.apache.cassandra.thrift.ColumnParent; 

import org.apache.cassandra.thrift.ConsistencyLevel; 

 

import org.apache.cassandra.thrift.TBinaryProtocol; 

 

import org.apache.thrift.protocol.TProtocol; 

import org.apache.thrift.transport.TFramedTransport; 

import org.apache.thrift.transport.TSocket; 

import org.apache.thrift.transport.TTransport; 

 

import com.charles.cassandra.util.CassandraOperationUtil; 

 

public class CassandraClusterStressTest 

    public static void main(String[] args) 

        throws Exception 

    { 

        //包装好的socket 

        TTransport tr = new TFramedTransport(new TSocket("192.168.129.34",9160)); 

        TProtocol proto = new TBinaryProtocol(tr); 

        Cassandra.Client client = new Cassandra.Client(proto); 

        tr.open(); 

         

        if(!tr.isOpen()) 

        { 

            System.out.println("无法连接到服务器!"); 

            return; 

        } 

         

        System.out.println("开始压力测试,我们插入50W条数据到2节点集群中"); 

        System.out.println("..."); 

         

        //标记开始时间 

        long startTime = System.currentTimeMillis(); 

         

        client.set_keyspace("Charles_Stress_Test2");//使用Charles_Stress_Test keyspace 

        ColumnParent parent = new ColumnParent("student");//column family 

         

        /* 

         * 这里我们插入50万条数据到Student内 

         * 每条数据包括id和name 

         */ 

        String key_user_id = "a"; 

        String k; 

        long timestamp; 

        Column idColumn =null; 

        Column nameColumn=null; 

         

        //这个sampleData代表了每插入1W条记录到Cassandra集群的用时毫秒的数据样本 

        List<Integer> sampleData = new ArrayList<Integer>(51); 

        for(int i = 0;i < 500000;i++) 

        { 

            k = key_user_id + i;//row key 

            timestamp = System.currentTimeMillis();//时间戳 

             

            //每行的第一个字段(id字段) 

            idColumn = new Column(CassandraOperationUtil.stringToByteBuffer("id"));//字段名 

            idColumn.setValue(CassandraOperationUtil.stringToByteBuffer(i + ""));//字段值 

            idColumn.setTimestamp(timestamp);//时间戳 

            client.insert( 

                CassandraOperationUtil.stringToByteBuffer(k),  

                parent,  

                idColumn,  

                ConsistencyLevel.ONE); 

             

            //每行的第二个字段(name字段) 

            nameColumn = new Column(CassandraOperationUtil.stringToByteBuffer("name")); 

            nameColumn.setValue(CassandraOperationUtil.stringToByteBuffer("student" + i)); 

            nameColumn.setTimestamp(timestamp); 

            client.insert( 

                CassandraOperationUtil.stringToByteBuffer(k),  

                parent,  

                nameColumn,  

                ConsistencyLevel.ONE); 

             

            //判断是否这是起始记录(用于标记起始时间戳)和第N 万条记录(第N万条记录的时间戳) 

            if( (i==0) || ( (i+1)%10000==0)){ 

                sampleData.add((int)(timestamp)); 

            } 

        } 

       

        //标记结束时间 

        long endTime = System.currentTimeMillis(); 

        //标记一共用时 

        long elapsedTime = endTime-startTime; 

         

        System.out.println("压力测试完毕,用时: "+elapsedTime+" 毫秒"); 

        

        //关闭连接 

        tr.close(); 

         

        //压力测试结束后,我们把所有的样本数据写入文件中等待处理 

        FileWriter fw = new FileWriter(new File("cassandra_insert_sample_data.txt")); 

        for(int j=0;j<sampleData.size();j++){ 

            fw.write(sampleData.get(j)+"\n"); 

        } 

        fw.flush(); 

        fw.close(); 

    } 

     

    

最终50W条记录插入完毕:控制台显示:

而且我们打开文本文件确定这些时间戳的样本都被记录了:

当然了,因为Cassandra的存储是基于内存的,所以我们定义了一个工具类用于转换字符串和字节数组:

/*  

 */ 

package com.charles.cassandra.util; 

 

import java.io.UnsupportedEncodingException; 

import java.nio.ByteBuffer; 

 

/** 

 * 

 * Description: 这个类提供了一些Cassandra操作的工具类 

 * 

 * @author charles.wang 

 * @created May 19, 2012 11:18:27 AM 

 *  

 */ 

public class CassandraOperationUtil { 

     

    /** 

     *因为在Cassandra中,信息都存在内存的,所以都是以ByteBuffer形式存储的,但是ByteBuffer对于人类来说没有String可读性强 

     *所以这个方法可以吧字符串转为ByteBuffer 

     */ 

    public static ByteBuffer stringToByteBuffer(String s) throws UnsupportedEncodingException{ 

         

        return ByteBuffer.wrap(s.getBytes("UTF-8")); 

    } 

     

    /** 

     *因为在Cassandra中,信息都存在内存的,所以都是以ByteBuffer形式存储的,但是ByteBuffer对于人类来说没有String可读性强 

     *所以对称的,这个方法吧ByteBuffer转为人类可读的字符串 

     */ 

    public static String byteBufferToString (ByteBuffer b) throws UnsupportedEncodingException{ 

         

        //先构建一个字节数组 

        byte[] bytes = new byte[b.remaining()]; 

        //吧bytebuffer里面的内容全部存入字节数组 

        b.get(bytes); 

        //然后把这些bytes转为String 

        return new String(bytes,"UTF-8"); 

    } 

 

Oracle的采样:

我们这里依然用循环插入50W条记录,不同的是,在循环的开始和循环每10000条记录时,我们把时间戳记录在List中,最终把这个List写入文本文件(oracle_input_sample_data.txt):

/*  

 */ 

package com.charles.cassandra.demo; 

 

 

import java.io.File; 

import java.io.FileWriter; 

import java.sql.Connection; 

import java.sql.Date; 

import java.sql.DriverManager; 

import java.sql.PreparedStatement; 

import java.sql.ResultSet; 

import java.sql.Statement; 

import java.sql.ResultSetMetaData; 

import java.sql.Timestamp; 

import java.util.ArrayList; 

import java.util.List; 

 

 

/** 

 * 

 * Description:插入50W条记录到关系数据库Oracle中 

 * 

 * @author charles.wang 

 * @created May 19, 2012 5:25:36 PM 

 *  

 */ 

 

public class OracleStressTest { 

 

  

     

     

     

    /** 

     * 既然要测负载,就尽可能减少方法调用的时间开销,所以我用了最原始的写法 

     * @param args 

     */ 

     

    public static void main(String[] args){ 

         

        String url="jdbc:oracle:thin:@192.168.129.14:15210:ora11g"; 

        String username="Charles_Stress_Test1"; 

        String password="Charles_Stress_Test1"; 

         

        String sDBDriver = "oracle.jdbc.driver.OracleDriver"; 

 

        try{ 

             

            System.out.println("开始压力测试,我们以预编译的方式插入50W条数据到Oracle中"); 

            System.out.println("..."); 

            //标记开始时间 

            long startTime=System.currentTimeMillis(); 

                     

                     

            Class.forName(sDBDriver).newInstance(); 

            Connection conn = DriverManager.getConnection(url,username,password); 

             

            //因为这里使用预编译语句,所以不用每次都生成新的执行计划 

             

            String rowkey=null; 

            String id=null; 

            String name=null; 

            Date date=null; 

            String statementString="insert into Student (rowkey,id,name,create_date )values(?,?,?,?)";; 

            

            PreparedStatement pstmt = conn.prepareStatement(statementString); 

             

            //这个sampleData代表了每插入1W条记录到Oracle数据库用时毫秒的数据样本 

            List<Integer> sampleData = new ArrayList<Integer>(51); 

             

            for(int i=0;i<500000;i++){ 

                 

                long timestamp = System.currentTimeMillis(); 

                rowkey="a"+i; 

                id=""+i; 

                name="student"+i; 

                date= new Date(timestamp); 

                  

                pstmt.setString(1,rowkey); 

                pstmt.setString(2, id); 

                pstmt.setString(3,name); 

                pstmt.setDate(4, date); 

                pstmt.execute(); 

                 

                //判断是否这是起始记录(用于标记起始时间戳)和第N 万条记录(第N万条记录的时间戳) 

                if( (i==0) || ( (i+1)%10000==0)){ 

                    sampleData.add((int)(timestamp)); 

                } 

            } 

             

            //关闭相关连接 

            pstmt.close(); 

            conn.close(); 

             

            long endTime=System.currentTimeMillis(); 

            long elapsedTime=endTime-startTime; 

             

            System.out.println("压力测试完毕,用时: "+elapsedTime+" 毫秒"); 

             

            //在压力测试结束之后,我们来把样本数据写入文本文件中 

            FileWriter fw = new FileWriter(new File("oracle_insert_sample_data.txt")); 

            for(int j=0;j<sampleData.size();j++){ 

                fw.write(sampleData.get(j)+"\n"); 

            } 

            fw.flush(); 

            fw.close(); 

             

        }catch(Exception e){ 

            System.out.println("数据库连接失败"); 

            e.printStackTrace(); 

        } 

         

        

        

    } 

     

 

 

最终50W条记录插入完毕:控制台显示:

而且我们打开文本文件确定这些时间戳的样本都被记录了:

 

 

Part 2: 分析采样数据并且绘制比较图:

我们用JFreechart强大的图表制作能力来绘制比较图:

首先我们依然定义一个工具类 ParseDataUtil,它可以完成两件事情,一是从样本文件中读取数据,然后时间戳相减,最终把所有每1W条数据的耗时时间存入List<Integer>对象,二是它可以吧List<Integer>对象传递给JFreechart的数据模型:

/*  

 */ 

package com.charles.parsedata.util; 

 

import java.io.BufferedReader; 

import java.io.File; 

import java.io.FileInputStream; 

import java.io.FileNotFoundException; 

import java.io.FileReader; 

import java.io.IOException; 

import java.io.InputStreamReader; 

import java.util.ArrayList; 

import java.util.List; 

 

import org.jfree.data.category.DefaultCategoryDataset; 

 

/** 

 *  

 * Description: 

 *  

 * @author charles.wang 

 * @created May 21, 2012 8:45:28 AM 

 *  

 */ 

public class ParseDataUtil { 

 

    /** 

     * 这个方法用于添加指定的分析来的数据作为JFreechart显示的数据集 

     *  

     * @param ds 

     *            JFreechart的数据集对象 

     * @param datas 

     *            从压力测试采样并且经过加工后的数据 

     * @param seriesName 

     *            曲线的名称 

     */ 

    public static void addDataToDataset(DefaultCategoryDataset ds, List<Integer> datas, String seriesName) { 

        // 对于数据集合的检查 

        if (datas.size() <= 0) 

            return; 

 

        // type表示横轴的每个坐标点 

        Integer value = 0; 

        String type = null; 

 

        // 用循环依次添加 

        for (int i = 1; i <= datas.size(); i++) { 

            // 获取每个样本数据中的横坐标纵坐标 

            type = i + ""; 

            value = datas.get(i - 1); 

            ds.addValue(value, seriesName, type); 

        } 

 

    } 

 

    /** 

     * 这个方法用于从样本数据中构建最终传入到JFreechart绘制的数据 

     *  

     * @param fileName 

     * @param numOfRecords 

     * @return 

     */ 

    public static List<Integer> buildSampleDataListFromFile(String fileName, int numOfRecords) { 

 

        // 判断参数 

        if (numOfRecords <= 0) 

            return null; 

 

        try { 

 

            // 创建一个rawSampleData 作为采样数据的List 

            List<Integer> rawSampleData = new ArrayList<Integer>(numOfRecords); 

 

            // 打开一个到指定文件的输入流 

            FileInputStream fis = new FileInputStream(fileName); 

            InputStreamReader isr = new InputStreamReader(fis); 

            BufferedReader br = new BufferedReader(isr); 

            if (br == null) { 

                System.out.println("样本文件不存在!"); 

                return null; 

            } 

            // 依次读入 

            for (int i = 0; i < numOfRecords; i++) { 

                String rawRecord = br.readLine(); 

                rawSampleData.add(Integer.parseInt(rawRecord)); 

            } 

 

            // 读完了关闭输入流 

            br.close(); 

            isr.close(); 

            fis.close(); 

 

            // 现在我们把rawSampleData转为真正可以被JFreeChart显示的SampleData 

            // 这里SampleData的每个数据都是用时,所以是当前时间戳-第一条记录的时间戳 

            int sampleDataSize = rawSampleData.size() - 1; 

            List<Integer> sampleData = new ArrayList<Integer>(sampleDataSize); 

            // 设置起始时间戳,以后每一个时间戳都要减去这个起始时间戳 

            Integer baseTimeStamp = rawSampleData.get(0); 

            // System.out.println("baseTimeStamp: "+baseTimeStamp); 

            // System.out.println("sampleDataSize: "+sampleData.size()); 

 

            // System.out.println("hello"); 

            for (int j = 0; j < sampleDataSize; j++) { 

                int time = rawSampleData.get(j + 1) - baseTimeStamp; 

                System.out.println(time); 

                sampleData.add(time); 

            } 

 

            return sampleData; 

 

        } catch (Exception ex) { 

            ex.printStackTrace(); 

            return null; 

        } 

 

    } 

 

然后我们有个最终执行画图的类,这个类吧从原始数据分析后的数据显示在图表上,并且作为对比,吧Oracle和Cassandra集群的数据显示在同一张表上:

/*  

 */ 

package com.charles.parsedata; 

 

import java.util.ArrayList; 

import java.util.List; 

 

import javax.swing.JPanel; 

 

import org.jfree.chart.ChartFactory; 

import org.jfree.chart.ChartPanel; 

import org.jfree.chart.JFreeChart; 

import org.jfree.chart.axis.NumberAxis; 

import org.jfree.chart.plot.CategoryPlot; 

import org.jfree.chart.plot.PlotOrientation; 

import org.jfree.data.category.DefaultCategoryDataset; 

import org.jfree.ui.ApplicationFrame; 

import org.jfree.ui.RefineryUtilities; 

 

import com.charles.parsedata.util.ParseDataUtil; 

 

 

/** 

 * 

 * Description: 用JFreechart来分析插入数据 

 * 

 * @author charles.wang 

 * @created May 21, 2012 8:38:27 AM 

 *  

 */ 

public class InsertDataStressTestDataParser extends ApplicationFrame{ 

 

 

 

 

 

public InsertDataStressTestDataParser(String s) { 

    super(s); 

    setContentPane(createDemoLine()); 

   }  

 

   public static void main(String[] args)  { 

    InsertDataStressTestDataParser fjc = new InsertDataStressTestDataParser("Cassandra&Oracle插入数据对比图"); 

    fjc.pack(); 

    RefineryUtilities.centerFrameOnScreen(fjc); 

    fjc.setVisible(true);  

 

   }  

 

   // 生成显示图表的面板 

   public static JPanel createDemoLine(){ 

    JFreeChart jfreechart = createChart(createDataset()); 

    return new ChartPanel(jfreechart); 

   }  

 

   // 生成图表主对象JFreeChart 

   public static JFreeChart createChart(DefaultCategoryDataset linedataset) { 

    //定义图表对象 

    JFreeChart chart = ChartFactory.createLineChart("Cassandra和Oracle插入数据对比图", // chart title 

      "记录数(单位:万条)", // 横轴标签 

      "用时(毫秒)", // 纵轴标签 

      linedataset, // 传入的数据集 

      PlotOrientation.VERTICAL, // 方向 

      true, // bool变量表示是否要加入图例(legend) 

      true, // 工具集 

      false // 是否添加url 

      ); 

    CategoryPlot plot = chart.getCategoryPlot(); 

    // 范围轴线 

    NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); 

    rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); 

    rangeAxis.setAutoRangeIncludesZero(true); 

    rangeAxis.setUpperMargin(0.20); 

    rangeAxis.setLabelAngle(Math.PI / 2.0);  

 

    return chart; 

   }  

 

   //生成数据 

   public static DefaultCategoryDataset createDataset() { 

    DefaultCategoryDataset ds = new DefaultCategoryDataset(); 

 

     

    List<Integer> data1 = ParseDataUtil.buildSampleDataListFromFile("cassandra_insert_sample_data.txt",51); 

    List<Integer> data2 = ParseDataUtil.buildSampleDataListFromFile("oracle_insert_sample_data.txt",51); 

    ParseDataUtil.addDataToDataset(ds, data1, "Cassandra插入数据所用时间分布图"); 

    ParseDataUtil.addDataToDataset(ds, data2, "Oracle插入数据所用时间分布图"); 

 

 

    return ds; 

   }  

    

    

最终对比图如下:

 

 

结论:

所以我们这里很清楚的看到:

(1) 无论是Cassandra集群还是Oracle,其插入操作用时都是线性的,也就是它的平均插入速率基本是恒速。

(2) 在低配置服务器上,Cassandra集群的插入数据操作耗时要高于Oracle关系数据库。

©著作权归作者所有:来自51CTO博客作者charles_wang888的原创作品,如需转载,请注明出处,否则将追究法律责任

OracleJFreechart压力测试NoSQL


0人推荐
随时随地看视频
慕课网APP