异步性能不如同步?通过压测讨论应该如何设置线程数

导读:本篇文章讲解 异步性能不如同步?通过压测讨论应该如何设置线程数,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1 代码实例

1.1 A服务

声明A服务提供五个方法:

public class BizParamDTO {
    private String field;
}

public interface AService {

    public String a1(BizParamDTO param) throws Exception;

    public String a2(BizParamDTO param) throws Exception;

    public String a3(BizParamDTO param) throws Exception;

    public String a4(BizParamDTO param) throws Exception;

    public String a5(BizParamDTO param) throws Exception;
}
复制代码

a1-a4休眠100毫秒,a5休眠600毫秒:

@Service
public class AServiceImpl implements AService {

    @Override
    public String a1(BizParamDTO param) throws Exception {
        System.out.println(Thread.currentThread().getName() + ",a1 param=" + param);
        TimeUnit.MILLISECONDS.sleep(100);
        return param.getField();
    }

    @Override
    public String a2(BizParamDTO param) throws Exception {
        System.out.println(Thread.currentThread().getName() + ",a2 param=" + param);
        TimeUnit.MILLISECONDS.sleep(100);
        return param.getField();
    }

    @Override
    public String a3(BizParamDTO param) throws Exception {
        System.out.println(Thread.currentThread().getName() + ",a3 param=" + param);
        TimeUnit.MILLISECONDS.sleep(100);
        return param.getField();
    }

    @Override
    public String a4(BizParamDTO param) throws Exception {
        System.out.println(Thread.currentThread().getName() + ",a4 param=" + param);
        TimeUnit.MILLISECONDS.sleep(100);
        return param.getField();
    }

    @Override
    public String a5(BizParamDTO param) throws Exception {
        System.out.println(Thread.currentThread().getName() + ",a5 param=" + param);
        TimeUnit.MILLISECONDS.sleep(600);
        return param.getField();
    }
}
复制代码

1.2 B服务

B服务同步调用A服务五个方法:

public interface BService {
    public void b(BizParamDTO param) throws Exception;
}

@Service
public class BServiceImpl implements BService {
    @Resource
    private AService aservice;

    @Override
    public void b(BizParamDTO param) throws Exception {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("taskB");
        String r1 = aservice.a1(param);
        String r2 = aservice.a2(param);
        String r3 = aservice.a3(param);
        String r4 = aservice.a4(param);
        String r5 = aservice.a5(param);
        List<String> result = Arrays.asList(r1, r2, r3, r4, r5);
        stopWatch.stop();
        System.out.println("b1-costTime=" + stopWatch.getTotalTimeMillis() + "ms");
    }
}
复制代码

1.3 C服务

C服务通过异步调用A服务:

public interface CService {
    public void c(BizParamDTO param) throws Exception;
}

@Service
public class CServiceImpl implements CService {
    private final static Integer TYPE = TypeEnum.CPU.getCode();

    @Resource
    private AService aservice;

    @Override
    public void c(BizParamDTO param) throws Exception {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("taskC");
        Future<String> f1 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                StopWatch s = new StopWatch();
                s.start();
                String result = aservice.a1(param);
                s.stop();
                System.out.println("a1-costTime=" + s.getTotalTimeMillis() + "ms");
                return result;
            }
        });
        Future<String> f2 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                StopWatch s = new StopWatch();
                s.start();
                String result = aservice.a2(param);
                s.stop();
                System.out.println("a2-costTime=" + s.getTotalTimeMillis() + "ms");
                return result;
            }
        });
        Future<String> f3 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                StopWatch s = new StopWatch();
                s.start();
                String result = aservice.a3(param);
                s.stop();
                System.out.println("a3-costTime=" + s.getTotalTimeMillis() + "ms");
                return result;
            }
        });
        Future<String> f4 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                StopWatch s = new StopWatch();
                s.start();
                String result = aservice.a4(param);
                s.stop();
                System.out.println("a4-costTime=" + s.getTotalTimeMillis() + "ms");
                return result;
            }
        });
        Future<String> f5 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                StopWatch s = new StopWatch();
                s.start();
                String result = aservice.a5(param);
                s.stop();
                System.out.println("a5-costTime=" + s.getTotalTimeMillis() + "ms");
                return result;
            }
        });

        // 等待结果
        StopWatch watch = new StopWatch("waitWatch");
        watch.start("f1.get()");
        String r1 = f1.get();
        watch.stop();

        watch.start("f2.get()");
        String r2 = f2.get();
        watch.stop();

        watch.start("f3.get()");
        String r3 = f3.get();
        watch.stop();

        watch.start("f4.get()");
        String r4 = f4.get();
        watch.stop();

        watch.start("f5.get()");
        String r5 = f5.get();
        watch.stop();

        // 输出结果
        List<String> result = Arrays.asList(r1, r2, r3, r4, r5);
        stopWatch.stop();
        System.out.println("c1-costTime=" + stopWatch.getTotalTimeMillis() + "ms,costTimeDetail=" + watch.prettyPrint());
    }
}
复制代码

1.4 线程池

我们把线程池划分为两种类型:

public enum TypeEnum {

    IO(1, "IO密集"),

    CPU(2, "CPU密集")
}
复制代码

CPU密集型线程数:CPU数量+1

IO密集型线程数:CPU数量除以(1-阻塞系数0.9)

public class MyThreadFactory {

    /** 线程执行器 **/
    private static volatile ThreadPoolExecutor executor;

    /** 队列存放任务数 **/
    private static int QUEUE_MAX_SIZE = 1000;

    /** 线程存活时间 **/
    private static long KEEP_ALIVE_TIME = 1000;

    public static ThreadPoolExecutor get(int type) {
        if (executor == null) {
            synchronized (ThreadFactory.class) {
                if (executor == null) {
                    int cpuNum = Runtime.getRuntime().availableProcessors();
                    int coreSize = cpuNum;
                    if (type == TypeEnum.CPU.getCode()) {
                        coreSize = cpuNum + 1;
                    } else if (type == TypeEnum.IO.getCode()) {
                        coreSize = cpuNum * 10;
                    }
                    int maxSize = coreSize;
                    BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(QUEUE_MAX_SIZE);
                    executor = new ThreadPoolExecutor(coreSize, maxSize, KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, queue);
                }
            }
        }
        return executor;
    }

    /**
     * 本机8核16处理器
     */
    public static void main(String[] args) {
        int cpuNum = Runtime.getRuntime().availableProcessors(); // 16
        System.out.println("cpuNum=" + cpuNum);
    }
}
复制代码

1.5 访问端点

@RestController
@RequestMapping("/test")
public class BizController {

    @Resource
    private BService bservice;
    @Resource
    private CService cservice;

    @PostMapping("/biz1")
    public boolean biz1(@RequestBody BizParamDTO param) throws Exception {
        bservice.b(param);
        return true;
    }

    @PostMapping("/biz2")
    public boolean biz2(@RequestBody BizParamDTO param) throws Exception {
        cservice.c(param);
        return true;
    }
}
复制代码

2 单次执行

2.1 同步执行

postman访问端点biz1,此时选择CPU密集型线程池:

http://localhost:8080/javafront/test/biz1
{
    "field": "a"
}
复制代码

耗时日志如下:

b1-costTime=1036ms
复制代码

耗时计算公式:

100ms(a1) + 100ms(a2) + 100ms(a3) + 100ms(a4) + 500ms(a5) = 1000ms
复制代码

2.2 异步执行

postman访问端点biz2:

http://localhost:8080/javafront/test/biz2
{
    "field": "a"
}
复制代码

耗时日志如下:

a2-costTime=104ms
a4-costTime=104ms
a1-costTime=104ms
a3-costTime=104ms
a5-costTime=602ms
c1-costTime=604ms,costTimeDetail=StopWatch 'waitWatch': running time = 604224000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
105678000  017%  f1.get()
000001800  000%  f2.get()
000048100  000%  f3.get()
000000400  000%  f4.get()
498495700  083%  f5.get()
复制代码

本次耗时日志稍显复杂,可以把日志分为执行部分和等待部分:

  • 执行部分
    • a1-a4分别执行耗时104ms
    • a5执行耗时602ms
a2-costTime=104ms
a4-costTime=104ms
a1-costTime=104ms
a3-costTime=104ms
a5-costTime=602ms
复制代码
  • 等待部分
    • c1总共耗时604ms
    • f1等待时间105ms
    • f2-f4等待时间为纳秒级
    • f5等待时间498ms
c1-costTime=604ms,costTimeDetail=StopWatch 'waitWatch': running time = 604224000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
105678000  017%  f1.get()
000001800  000%  f2.get()
000048100  000%  f3.get()
000000400  000%  f4.get()
498495700  083%  f5.get()
复制代码

2.3 本章总结

  • 同步执行耗时为每个节点耗时累加
  • 异步执行耗时为节点中耗时最长节点
  • 单次执行耗时异步优于同步

3 压力测试

3.1 压测思路

  • 使用工具JMeter
  • 线程组配置
    • 线程数10、20、30递增
    • Ramp-Up时间0秒
    • 持续时间60s
    • 循环次数永久
  • 关注聚合报告指标
    • 95Line
    • 吞吐量
    • 异常比例
  • 执行方式
    • 同步执行
    • 异步执行,线程池使用CPU密集型
    • 异步执行,线程池使用IO密集型

3.2 压测分析

3.2.1 压测结果

异步性能不如同步?通过压测讨论应该如何设置线程数

  • 同步执行
    • 在不同线程数下耗时总体稳定,均为1000ms左右
  • 异步(IO密集型)
    • 在不同线程数下耗时总体稳定,均为600ms左右
  • 异步(CPU密集型)
    • 随着线程数增多,耗时越来越大,性能表现不如同步

3.2.2 耗时分析

现在分析异步在50线程时耗时日志,分析耗时主要发生执行部分,还是发生在等待部分。

(1) 异步(IO密集型)

a5-costTime=601ms
a1-costTime=108ms
a4-costTime=108ms
a2-costTime=108ms
a3-costTime=108ms
c1-costTime=602ms,costTimeDetail=StopWatch 'waitWatch': running time = 602183001 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
105515000  018%  f1.get()
000000200  000%  f2.get()
000777201  000%  f3.get()
000101800  000%  f4.get()
495788800  082%  f5.get()
复制代码
  • 执行部分
    • a1-a4分别执行耗时约为108ms
    • a5执行耗时601ms
  • 等待部分
    • f1等待时间约为150ms
    • f5等待时间约为495ms
  • 分析小结
    • 执行和等待均符合预期

(2) 异步(CPU密集型)

a1-costTime=110ms
a2-costTime=109ms
a3-costTime=110ms
a5-costTime=613ms
a4-costTime=110ms
c1-costTime=3080ms,costTimeDetail=StopWatch 'waitWatch': running time = 3080277201 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
2528728000  082%  f1.get()
016059001   001%  f2.get()
031992801   001%  f3.get()
000046199   000%  f4.get()
503451200   016%  f5.get()
复制代码
  • 执行部分

    • a1-a4分别执行耗时约为110ms
    • a5执行耗时613ms
  • 等待部分

    • f1等待时间约为2.5s
    • f5等待时间约为500ms
    • 其它任务等待时间也有所增加
  • 分析小结

    • 耗时主要在等待部分
    • 执行部分耗时符合预期

3.3 压测总结

  • 如果线程池选择不合适,异步性能不如同步
  • 如果任务耗时长,应该增加配置线程数

4 文章总结

本文第一编写了同步和异步代码,并在代码中输出了耗时日志。第二分析单次执行同步和异步的表现,异步优于同步。第三结合不同线程池配置进行压测,如果线程池选择不合适,异步执行性能不如同步,所以要配置合适线程数。

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94749.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!