您的位置:首页 > 数据库 > Oracle

用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):

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关系数据库。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息