• 后端接口并行调用方案


    1. 一个串行调用的例子

    如果让你设计一个APP首页查询的接口,它需要查用户信息、需要查banner信息、需要查标签信息等等。一般情况,小伙伴会实现如下:

    1. public AppHeadInfoResponse queryAppHeadInfo(AppInfoReq req) {
    2.     //查用户信息
    3.     UserInfoParam userInfoParam = buildUserParam(req);
    4.     UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam);
    5.     //查banner信息
    6.     BannerParam bannerParam = buildBannerParam(req);
    7.     BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam);
    8.     //查标签信息
    9.     LabelParam labelParam = buildLabelParam(req);
    10.     LabelDTO labelDTO = labelService.queryLabelInfo(labelParam);
    11.     //组装结果
    12.     return buildResponse(userInfoDTO,bannerDTO,labelDTO);
    13. }

    这段代码会有什么问题嘛?其实这是一段挺正常的代码,但是这个方法实现中,查询用户、banner、标签信息,是串行的。如果查询用户信息耗时200ms,查询banner信息100ms,查询标签信息200ms的话,耗时就是500ms啦。

    其实为了优化性能,我们可以修改为并行调用的方式,耗时可以降为200ms,如下图所示:

    2. CompletionService实现并行调用

    对于上面的例子,如何实现并行调用呢?

    有小伙伴说,可以使用Future+Callable实现多个任务的并行调用。但是线程池执行批量任务时,返回值用Future的get()获取是阻塞的,如果前一个任务执行比较耗时的话,get()方法会阻塞,形成排队等待的情况。

    CompletionService是对定义ExecutorService进行了包装,可以一边生成任务,一边获取任务的返回值。让这两件事分开执行,任务之间不会互相阻塞,可以获取最先完成的任务结果。

    CompletionService的实现原理比较简单,底层通过FutureTask+阻塞队列,实现了任务先完成的话,可优先获取到。也就是说任务执行结果按照完成的先后顺序来排序,先完成可以优先获取到。内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,你调用CompletionService的poll或take方法即可获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果。

    接下来,我们来看下,如何用CompletionService,实现并行查询APP首页信息哈。思考步骤如下:

    1. 我们先把查询用户信息的任务,放到线程池,如下:

    1. ExecutorService executor = Executors.newFixedThreadPool(10);
    2. //查询用户信息
    3. CompletionService<UserInfoDTO> userDTOCompletionService = new ExecutorCompletionService<UserInfoDTO>(executor);
    4. Callable<UserInfoDTO> userInfoDTOCallableTask = () -> {
    5.       UserInfoParam userInfoParam = buildUserParam(req);
    6.       return userService.queryUserInfo(userInfoParam);
    7.   };
    8. userDTOCompletionService.submit(userInfoDTOCallableTask);
    1. 如果想把查询banner信息的任务,也放到这个线程池的话,发现不好放了,因为返回类型不一样,一个是UserInfoDTO,另外一个是BannerDTO。那这时候,我们把泛型声明为Object即可,因为所有对象都是继承于Object的。如下:

    1. ExecutorService executor = Executors.newFixedThreadPool(10);
    2. //查询用户信息
    3. CompletionService<Object> baseDTOCompletionService = new ExecutorCompletionService<Object>(executor);
    4. Callable<Object> userInfoDTOCallableTask = () -> {
    5.     UserInfoParam userInfoParam = buildUserParam(req);
    6.     return userService.queryUserInfo(userInfoParam);
    7. };
    8. //banner信息任务
    9. Callable<Object> bannerDTOCallableTask = () -> {
    10.     BannerParam bannerParam = buildBannerParam(req);
    11.     return bannerService.queryBannerInfo(bannerParam);
    12. };
    13. //提交用户信息任务
    14. baseDTOCompletionService.submit(userInfoDTOCallableTask);
    15. //提交banner信息任务
    16. baseDTOCompletionService.submit(bannerDTOCallableTask);
    1. 这里会有个问题,就是获取返回值的时候,我们不知道哪个Object是用户信息的DTO,哪个是BannerDTO怎么办呢?这时候,我们可以在参数里面做个扩展嘛,即参数声明为一个基础对象BaseRspDTO,再搞个泛型放Object数据的,然后基础对象BaseRspDTO有个区分是UserDTO还是BannerDTO的唯一标记属性key。代码如下:

    1. public class BaseRspDTO<T extends Object> {
    2.     //区分是DTO返回的唯一标记,比如是UserInfoDTO还是BannerDTO
    3.     private String key;
    4.     //返回的data
    5.     private T data;
    6.     public String getKey() {
    7.         return key;
    8.     }
    9.     public void setKey(String key) {
    10.         this.key = key;
    11.     }
    12.     public T getData() {
    13.         return data;
    14.     }
    15.     public void setData(T data) {
    16.         this.data = data;
    17.     }
    18. }
    19. //并行查询App首页信息
    20. public AppHeadInfoResponse parallelQueryAppHeadPageInfo(AppInfoReq req) {
    21.     long beginTime = System.currentTimeMillis();
    22.     System.out.println("开始并行查询app首页信息,开始时间:" + beginTime);
    23.     ExecutorService executor = Executors.newFixedThreadPool(10);
    24.     CompletionService<BaseRspDTO<Object>> baseDTOCompletionService = new ExecutorCompletionService<BaseRspDTO<Object>>(executor);
    25.     //查询用户信息任务
    26.     Callable<BaseRspDTO<Object>> userInfoDTOCallableTask = () -> {
    27.         UserInfoParam userInfoParam = buildUserParam(req);
    28.         UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam);
    29.         BaseRspDTO<Object> userBaseRspDTO = new BaseRspDTO<Object>();
    30.         userBaseRspDTO.setKey("userInfoDTO");
    31.         userBaseRspDTO.setData(userInfoDTO);
    32.         return userBaseRspDTO;
    33.     };
    34.     //banner信息查询任务
    35.     Callable<BaseRspDTO<Object>> bannerDTOCallableTask = () -> {
    36.         BannerParam bannerParam = buildBannerParam(req);
    37.         BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam);
    38.         BaseRspDTO<Object> bannerBaseRspDTO = new BaseRspDTO<Object>();
    39.         bannerBaseRspDTO.setKey("bannerDTO");
    40.         bannerBaseRspDTO.setData(bannerDTO);
    41.         return bannerBaseRspDTO;
    42.     };
    43.     //label信息查询任务
    44.     Callable<BaseRspDTO<Object>> labelDTODTOCallableTask = () -> {
    45.         LabelParam labelParam = buildLabelParam(req);
    46.         LabelDTO labelDTO = labelService.queryLabelInfo(labelParam);
    47.         BaseRspDTO<Object> labelBaseRspDTO = new BaseRspDTO<Object>();
    48.         labelBaseRspDTO.setKey("labelDTO");
    49.         labelBaseRspDTO.setData(labelDTO);
    50.         return labelBaseRspDTO;
    51.     };
    52.     //提交用户信息任务
    53.     baseDTOCompletionService.submit(userInfoDTOCallableTask);
    54.     //提交banner信息任务
    55.     baseDTOCompletionService.submit(bannerDTOCallableTask);
    56.     //提交label信息任务
    57.     baseDTOCompletionService.submit(labelDTODTOCallableTask);
    58.     UserInfoDTO userInfoDTO = null;
    59.     BannerDTO bannerDTO = null;
    60.     LabelDTO labelDTO = null;
    61.     try {
    62.         //因为提交了3个任务,所以获取结果次数是3
    63.         for (int i = 0; i < 3; i++) {
    64.             Future<BaseRspDTO<Object>> baseRspDTOFuture = baseDTOCompletionService.poll(1TimeUnit.SECONDS);
    65.             BaseRspDTO baseRspDTO = baseRspDTOFuture.get();
    66.             if ("userInfoDTO".equals(baseRspDTO.getKey())) {
    67.                 userInfoDTO = (UserInfoDTO) baseRspDTO.getData();
    68.             } else if ("bannerDTO".equals(baseRspDTO.getKey())) {
    69.                 bannerDTO = (BannerDTO) baseRspDTO.getData();
    70.             } else if ("labelDTO".equals(baseRspDTO.getKey())) {
    71.                 labelDTO = (LabelDTO) baseRspDTO.getData();
    72.             }
    73.         }
    74.     } catch (InterruptedException e) {
    75.         e.printStackTrace();
    76.     } catch (ExecutionException e) {
    77.         e.printStackTrace();
    78.     }
    79.     System.out.println("结束并行查询app首页信息,总耗时:" + (System.currentTimeMillis() - beginTime));
    80.     return buildResponse(userInfoDTO, bannerDTO, labelDTO);
    81. }

    到这里为止,一个基于CompletionService实现并行调用的例子已经实现啦。是不是很开心,哈哈。

    3. 抽取通用的并行调用方法

    我们回过来观察下第2小节,查询app首页信息的demo:CompletionService实现了并行调用。不过大家有没有什么其他优化想法呢?比如,假设别的业务场景,也想通过并行调用优化,那是不是也得搞一套类似第2小节的代码。所以,我们是不是可以抽取一个通用的并行方法,让别的场景也可以用,对吧?这就是后端思维啦

    基于第2小节的代码,我们如何抽取通用的并行调用方法呢。

    首先,这个通用的并行调用方法,不能跟业务相关的属性挂钩,所以方法的入参应该有哪些呢?

    方法的入参,可以有Callable。因为并行,肯定是多个Callable任务的。所以,入参应该是一个Callable的数组。再然后,基于上面的APP首页查询的例子,Callable里面得带BaseRspDTO泛型,对吧?因此入参就是List<Callable<BaseRspDTO<Object>>> list

    那并行调用的出参呢?你有多个Callable的任务,是不是得有多个对应的返回,因此,你的出参可以是List<BaseRspDTO<Object>>。我们抽取的通用并行调用模板,就可以写成酱紫:

    1.     public List<BaseRspDTO<Object>> executeTask(List<Callable<BaseRspDTO<Object>>> taskList) {
    2.         
    3.         List<BaseRspDTO<Object>> resultList = new ArrayList<>();
    4.         //校验参数
    5.         if (taskList == null || taskList.size() == 0) {
    6.             return resultList;
    7.         }
    8.         
    9.         ExecutorService executor = Executors.newFixedThreadPool(10);
    10.         CompletionService<BaseRspDTO<Object>> baseDTOCompletionService = new ExecutorCompletionService<BaseRspDTO<Object>>(executor);
    11.         //提交任务
    12.         for (Callable<BaseRspDTO<Object>> task : taskList) {
    13.             baseDTOCompletionService.submit(task);
    14.         }
    15.         try {
    16.             //遍历获取结果
    17.             for (int i = 0; i < taskList.size(); i++) {
    18.                 Future<BaseRspDTO<Object>> baseRspDTOFuture = baseDTOCompletionService.poll(2TimeUnit.SECONDS);
    19.                 resultList.add(baseRspDTOFuture.get());
    20.             }
    21.         } catch (InterruptedException e) {
    22.             e.printStackTrace();
    23.         } catch (ExecutionException e) {
    24.             e.printStackTrace();
    25.         }
    26.         return resultList;
    27.     }

    既然我们是抽取通用的并行调用方法,那以上的方法是否还有哪些地方需要改进的呢?

    • 第一个可以优化的地方,就是executor线程池,比如有些业务场景想用A线程池,有些业务想用B线程池,那么,这个方法,就不通用啦,对吧。我们可以把线程池以参数的形式提供出来,给调用方自己控制。

    • 第二个可以优化的地方,就是CompletionServicepoll方法获取时,超时时间是写死的。因为不同业务场景,超时时间要求可能不一样。所以,超时时间也是可以以参数形式放出来,给调用方自己控制。

    我们再次优化一下这个通用的并行调用模板,代码如下:

    1. public List<BaseRspDTO<Object>> executeTask(List<Callable<BaseRspDTO<Object>>> taskList, long timeOut, ExecutorService executor) {
    2.         
    3.     List<BaseRspDTO<Object>> resultList = new ArrayList<>();
    4.     //校验参数
    5.     if (taskList == null || taskList.size() == 0) {
    6.         return resultList;
    7.     }
    8.     if (executor == null) {
    9.         return resultList;
    10.     }
    11.     if (timeOut <= 0) {
    12.         return resultList; 
    13.     }
    14.         
    15.     //提交任务
    16.     CompletionService<BaseRspDTO<Object>> baseDTOCompletionService = new ExecutorCompletionService<BaseRspDTO<Object>>(executor);
    17.     for (Callable<BaseRspDTO<Object>> task : taskList) {
    18.         baseDTOCompletionService.submit(task);
    19.     }
    20.     try {
    21.         //遍历获取结果
    22.         for (int i = 0; i < taskList.size(); i++) {
    23.           Future<BaseRspDTO<Object>> baseRspDTOFuture = baseDTOCompletionService.poll(timeOut, TimeUnit.SECONDS);
    24.           resultList.add(baseRspDTOFuture.get());
    25.         }
    26.       } catch (InterruptedException e) {
    27.         e.printStackTrace();
    28.     } catch (ExecutionException e) {
    29.         e.printStackTrace();
    30.     }
    31.     return resultList;
    32. }

    以后别的场景也需要用到并行调用的话,直接调用你的这个方法即可,是不是有点小小的成就感啦,哈哈。

    4. 代码思考以及设计模式应用

    我们把抽取的那个公用的并行调用方法,应用到App首页信息查询的例子,代码如下:

    1. public AppHeadInfoResponse parallelQueryAppHeadPageInfo1(AppInfoReq req) {
    2.         long beginTime = System.currentTimeMillis();
    3.         System.out.println("开始并行查询app首页信息,开始时间:" + beginTime);
    4.         //用户信息查询任务
    5.         Callable<BaseRspDTO<Object>> userInfoDTOCallableTask = () -> {
    6.             UserInfoParam userInfoParam = buildUserParam(req);
    7.             UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam);
    8.             BaseRspDTO<Object> userBaseRspDTO = new BaseRspDTO<Object>();
    9.             userBaseRspDTO.setKey("userInfoDTO");
    10.             userBaseRspDTO.setData(userInfoDTO);
    11.             return userBaseRspDTO;
    12.         };
    13.         //banner信息查询任务
    14.         Callable<BaseRspDTO<Object>> bannerDTOCallableTask = () -> {
    15.             BannerParam bannerParam = buildBannerParam(req);
    16.             BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam);
    17.             BaseRspDTO<Object> bannerBaseRspDTO = new BaseRspDTO<Object>();
    18.             bannerBaseRspDTO.setKey("bannerDTO");
    19.             bannerBaseRspDTO.setData(bannerDTO);
    20.             return bannerBaseRspDTO;
    21.         };
    22.         //label信息查询任务
    23.         Callable<BaseRspDTO<Object>> labelDTODTOCallableTask = () -> {
    24.             LabelParam labelParam = buildLabelParam(req);
    25.             LabelDTO labelDTO = labelService.queryLabelInfo(labelParam);
    26.             BaseRspDTO<Object> labelBaseRspDTO = new BaseRspDTO<Object>();
    27.             labelBaseRspDTO.setKey("labelDTO");
    28.             labelBaseRspDTO.setData(labelDTO);
    29.             return labelBaseRspDTO;
    30.         };
    31.         List<Callable<BaseRspDTO<Object>>> taskList = new ArrayList<>();
    32.         taskList.add(userInfoDTOCallableTask);
    33.         taskList.add(bannerDTOCallableTask);
    34.         taskList.add(labelDTODTOCallableTask);
    35.         ExecutorService executor = Executors.newFixedThreadPool(10);
    36.         List<BaseRspDTO<Object>> resultList = parallelInvokeCommonService.executeTask(taskList, 3, executor);
    37.         if (resultList == null || resultList.size() == 0) {
    38.             return new AppHeadInfoResponse();
    39.         }
    40.         UserInfoDTO userInfoDTO = null;
    41.         BannerDTO bannerDTO = null;
    42.         LabelDTO labelDTO = null;
    43.         //遍历结果
    44.         for (int i = 0; i < resultList.size(); i++) {
    45.             BaseRspDTO baseRspDTO = resultList.get(i);
    46.             if ("userInfoDTO".equals(baseRspDTO.getKey())) {
    47.                 userInfoDTO = (UserInfoDTO) baseRspDTO.getData();
    48.             } else if ("bannerDTO".equals(baseRspDTO.getKey())) {
    49.                 bannerDTO = (BannerDTO) baseRspDTO.getData();
    50.             } else if ("labelDTO".equals(baseRspDTO.getKey())) {
    51.                 labelDTO = (LabelDTO) baseRspDTO.getData();
    52.             }
    53.         }
    54.         System.out.println("结束并行查询app首页信息,总耗时:" + (System.currentTimeMillis() - beginTime));
    55.         return buildResponse(userInfoDTO, bannerDTO, labelDTO);
    56.     }

    基于以上代码,小伙伴们,是否还有其他方面的优化想法呢?比如这几个Callable查询任务,我们是不是也可以抽取一下?让代码更加简洁。

    二话不说,现在我们直接建一个BaseTaskCommand类,实现Callable接口,把查询用户信息、查询banner信息、label标签信息的查询任务放进去。

    代码如下:

    1. public class BaseTaskCommand implements Callable<BaseRspDTO<Object>> {
    2.     private String key;
    3.     private AppInfoReq req;
    4.     private IUserService userService;
    5.     private IBannerService bannerService;
    6.     private ILabelService labelService;
    7.     public BaseTaskCommand(String key, AppInfoReq req, IUserService userService, IBannerService bannerService, ILabelService labelService) {
    8.         this.key = key;
    9.         this.req = req;
    10.         this.userService = userService;
    11.         this.bannerService = bannerService;
    12.         this.labelService = labelService;
    13.     }
    14.     @Override
    15.     public BaseRspDTO<Objectcall() throws Exception {
    16.         if ("userInfoDTO".equals(key)) {
    17.             UserInfoParam userInfoParam = buildUserParam(req);
    18.             UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam);
    19.             BaseRspDTO<Object> userBaseRspDTO = new BaseRspDTO<Object>();
    20.             userBaseRspDTO.setKey("userInfoDTO");
    21.             userBaseRspDTO.setData(userInfoDTO);
    22.             return userBaseRspDTO;
    23.         } else if ("bannerDTO".equals(key)) {
    24.             BannerParam bannerParam = buildBannerParam(req);
    25.             BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam);
    26.             BaseRspDTO<Object> bannerBaseRspDTO = new BaseRspDTO<Object>();
    27.             bannerBaseRspDTO.setKey("bannerDTO");
    28.             bannerBaseRspDTO.setData(bannerDTO);
    29.             return bannerBaseRspDTO;
    30.         } else if ("labelDTO".equals(key)) {
    31.             LabelParam labelParam = buildLabelParam(req);
    32.             LabelDTO labelDTO = labelService.queryLabelInfo(labelParam);
    33.             BaseRspDTO<Object> labelBaseRspDTO = new BaseRspDTO<Object>();
    34.             labelBaseRspDTO.setKey("labelDTO");
    35.             labelBaseRspDTO.setData(labelDTO);
    36.             return labelBaseRspDTO;
    37.         }
    38.         
    39.         return null;
    40.     }
    41.     private UserInfoParam buildUserParam(AppInfoReq req) {
    42.         return new UserInfoParam();
    43.     }
    44.     private BannerParam buildBannerParam(AppInfoReq req) {
    45.         return new BannerParam();
    46.     }
    47.     private LabelParam buildLabelParam(AppInfoReq req) {
    48.         return new LabelParam();
    49.     }
    50. }

    以上这块代码,构造函数还是有比较多的参数,并且call()方法中,有多个if...else...,如果新增一个条件分支(比如查询浮层信息),那又得在call方法里修改了,并且BaseTaskCommand的构造器也要修改了

    大家是否有印象,当程序中出现多个if...else...时,我们就可以考虑使用策略模式+工厂模式优化。

    我们声明多个策略实现类,把条件分支里的实现,搬到策略类,如下:

    1. public interface IBaseTask {
    2.     //返回每个策略类的key,如是usetInfoDTO还是bannerDTO,还是labelDTO
    3.     String getTaskType();
    4.     BaseRspDTO<Objectexecute(AppInfoReq req);
    5.     
    6. }
    7.   
    8. //用户信息策略类
    9. @Service
    10. public class UserInfoStrategyTask implements IBaseTask {
    11.     @Autowired
    12.     private IUserService userService;
    13.     
    14.     @Override
    15.     public String getTaskType() {
    16.         return "userInfoDTO";
    17.     }
    18.     @Override
    19.     public BaseRspDTO<Objectexecute(AppInfoReq req) {
    20.         UserInfoParam userInfoParam = userService.buildUserParam(req);
    21.         UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam);
    22.         BaseRspDTO<Object> userBaseRspDTO = new BaseRspDTO<Object>();
    23.         userBaseRspDTO.setKey(getTaskType());
    24.         userBaseRspDTO.setData(userBaseRspDTO);
    25.         return userBaseRspDTO;
    26.     }
    27. }
    28. /**
    29.   * banner信息策略实现类
    30.   **/
    31. @Service
    32. public class BannerStrategyTask implements IBaseTask {
    33.     @Autowired
    34.     private IBannerService bannerService;
    35.     @Override
    36.     public String getTaskType() {
    37.         return "bannerDTO";
    38.     }
    39.     @Override
    40.     public BaseRspDTO<Objectexecute(AppInfoReq req) {
    41.         BannerParam bannerParam = bannerService.buildBannerParam(req);
    42.         BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam);
    43.         BaseRspDTO<Object> bannerBaseRspDTO = new BaseRspDTO<Object>();
    44.         bannerBaseRspDTO.setKey(getTaskType());
    45.         bannerBaseRspDTO.setData(bannerDTO);
    46.         return bannerBaseRspDTO;
    47.     }
    48. }
    49.   
    50. ...

    然后这几个策略实现类,怎么交给spring管理呢?我们可以实现ApplicationContextAware接口,把策略的实现类注入到一个map,然后根据请求方不同的策略请求类型(即userInfoDTO还是bannerDTO等),去实现不同的策略类调用。其实这类似于工厂模式的思想。代码如下:

    1. /**
    2.   * 策略工厂类
    3.   **/
    4. @Component
    5. public class TaskStrategyFactory implements ApplicationContextAware {
    6.     private Map<String, IBaseTask> map = new ConcurrentHashMap<>();
    7.     @Override
    8.     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    9.         Map<String, IBaseTask> tempMap = applicationContext.getBeansOfType(IBaseTask.class);
    10.         tempMap.values().forEach(iBaseTask -> {
    11.             map.put(iBaseTask.getTaskType(), iBaseTask);
    12.         });
    13.     }
    14.     public BaseRspDTO<ObjectexecuteTask(String key, AppInfoReq req) {
    15.         IBaseTask baseTask = map.get(key);
    16.         if (baseTask != null) {
    17.             System.out.println("工厂策略实现类执行");
    18.             return baseTask.execute(req);
    19.         }
    20.         return null;
    21.     }
    22. }

    有了策略工厂类TaskStrategyFactory,我们再回来优化下BaseTaskCommand类的代码。它的构造器已经不需要多个IUserService userService, IBannerService bannerService, ILabelService labelService啦,只需要传入策略工厂类TaskStrategyFactory即可。同时策略也不需要多个if...else...判断了,用策略工厂类TaskStrategyFactory代替即可。优化后的代码如下:

    1. public class BaseTaskCommand implements Callable<BaseRspDTO<Object>> {
    2.     private String key;
    3.     private AppInfoReq req;
    4.     private TaskStrategyFactory taskStrategyFactory;
    5.     public BaseTaskCommand(String key, AppInfoReq req, TaskStrategyFactory taskStrategyFactory) {
    6.         this.key = key;
    7.         this.req = req;
    8.         this.taskStrategyFactory = taskStrategyFactory;
    9.     }
    10.     @Override
    11.     public BaseRspDTO<Objectcall() throws Exception {
    12.         return taskStrategyFactory.executeTask(key, req);
    13.     }
    14. }  

    因此整个app首页信息并行查询,就可以优化成这样啦,如下:

    1. public AppHeadInfoResponse parallelQueryAppHeadPageInfo2(AppInfoReq req) {
    2.     long beginTime = System.currentTimeMillis();
    3.     System.out.println("开始并行查询app首页信息(最终版本),开始时间:" + beginTime);
    4.     List<Callable<BaseRspDTO<Object>>> taskList = new ArrayList<>();
    5.     //用户信息查询任务
    6.     taskList.add(new BaseTaskCommand("userInfoDTO", req, taskStrategyFactory));
    7.     //banner查询任务
    8.     taskList.add(new BaseTaskCommand("bannerDTO", req, taskStrategyFactory));
    9.     //标签查询任务
    10.     taskList.add(new BaseTaskCommand("labelDTO", req, taskStrategyFactory));
    11.     ExecutorService executor = Executors.newFixedThreadPool(10);
    12.     List<BaseRspDTO<Object>> resultList = parallelInvokeCommonService.executeTask(taskList, 3, executor);
    13.     if (resultList == null || resultList.size() == 0) {
    14.         return new AppHeadInfoResponse();
    15.     }
    16.     UserInfoDTO userInfoDTO = null;
    17.     BannerDTO bannerDTO = null;
    18.     LabelDTO labelDTO = null;
    19.     for (BaseRspDTO<Object> baseRspDTO : resultList) {
    20.         if ("userInfoDTO".equals(baseRspDTO.getKey())) {
    21.             userInfoDTO = (UserInfoDTO) baseRspDTO.getData();
    22.         } else if ("bannerDTO".equals(baseRspDTO.getKey())) {
    23.             bannerDTO = (BannerDTO) baseRspDTO.getData();
    24.         } else if ("labelDTO".equals(baseRspDTO.getKey())) {
    25.             labelDTO = (LabelDTO) baseRspDTO.getData();
    26.         }
    27.     }
    28.     System.out.println("结束并行查询app首页信息(最终版本),总耗时:" + (System.currentTimeMillis() - beginTime));
    29.     return buildResponse(userInfoDTO, bannerDTO, labelDTO);
    30.   }

    5. 思考总结

    以上代码整体优化下来,已经很简洁啦。那还有没有别的优化思路呢。

    其实还是有的,比如,把唯一标记的key定义为枚举,而不是写死的字符串"userInfoDTO"、"bannerDTO","labelDTO"。还有,除了CompletionService,有些小伙伴喜欢用CompletableFuture实行并行调用,大家可以自己动手操戈写一写。

    本文大家学到了哪些知识呢?

    1. 如何优化接口性能?某些场景下,可以使用并行调用代替串行。

    2. 如何实现并行调用呢?可以使用CompletionService

    3. 学到的后端思维是?日常开发中,要学会抽取通用的方法、或者工具。

    4. 策略模式和工厂模式的应用

  • 相关阅读:
    Docker安装入门教程
    spring boot 时间格式化输出
    Moment.js的常用函数、借助vue和Moment.js实现一个简单的时钟
    HaaS学习笔记 | 最详细的HaaS Python轻应用开发快速入门教程
    全面理解Web3.0时代
    深度详解 Android 屏幕刷新机制之 Choreographer
    我的 ReactNative 开发利器: 常用命令总结
    apriltag_ros简单上手
    除了console.log(),很多人不知道的其他方法console.table,console.dir,console.time等
    加菲猫卡丁车:激情竞速 Mac(卡通赛车竞速游戏)原生版
  • 原文地址:https://blog.csdn.net/WXF_Sir/article/details/124858958