侧边栏壁纸
博主头像
一朵云的博客博主等级

拥抱生活,向阳而生。

  • 累计撰写 67 篇文章
  • 累计创建 25 个标签
  • 累计收到 7 条评论

目 录CONTENT

文章目录

批量制造数据 -- Java 多线程处理业务

一朵云
2022-03-11 / 0 评论 / 3 点赞 / 7976 阅读 / 6202 字

需求:

  接到个测试数据统计相关模块的需求,需要我们模拟“用户激活、注册、登录、创角、付费”的行为,制造若干用户行为数据。

思考:

  在不熟悉接口对数据库的插入关系的情况下,直接对数据库表自行添加数据是十分不合理的。

  又考虑到当前主要是针对业务,以及数据准确性进行测试,数据量不会特别大,所以应该采取调用接口的方式模拟用户行为。

  使用接口进行模拟,这里推荐两种方式:

  ①、使用Java等编程语言编写代码。(推荐)

  ②、使用JMeter工具进行脚本编写。

  上述两种方式都能实现批量调用接口的目的,但是个人更推荐自己进行编程,这样更利于调试纠错和后续拓展。

举例:

  现需要模拟用户激活的场景,如100人在《火影忍者》游戏上激活;200人在《海贼王》游戏上激活......

image.png

  上图是通过抓包拿到的 “用户设备激活” 接口,只要循环调用就可以达到批量制造数据的目的了。

实践:

自行搭建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同步计数器,用来管理多线程流程,避免线程没跑完就返回结果了。

3

评论区