要想知道一个系统的承受能力,那么必然要用一些工具进行压测,本节使用jmeter来进行压测,看看效果作为参考。

1. JMeter入门

  • 官网:https://jmeter.apache.org/
  • 使用:点击bat文件即可用运行
  • 步骤:添加线程组、添加监听器(聚合报告)、线程组右键->添加sampler-》http请求

在本地对/goods/to_list这个简单的压力测试,其实这个接口里面就一个任务:

1
List<GoodsVo> goodsVoList = goodsService.getGoodsVoList();

那么,我以1000的并发,循环10次,尽快执行完。测试结果发现吞吐量最高大约是350。这个并发量比较小。

2. 自定义变量模拟多用户

模拟多个不同用户同时操作。其实就是建立一个文件,然后引用配置文件中变量即可。下面有示例。

  1. 测试计划->添加配置元件->CSV Data Set Config
  2. 引用变量${}

3. JMeter命令行使用

先在本地用软件生成一个jmx文件,将其上传到Liunx服务器上,这个服务器上现在跑当前程序的war包,如何生成这个war见下面介绍。

linux上安装好jmeter执行:

jmeter.sh -n -t xxx.jmx -l result.jtl

生成结果保存到result.jtl文件中。可以在图形化界面软件中打开这个结果进行查看。

在一台linux上进行测试,接口就上面提到的to_list。5000并发量,循环10次,在上面的测试结果大概是1267的QPS。记录此值,下面进行优化。

3.1 秒杀接口测试

我们的重点是对do_miaosha这个接口进行测试。但是呢,我们不能用一个user来测试,所以在压测之前,我们需要准备好数据:

整体思路是:先往数据库插入5000条数据,然后生成5000个token到一个txt文件中。

3.2 连接数据库的工具类:DBUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
}

3.3 db.properties文件

1
2
3
4
5
6
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

3.4 执行程序,要先启动web程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class UserUtil {

private static void createUser(int count) throws Exception{
List<MiaoshaUser> users = new ArrayList<MiaoshaUser>(count);
//生成用户
for(int i=0;i<count;i++) {
MiaoshaUser user = new 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;i<users.size();i++) {
MiaoshaUser user = users.get(i);
pstmt.setInt(1, 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;i<users.size();i++) {
MiaoshaUser user = users.get(i);
URL url = new 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文件。

我们的目标是生成userIdtoken的文件,所以我们需要对doLogin这个方法进行修改,原来是返回Result<Boolean>,现在返回Result<String>,这个String就是生成的token

如果顺利的话,生成的文件是这样的:

1
2
3
4
13000000000,3e9e716b555047f2af8ccdb3224da4f2
13000000001,53f55f4b1b3247669c5c2588548d8ee8
13000000002,87a313072df74b2d944c3227b14c2d4a
13000000003,77c7e4a834fd4986952a78c18c27d22c

下面,打开JMeter软件,首先是按照上面的步骤CSV Data Set Config,引入tokens.txt这个文件。在Variable Names这一项写上userId,token,这样,就可以获取到这两个参数。

image

然后配置好http请求:

image

Aggregate Report来查看结果。这里用5000的并发来发请求。

我在数据库准备5个秒杀商品。

在测试中,发现数据库的秒杀商品数量竟然变成了负数。。这个时候出现了线程安全,我们的超卖现象。

有的时候也能根据预期执行完,我们会发现5000个用户只有5个人抢到了。数据库里只有五条记录。秒杀的压力测试效果我们已经达到了,下面就是线程安全和提高并发量的工作了。

4. redis压测工具redis-benchmark

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字节的数据包

5. spring Boot打war包

添加spring-boot-starter-tomcatprovided依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>

添加maven-war-plugin插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>

不要忘记上面的:

1
<packaging>jar</packaging>

改为:

1
<packaging>war</packaging>

最后,修改启动函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
@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包了。