要想知道一个系统的承受能力,那么必然要用一些工具进行压测,本节使用jmeter来进行压测,看看效果作为参考。
1. JMeter入门
在本地对/goods/to_list
这个简单的压力测试,其实这个接口里面就一个任务:
1
| List<GoodsVo> goodsVoList = goodsService.getGoodsVoList();
|
那么,我以1000的并发,循环10次,尽快执行完。测试结果发现吞吐量最高大约是350。这个并发量比较小。
2. 自定义变量模拟多用户
模拟多个不同用户同时操作。其实就是建立一个文件,然后引用配置文件中变量即可。下面有示例。
- 测试计划->添加配置元件->
CSV Data Set Config
- 引用变量${}
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: 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"); 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
文件。
我们的目标是生成userId
和token
的文件,所以我们需要对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
,这样,就可以获取到这两个参数。
然后配置好http
请求:
用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-tomcat
的provided
依赖
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
包了。