Jmeter开源性能测试工具某些功能不如某些商业化工具方便,但免费是其巨大优势,况且某些方便的功能也可以通过我们“自食其力”地实现。本文涉及:数据库中BLOB字段的处理;Jmeter与H2 Database在性能测试中的使用;Jmeter分布式测试。
1、需求背景
最近因项目性能测试需要,学习了Jmeter这个开源工具。但发现其某些地方确实不如LoadRunner方便。比如如下两个问题:
1) 当业务处理代码(JAVA请求代码)需要变更时,新的Jar包没法统一更新(部署)到各个代理端;
2) 全局的序列化参数(即需要唯一取值的参数)无法进行统一同步,因为不同的代理端运行在不同的JVM之上,代码的synchronized仅能在单独的JVM生效。
对比LoadRunner,第一个问题,脚本代码的任何更新只需要在Controller端刷新一下就可以同步到代理机了;第二个问题,LoadRunner使用参数block就可以全局管理那些需要唯一取值的参数。
还好,在JAVA类请求中,使用“外挂”数据库可以解决以上两个问题。这里介绍下H2 Database,一个嵌入式开源数据库,不需安装,整个实现环境就一个jar包,文档及下载地址是:http://www.h2database.com/。
2、解决方法
1) 结构图示
先画个图说明一下解决方法:
其中jar包文件以BLOB的形式存放在H2数据库表extjars(batchno,putedtime,filename,content)中。
2) Blob对象处理
Blob对象处理,即服务端文件入库和代理端读取文件的过程和方法。先创建一个表来保存不同版本的jar文件:
Create table extjars (batchno varchar(26),putedtime varchar(26),filename varchar(260),contentblob);
Blob入库
向表中插入BLOB对象时,新的JDBC6已经可以直接插入blob数据,而不像老版本的jdbc需要先insert一个空的blob然后再获取这个对象并写入数据。
下面是插入Blob字段(这里的blob字段就是存放jar包的)的主要代码:
/**
* 将文件以二进制Blob对象放入表中
* @param batchno
* @param filename
*/
public voidinsertAJarFile(Connection conn,String batchno,String filename){
String curtime =CommonUtil.getCurStringTime();
String sqlInsert ="insert into extjars(batchno,putedtime,filename,content)values(?,?,?,?)";
Connection conn = null;
try {
//创建一个blob对象
Blob blob =conn.createBlob();
//打开输入文件流
InputStream is = newFileInputStream(new File(filename));
//设置流写入开始位置
OutputStream out =blob.setBinaryStream(1);
//设置缓冲大小10Mb
byte[] temp = newbyte[1024000];
int length;
//开始写入
while ((length = is.read(temp)) !=-1) {
out.write(temp, 0, length);
}
is.close();
out.flush();
out.close();
//插入普通字段和blob字段
PreparedStatement pstmt =conn.prepareStatement(sqlInsert);
pstmt.setString(1, batchno);
pstmt.setString(2, curtime);
pstmt.setString(3, filename);
pstmt.setBlob(4, blob);
pstmt.executeUpdate();
pstmt.close();
conn.commit();
//释放blob资源
blob.free();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Blob读取
插入整个文件到表中后,代理端(Slave)使用下面这段代码读取Jar文件到本地磁盘:
/**
* 从表中获取最新版本文件
* @param conn数据库连接句柄
* @param jarName要下载文件的名字
* @param storePath存放路径
* @return
*/
public void getLastJarFile(Connetionconn,String jarName,String storePath){
//查询最大版本号的文件信息
String sqlSelect = "SELECTbatchno, putedtime,filename,content FROM EXTJARS where PUTEDTIME = (selectmax(putedtime) from extjars)";
if(!jarName.equals("")&&null!= jarName){
//查询指定文件名最大版本号的记录信息
sqlSelect = "SELECTbatchno, putedtime,filename,content FROM EXTJARS where batchno = (SELECTMAX(batchno) FROM EXTJARS where filename like '%"+jarName+"%') andfilename like '%"+jarName+"%'";
}
String batchno = "";
String putedtime = "";
String fullname = "";
String filename = "";
try {
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sqlSelect);
Blob blob = null;
while(rs.next())
{
batchno= rs.getString(1);
putedtime= rs.getString(2);
fullname = rs.getString(3);
if(fullname.indexOf("/")>=0){
filename=fullname.substring(fullname.lastIndexOf("/")+1);
}
//获取结果集中的Blob对象
blob = rs.getBlob(4);
}
//将流形式的blob对象放入byte数组中
byte[] temp = new byte[(int)blob.length()];
InputStream in = blob.getBinaryStream();
in.read(temp);
//将缓存的byte写到文件
storePath = storePath + "/"+ filename;
File file = new File(storePath);
FileOutputStream fout = newFileOutputStream(file);
fout.write(temp);
fout.flush();
in.close();
fout.close();
blob.free();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
这里将封装的方法打包后放入<jmeterPath>/lib/ext目录下,并在主控端建立一个Java请求的测试计划,专门用于更新代理端的jar包。此过程省略。
3) 全局序列化参数
这个问题的解决借鉴LoadRunner里“uniquenumber”的block方式,按VU数量进行分区,每个VU(所有代理机上的)在分区内的数据进行递增。
分块数据存储结构为:create table xxx_paramName(HOST_VUID varchar(20), BASE_NUMB, varchar(20),LAST_NUMB varchar(20));
HOST_VUID:表示线程标志(VUID),主键唯一;
BASE_NUMB:表示分块数据初始值;
LAST_NUMB:表示分块数据被使用过的最大值(下次的起点);
这部分代码比较简单,即读取配置文件的初始值(需要参数化的变量,变量的初始值,VU数量),分块入库。
重点说一下H2数据库在这里的特殊作用:
启动TCP连接
启动TCP连接,为客户端(代理机)提供数据获取和更新服务。
□服务端启动代码如下:
/**
* 创建一个H2数据库TCP服务
* @param port 本地端口
*/
public static Server startH2TCPServer(Stringport) {
Server server = null;
try {
server = Server.createTcpServer(newString[] { "-tcpPort", port, "-tcpAllowOthers" })
.start();
} catch (SQLException e) {
e.printStackTrace();
throw newRuntimeException(e);
}
return server;
}
□客户端连接代码如下:
/**
* 通过TCP方式连接到H2数据库
* @param ipport
* @param dbtype
* @param dbname
* @returnConnection
*/
public static Connection createH2TCPConnection(Stringipport,
String dbtype,String dbname) {
String sourceURL = null;
Connection conn = null;
try {
//注意TCP连接的写法
if (dbname.isEmpty()){
sourceURL ="jdbc:h2:tcp://" + ipport + "/mem:" + dbname;
} else {
sourceURL ="jdbc:h2:tcp://" + ipport + "/~/" + dbname;
}
Class.forName("org.h2.Driver");
//连接用户和密码要根据约定修改
conn =DriverManager.getConnection(sourceURL, "user", "password");
CommonUtil.printLog2Console("Databaseconnected successfully!");
} catch (Exception e) {
e.printStackTrace();
return null;
}
return conn;
}
启动WEB连接
H2数据库另一个极好的功能是可以通过浏览器的方式连接并编辑。
□服务端启动代码如下:
/**
* 启动一个H2 WEB Server,其他客户端可以通过IE访问
* @param port
* @return
*/
public static Server startH2WEBServer(Stringport) {
Server server = null;
try {
server = Server.createWebServer(newString[]{"-webPort",port, "-webAllowOthers" }).start();
System.out.println("DatabaseServer(WEB) Startup Success : " + server.getURL());
} catch (SQLException e) {
System.out.println ("Startthe database error : " + e.toString());
e.printStackTrace();
throw newRuntimeException(e);
}
return server;
}
□使用IE连接:
Jmeter中本框架的使用
下面是Jmeter中使用DeBlock的calss的必须实现的代码:
publicclass YourClassName extends AbstractJavaSamplerClient {
//VU序号,根据VU数递增
private static vuid = 0;
//与vuid组成VU(线程)名字
private String VUName = "";
//需要在全部线程上同步的序列化变量
private static int msgid1 = 0;
//当前线程迭代计数
private int loops = 0;
//管理端IP和端口
private String DOBLOCK_SERVER = "";
public ArgumentsgetDefaultParameters() {
Argumentsparams = new Arguments();
params.addArgument("DOBLOCK_SERVER","192.168.219.194:50000");
returnparams;
}
public void setupTest(JavaSamplerContextarg0) {
try {
DOBLOCK_SERVER= arg0.getParameter("DOBLOCK_SERVER");
//连接到服务端
DeBlock de = newDeBlock(DOBLOCK_SERVER);
//设置VU标志(线程唯一ID)
VUName= de.setVUName(++vuid);
//根据VUNAME和参数名msgid1取得开始值, system_tag:与业务系统相关的标志
Stringstart = de.getBlockBase(system_tag, "msgid1", VUName);
msgid1 = Integer.parseInt(start);
} catch (Exception e) {
e.printStackTrace();
}
//其他初始化代码
.........
}
//迭代中使用msgid1
public SampleResultrunTest(JavaSamplerContext arg0) {
//迭代代码
msgid1++;
loops++;
}
//测试结束时调用
publicvoid teardownTest(JavaSamplerContextarg0) {
try {
DeBlockde = new DeBlock(DOBLOCK_SERVER);
de.updateLastNumb(system_tag,"msgid1", VUName,loops);
}catch (Exception e) {
e.printStackTrace();
}
//其他代码
...............
}
}
本篇完。