需求:
接到个测试数据统计相关模块的需求,需要我们模拟“用户激活、注册、登录、创角、付费”的行为,制造若干用户行为数据。
思考:
在不熟悉接口对数据库的插入关系的情况下,直接对数据库表自行添加数据是十分不合理的。
又考虑到当前主要是针对业务,以及数据准确性进行测试,数据量不会特别大,所以应该采取调用接口的方式模拟用户行为。
使用接口进行模拟,这里推荐两种方式:
①、使用Java等编程语言编写代码。(推荐)
②、使用JMeter工具进行脚本编写。
上述两种方式都能实现批量调用接口的目的,但是个人更推荐自己进行编程,这样更利于调试纠错和后续拓展。
举例:
现需要模拟用户激活的场景,如100人在《火影忍者》游戏上激活;200人在《海贼王》游戏上激活......
上图是通过抓包拿到的 “用户设备激活” 接口,只要循环调用就可以达到批量制造数据的目的了。
实践:
自行搭建springboot项目架构,以下为service层的主要业务逻辑:
/**
* 批量初始化设备
* @param initDto
* @return
*/
@Override
public ResultVO addInitDevice(InitDto initDto) {
long startTime = new Date().getTime();
Map map = new HashMap();
int sum = 0;
map.put("sdkId", initDto.getSdkId());
map.put("dcChannelId", 3);
map.put("versionNo", 31);
map.put("subChannelId", 0);
map.put("deviceFactory", "HUAWEI");
map.put("deviceMac", "test:81:65:18:e6:67");
map.put("appId", initDto.getAppId());
map.put("channelId", initDto.getChannelId());
map.put("deviceOs", 1);
map.put("osVersion", "android5.1.1");
map.put("deviceModel", "SEA-AL10");
map.put("deviceDpi", "720×1280");
map.put("dcProductId", "2225");
//循环调用激活接口
for (int i = 0; i < initDto.getNumber(); i++) {
map.put("deviceId", this.getOnlyOneDeviceId());
//callInit是封装了的调用接口的方法
if (this.callInit(map, initDto.getTime()))
sum++;
}
ResultVO resultVO = new ResultVO();
if (sum > 0) {
resultVO.setCode(200);
resultVO.setMsg("success");
resultVO.setData(sum);
} else {
resultVO.setCode(100);
resultVO.setMsg("都初始化失败了");
}
long endTime = new Date().getTime();
System.out.println("此次处理总耗时:"+ (endTime - startTime)/1000);
return resultVO;
}
上述为单线程处理,循环中的内容需要等待前一个完成才能继续,相当于只有一个队伍,排队进行。
当前业务循环1000次,需要花费21秒。

优化:
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
/**
* 使用线程池优化过的初始化设备
* @param initDto 初始化的入参对象
* @return
*/
@Override
public ResultVO addInitDevice2(InitDto initDto) {
long startTime = new Date().getTime();
Map map = new HashMap();
AtomicInteger sum = new AtomicInteger(0);
map.put("sdkId", initDto.getSdkId());
map.put("dcChannelId", 3);
map.put("versionNo", 31);
map.put("subChannelId", 0);
map.put("deviceFactory", "HUAWEI");
map.put("deviceMac", "test:81:65:18:e6:67");
map.put("appId", initDto.getAppId());
map.put("channelId", initDto.getChannelId());
map.put("deviceOs", 1);
map.put("osVersion", "android5.1.1");
map.put("deviceModel", "SEA-AL10");
map.put("deviceDpi", "720×1280");
map.put("dcProductId", "2225");
//倒数,此处用于多线程控制(全部线程执行完再继续执行)
CountDownLatch countDownLatch = new CountDownLatch(initDto.getNumber());
//todo 循环执行业务逻辑
for (int i = 0; i < initDto.getNumber(); i++) {
map.put("deviceId", getOnlyOneDeviceId());
//todo 使用线程池
threadPoolTaskExecutor.execute(()->{
if (callInit(map, initDto.getTime())){
sum.getAndAdd(1);
}
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
ResultVO resultVO = new ResultVO();
if (sum.get()>0) {
resultVO.setCode(200);
resultVO.setMsg("success");
resultVO.setData(sum);
} else {
resultVO.setCode(100);
resultVO.setMsg("都初始化失败了");
}
long endTime = new Date().getTime();
System.out.println("此次处理总耗时:"+ (endTime - startTime)/1000);
return resultVO;
}
使用多线程处理后,从21秒优化到5秒。

这里采用的线程池SpringBoot自带、推荐的ThreadPoolTaskExecutor管理线程池。CountDownLatch同步计数器,用来管理多线程流程,避免线程没跑完就返回结果了。
评论区