其实用JMeter就可以对mysql做性能测试了

我用Super Smack做过几次MySQL性能测试。虽然可以用,但这种用c写的程序存在安装困难的问题:在阿里云的centos上可以装的上,在aws上未必就装的上;配置起来也很难受,那些个smack文件,太古怪。

后来我发现,其实用JMeter的jdbc request就可以对mysql性能测试。我用的模式是:

引用
1. 在开发机器上(mac/ubuntu/windows)以gui方式配置好Test Plan, 并试运行。

2. 将test plan文件上传到无干扰无界面的linux测试机上,以non-gui方式在测试机上执行测试,再收集结果。

JMeter支持server模式:用本地的jmeter控制测试机上的jmeter, 直接在本机上启动、停止、查看结果,用起来非常顺手。但它有个问题:在测试过程中,测试机上的jmeter要不停地从本地机器获取测试数据,并返回采样数据到本地机器。这些数据流动会增加测试机上JMeter的负担,使qps偏低。

所以我还是选择了在测试机上使用non-gui模式这种方案。下面给个完整的例子,例示jmeter jdbc request的使用。

例示:用JMeter对MySQL进行压力测试

测试目标
使用普通varchar作为主键,比起使用自增数字类型作为主键,到底会慢多少?这个性能损失是不是可以忽略不计?

测试数据准备

建两张差不多的表,一个使用数字user_id作主键,一个直接使用user_name

drop table if exists user_id_pk;
drop table if exists user_name_pk;


create table user_id_pk (
  user_id bigint unsigned not null auto_increment ,
  user_name varchar(50) not null 
  description varchar(200) default null
  created_when datetime not null
  primary key(user_id),
  unique key idx_user_name (user_name) 
) ;
create table user_name_pk (
  user_name varchar(50) not null ,
  description varchar(200) default null,
  created_when datetime not null,
  primary key(user_name)
) ;

生成两个数据文件,一个用于测试插入性能,一个用于在插入完后测试查询性能。 两个文件都是一百万行。

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.math.RandomUtils;

/**
 * 请设置-Xmx3072m
 */
public class GenCsvDataForUserTable {

	public static void main(String[] args) throws IOException {
		File rootDir = new File("/home/ec2-user/kentbench/usertable");
		File forInsertFile = new File(rootDir, "for-insert.csv");
		File forQueryFile = new File(rootDir, "for-query.csv");

		rootDir.mkdirs();
		forInsertFile.delete();
		forQueryFile.delete();

		int TOTAL_RECORDS = 1000000;
		int saveTheshhold = TOTAL_RECORDS / 10;

		List<String> forInsertLines = new ArrayList<String>();
		List<String> forQueryLines = new ArrayList<String>();

		for (int i = 1; i <= TOTAL_RECORDS; i++) {
			String userId = String.valueOf(i);
			String userName = UUID.randomUUID().toString();
			String description = RandomStringUtils
					.randomAlphanumeric(RandomUtils.nextInt(200) + 1);
			String line = userId + "," + userName + "," + description;
			forInsertLines.add(line);

			// 同时也用于查询
			forQueryLines.add(line);

			// 累积到一定量就存一下
			if (i % saveTheshhold == 0) {
				System.out.println("done with No." + i);

				FileUtils.writeLines(forInsertFile, forInsertLines, true);
				// 用于查询的数据先弄散,再保存
				Collections.shuffle(forQueryLines);
				FileUtils.writeLines(forQueryFile, forQueryLines, true);
				forInsertLines = new ArrayList<String>();
				forQueryLines = new ArrayList<String>();
			}

		}

		System.out.println("done with for-insert.csv. please check "
				+ forInsertFile);
		System.out.println("done with for-query.csv. please check "
				+ forQueryFile);
	}
}

Test Plan配置
一个plan用来往两个表里插入数据,另一个用来从两个表里查询出数据。

以“插入”为例:

线程池配置

数据库JDBC配置

数据文件配置
这里会用到上面生成的数据文件。另外还为每列指定一个变量名,这些变量名在下面中会用到

JDBC Request配置 – 插入到以user_id为主键的表
“parameter values”对应的就是数据文件配置中定义的变量名

JDBC Request配置 – 插入到以user_name为主键的表

测试的执行
点一下JMeter中的“播放”按钮即可执行测试,看下基本的效果 (本地使用了gui模式, 为了查看执行效果,应该添加一个Listener如Summary Report.  此处不表,请自行google)

我们可以将这个test plan存储为一个*.jmx文件,再把它上传到一个无界面无干扰的测试环境中执行。但在执行这一步之前,应该禁掉一个jdbc request, 只保留另一个。否则JMeter会同时执行两个jdbc request, 这会干扰测试结果.
这里解释了为什么不应该同时执行。

  (这个测完后再互换一下,然后再执行一次测试。)

在测试机上执行:

$jmeter -n -t my.jmx 

执行完后查看控制台输出或者jmeter.log, 可以看到类似于这样的数据:

引用
summary = 1000000 in   349s = 2863.4/s Avg:    34 Min:     0 Max:  2339 Err:     0 (0.00%)

其中s = 2863.4/s就是吞吐量(tps), Avg:34是平均RT (ms)

补充说明
用于查询的Test Plan的配置办法与上述的用于插入的Plan的配法差不多,不过你一定要加上一个Assertion配置,以确保某条select语句符合我们的预期,确实查到了数据。


这个意思是每次查询返回一条记录。”_#”代表返回的记录数. firstColumn代表返回记录的第1个字段,这个变量名要在jdbc request中配置。

(对于这种记录数的Assertion, 是否通过第1个字段来判断根本无所谓,第2个字段,第N个字段都可以)

测试结果
场景: 逐渐插入数据,直到累积到100万条

rt qps
用user_id当主键的表 34 2863.4
用user_name当主键的表 41 2346.8
下表比上表 慢20% 低18%

场景: 在100万条数据的表中查询100万次

rt qps
用user_id当主键的表 16 5803.9
用user_name当主键的表 18 5270.7
下表比上表 慢12.5 低9%

(机器:aws ec2 c3.large, cpu 2核, 内存3.75g, ssd硬盘,操作系统Amazon Linux AMI release, mysql 5.5.40 innodb)

结论:使用varchar作为主键,比使用id作为主键性能要差10%-20%,而且插入的性能差别比查询的性能差别更大。插入场景差别更大应该是因为varchar随机插入时会导致page split所致。

这个经历比起用Super Smack还是愉快的多的!

Leave a Comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.