官网:https://jmeter.apache.org/
使用:点击bat文件即可用运行
步骤:添加线程组、添加监听器(聚合报告)、线程组右键->添加sampler-》http请求
在本地对/goods/to_list这个简单的压力测试,其实这个接口里面就一个任务:
List goodsVoList = goodsService.getGoodsVoList();
那么,我以1000的并发,循环10次,尽快执行完。测试结果发现吞吐量最高大约是350。这个并发量比较小。
模拟多个不同用户同时操作。其实就是建立一个文件,然后引用配置文件中变量即可。下面有示例。
先在本地用软件生成一个jmx文件,将其上传到Liunx服务器上,这个服务器上现在跑当前程序的war包,如何生成这个war见下面介绍。
在linux上安装好jmeter.执行:
jmeter.sh -n -t xxx.jmx -l result.jtl
生成结果保存到result.jtl文件中。可以在图形化界面软件中打开这个结果进行查看。
在一台linux上进行测试,接口就上面提到的to_list。5000并发量,循环10次,在上面的测试结果大概是1267的QPS。记录此值,下面进行优化。
我们的重点是对do_miaosha这个接口进行测试。但是呢,我们不能用一个user来测试,所以在压测之前,我们需要准备好数据:
整体思路是:先往数据库插入5000条数据,然后生成5000个token到一个txt文件中。
public class DBUtil {
private static Properties props;
static {
try {
InputStream in = DBUtil.class.getClassLoader().getResourceAsStream("db.properties");
props = new Properties();
props.load(in);
in.close();
}catch(Exception e) {
e.printStackTrace();
}
}
public static Connection getConn() throws Exception{
String url = props.getProperty("spring.datasource.url");
String username = props.getProperty("spring.datasource.username");
String password = props.getProperty("spring.datasource.password");
String driver = props.getProperty("spring.datasource.driver-class-name");
Class.forName(driver);
return DriverManager.getConnection(url,username, password);
}
}
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/miaosha?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.filters=stat
public class UserUtil {
private static void createUser(int count) throws Exception{
List users = new ArrayList(count);
//生成用户
for(int i=0;inew MiaoshaUser();
user.setId(13000000000L+i);
user.setLoginCount(1);
user.setNickname("user"+i);
user.setRegisterDate(new Date());
user.setSalt("1a2b3c");
user.setPassword(MD5Util.inputPassToDbPass("123456", user.getSalt()));
users.add(user);
}
System.out.println("create user");
// //插入数据库
Connection conn = DBUtil.getConn();
String sql = "insert into miaosha_user(login_count, nickname, register_date, salt, password, id)values(?,?,?,?,?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
for(int i=0;i1, user.getLoginCount());
pstmt.setString(2, user.getNickname());
pstmt.setTimestamp(3, new Timestamp(user.getRegisterDate().getTime()));
pstmt.setString(4, user.getSalt());
pstmt.setString(5, user.getPassword());
pstmt.setLong(6, user.getId());
pstmt.addBatch();
}
pstmt.executeBatch();
pstmt.close();
conn.close();
System.out.println("insert to db");
//登录,生成token
String urlString = "http://localhost:8080/login/do_login";
File file = new File("D:/tokens.txt");
if(file.exists()) {
file.delete();
}
RandomAccessFile raf = new RandomAccessFile(file, "rw");
file.createNewFile();
raf.seek(0);
for(int i=0;inew URL(urlString);
HttpURLConnection co = (HttpURLConnection)url.openConnection();
co.setRequestMethod("POST");
co.setDoOutput(true);
OutputStream out = co.getOutputStream();
String params = "mobile="+user.getId()+"&password="+MD5Util.inputPassToFormPass("123456");
out.write(params.getBytes());
out.flush();
InputStream inputStream = co.getInputStream();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte buff[] = new byte[1024];
int len = 0;
while((len = inputStream.read(buff)) >= 0) {
bout.write(buff, 0 ,len);
}
inputStream.close();
bout.close();
String response = new String(bout.toByteArray());
JSONObject jo = JSON.parseObject(response);
String token = jo.getString("data");
System.out.println("create token : " + user.getId());
String row = user.getId()+","+token;
raf.seek(raf.length());
raf.write(row.getBytes());
raf.write("\r\n".getBytes());
System.out.println("write to file : " + user.getId());
}
raf.close();
System.out.println("over");
}
public static void main(String[] args)throws Exception {
createUser(5000);
}
}
最后查看数据库是否生成了5000条用户信息,以及是否在D盘下生成了相应的token文件。
我们的目标是生成userId和token的文件,所以我们需要对doLogin这个方法进行修改,原来是返回Result,现在返回Result,这个String就是生成的token。
如果顺利的话,生成的文件是这样的:
13000000000,3e9e716b555047f2af8ccdb3224da4f2
13000000001,53f55f4b1b3247669c5c2588548d8ee8
13000000002,87a313072df74b2d944c3227b14c2d4a
13000000003,77c7e4a834fd4986952a78c18c27d22c
下面,打开JMeter软件,首先是按照上面的步骤CSV Data Set Config,引入tokens.txt这个文件。在Variable Names这一项写上userId,token,这样,就可以获取到这两个参数。
然后配置好http请求:
用Aggregate Report来查看结果。这里用5000的并发来发请求。
我在数据库准备5个秒杀商品。
在测试中,发现数据库的秒杀商品数量竟然变成了负数。。这个时候出现了线程安全,我们的超卖现象。
有的时候也能根据预期执行完,我们会发现5000个用户只有5个人抢到了。数据库里只有五条记录。秒杀的压力测试效果我们已经达到了,下面就是线程安全和提高并发量的工作了。
redis-benchmark -h 127.0.0.1 p 6379 -c 100 -n 100000
100个并发连接,100000个请求。
redis-benchmark -h 127.0.0.1 p 6379 -q -d 100
存取大小为100字节的数据包
添加spring-boot-starter-tomcat的provided依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<scope>providedscope>
dependency>
添加maven-war-plugin插件
<build>
<finalName>${project.artifactId}finalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-war-pluginartifactId>
<configuration>
<failOnMissingWebXml>falsefailOnMissingWebXml>
configuration>
plugin>
plugins>
build>
不要忘记上面的:
<packaging>jarpackaging>
改为:
<packaging>warpackaging>
最后,修改启动函数:
@EnableTransactionManagement
@SpringBootApplication
public class MiaoshaApplication extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(MiaoshaApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MiaoshaApplication.class);
}
}
执行mvn clean package命令,执行成功,就可以看到war包了。