• 一种Furture模式处理请求中循环独立的任务的方法


    业务中经常碰到查询一个list后又需要对list进行后续处理(在sql层面不方便处理),需要循环遍历list

    如果list中各item的业务独立,可用future模式来大大提高性能



    1.关于future模式的概念

    参考:彻底理解Java的Future模式  https://www.cnblogs.com/cz123/p/7693064.html

    先上一个场景:假如你突然想做饭,但是没有厨具,也没有食材。网上购买厨具比较方便,食材去超市买更放心。

    实现分析:在快递员送厨具的期间,我们肯定不会闲着,可以去超市买食材。所以,在主线程里面另起一个子线程去网购厨具。

    但是,子线程执行的结果是要返回厨具的,而run方法是没有返回值的。所以,这才是难点,需要好好考虑一下。




    2.关于实现

    (1) callable+future+线程池:

    1. Future<Integer> future =es.submit(calTask);  
    callable无法直接start,需要借助线程池

    Future<T> = 线程池.submit(Callable<T>);

    Future<T>.get();


    (2)   callable +futuretask+线程池

    1. FutureTask<Integer> futureTask=new FutureTask<>(calTask);  
    2.         //执行任务  
    3.         es.submit(futureTask);  
    FutureTask<T> = new FutureTask<T>(Callable<T>);

    线程池.submit(FutureTask<T>)

    FutureTask<T>.get();


    (3)   callable +futuretask+Thread

    同时FureTask也实现了runnable,可以直接允许不用线程池

    FutureTask<Class> ft = new FutureTask<Class>((Callable<Class>) 
    Thread thread = new Thread(ft);
    thread.start;

    FutureTask<T> = new FutureTask<T>(Callable<T>);

    Thread = new Thread(FutureTask<T>);

    Thread.start();

    FutureTask<T>.get();


    参考:Java多线程编程:Callable、Future和FutureTask浅析(多线程编程之四)     博客比较深入

    http://blog.csdn.net/javazejian/article/details/50896505  



    3. 应用

    业务模型是这样的:

    List<T> = sql;
    
    for(T : list<T>) {
         sql(T);
         T.doSomething();
    }

    以future模式重构

    List<T> = sql;
    
    List<FutureTask<T>> taskList = new ArrayList<FutureTask<T>>(list.size());
    
    for(T t: list<T>) {
    
        FutureTask<T>  f = new FutureTask<T>(new TaskCallable<T>(t));      
    
        taskList.add(f);
    
        Thread thread = new Thread(f);
    
        thread.start();
    
    }
    
    for(FutureTask<T> ft : taskList) {
    
        ft.get();
    
    }

    将T分为多个线程并行处理,同时等待所有计算结果,整合后返回

    这种模式在业务中达到以下效果:

    原(ms)现(ms)
    455179
    422152
    422120
    450102
    51792
    48088
      


    另一个请求:

    原 A(ms)现 B(ms)
    1217216
    584184
    505171
    503116
    55696
    线上60ms22ms
      

    可以看到,微观上响应速度提高了5倍

    以腾讯压力测试:

     AB
    20并发(4*4)2分钟 第一次tps:127.18   97.15mstps:212.98    57.57ms
    第二次tps:123.59  100.57mstps:208.77   57.50ms
    100并发  唯一一次tps:241.39  233.02mstps:256.48   209.21ms
    不处理   唯一一次     tps:793.77  71.93ms  
       
       
    分析:

    20并发下,性能差不多差一倍

    100并发下,差距不大了,推测为mysql顶不住了,证明这种方案在高压下不可取,原本一次查询变成了n+1次,应尽量避免


    4.封装

    虽然原则上予以避免,力求在单次sql层面解决,但迫于需求频繁修改,由于这种情况比较常见,故封装一下:

    @Service
    public class FurtureService<T> {
    
        /**
         *
         * @param o   外部类对象,用于取得某对象的内部类
         * @param list    待循环多线程处理的数据
         * @param c    内部Callable类型
         * @param args    内部类参数 数组
         */
        public void run(Object o, List<T> list, Class c, Object [] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, ExecutionException, InterruptedException {
    
            List<FutureTask<Class>> taskList = new ArrayList<FutureTask<Class>>(list.size());
        //    ExecutorService exec = Executors.newFixedThreadPool(list.size());
    
            Constructor [] constructor = null;
    
            constructor = c.getConstructors();
    
            for(int i=0; i<list.size(); ++i) {
                T t = list.get(i);
                FutureTask<Class> ft = new FutureTask<Class>((Callable<Class>) constructor[0].newInstance(o, list.get(i), args));
                taskList.add(ft);
            //    exec.submit(ft);
                Thread thread = new Thread(ft);
                thread.start();
            }
    
            for (FutureTask<Class> ft : taskList) {
                ft.get();
            }
        //    exec.shutdown();
    
        }
    
    }
    

    调用:

        @Autowired
        private FurtureService<T> furtureService;

    furtureService.run(this, list, ComputeTask.class, new Object[]{para});

    其实还是有优点的:


    1.提高了cpu使用率

    2.一定程度弥补了不能在数据库层面一并取出数据的性能


    缺点:

    1.数据库压力倍增,1次sql变成了n+1次sql查询

     


    355

    308

    311

    275

    271


    205

    178

    119

    105

    84

    91

    96



    455

    422

    422

    450

    517

    480


    250

    179

    152

    120

    102

    92

    88


  • 相关阅读:
    SQLdiag-配置文件-ProfilerCollector
    SQLdiag-配置文件-PerfmonCollector
    SQLdiag-初识
    Trace-跟踪高消耗的语句需添加哪些事件
    RML Utilities for SQL Server
    【译】第十五篇 Integration Services:SSIS参数
    修改数据文件和日志文件路径
    Trace-导出已有的服务器端跟踪
    iphoneX的适配问题
    添加阿里巴巴图标,让你页面小图标都是CSS3写成
  • 原文地址:https://www.cnblogs.com/silyvin/p/9106705.html
Copyright © 2020-2023  润新知