用JFreeChart 来分析Cassandra/Oracle插入海量数据的性能 推荐
2012-05-21 11:22
162 查看
为了分析在插入海量数据到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):
最终50W条记录插入完毕:控制台显示:
而且我们打开文本文件确定这些时间戳的样本都被记录了:
当然了,因为Cassandra的存储是基于内存的,所以我们定义了一个工具类用于转换字符串和字节数组:
Oracle的采样:
我们这里依然用循环插入50W条记录,不同的是,在循环的开始和循环每10000条记录时,我们把时间戳记录在List中,最终把这个List写入文本文件(oracle_input_sample_data.txt):
最终50W条记录插入完毕:控制台显示:
而且我们打开文本文件确定这些时间戳的样本都被记录了:
Part 2: 分析采样数据并且绘制比较图:
我们用JFreechart强大的图表制作能力来绘制比较图:
首先我们依然定义一个工具类 ParseDataUtil,它可以完成两件事情,一是从样本文件中读取数据,然后时间戳相减,最终把所有每1W条数据的耗时时间存入List<Integer>对象,二是它可以吧List<Integer>对象传递给JFreechart的数据模型:
然后我们有个最终执行画图的类,这个类吧从原始数据分析后的数据显示在图表上,并且作为对比,吧Oracle和Cassandra集群的数据显示在同一张表上:
最终对比图如下:
结论:
所以我们这里很清楚的看到:
(1) 无论是Cassandra集群还是Oracle,其插入操作用时都是线性的,也就是它的平均插入速率基本是恒速。
(2) 在低配置服务器上,Cassandra集群的插入数据操作耗时要高于Oracle关系数据库。
为了公平起见,我们做了以下处理:
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关系数据库。
相关文章推荐
- mongoDB批量插入数据性能分析、索引效率
- DBA_Oracle海量数据处理分析(方法论)
- 性能分析工具-PerfView 推荐
- .NET批量大数据插入性能分析及比较(5.使用SqlBulkCopy)
- ORACLE-DBA相关知识,性能问题分析(一)
- 推荐5款超实用的.NET性能分析工具
- Oracle - PGA自动管理原理深入分析及性能调整(4)
- 如何配置oracle的性能分析工具statspack
- 通过ORACLE分析函数过滤重复数据 推荐
- ORACLE UPDATE 语句语法与性能分析的一点看法(转载)
- 推荐5款超实用的.NET性能分析工具
- ORACLE空间管理实验6:块管理之ASSM下插入操作--高水位的影响及大并发插入的性能问题
- .NET批量大数据插入性能分析及比较(4.使用DataAdapter批量插入)
- ORACLE-DBA相关知识,性能问题分析(一)
- Oracle性能分析10:重建索引续-常用SQL
- Oracle - PGA自动管理原理深入分析及性能调整(5)
- 通过实验分析索引对MySQL插入时性能的影响
- Oracle 11g 中SQL性能优化新特性之SQL性能分析器(SQLPA)
- 5.读书笔记收获不止Oracle之 插入表性能示例
- ORACLE UPDATE 语句语法与性能分析的一点看法