CompletableFuture 异步编排

发布时间 2023-08-13 16:39:38作者: homle

 

 1. 业务场景

   查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多的时间。

  假如商品详情页的每个查询,需要如下标注的时间才能完成,那么,用户需要 5.5s 后才能看到商品详情页的内容。很显然是不能接受的,如果有多个线程同时完成这 6 步操作,也许只需要 1.5s 即可完成响应。

  获取sku基本信息(0.5s);获取sku的图片信息(0.5s);获取sku的促销信息(1s);获取spu的所有销售属性(1s);获取规格参数组及规格参数(1.5s);spu详情(1s)

 

2. 查询商品详情接口实现

    @GetMapping("/{skuId}.html")
    public String skuItem(@PathVariable("skuId") Long skuId, Model model) throws ExecutionException, InterruptedException {

        System.out.println("准备查询" + skuId + "详情");
        long start = System.currentTimeMillis();

        SkuItemVo vos = skuInfoService.item(skuId);

        model.addAttribute("item",vos);

        long end = System.currentTimeMillis();
        System.out.println("改造前耗时:" + (end - start));

        return "item";
    }

    @Override
    public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {
        SkuItemVo skuItemVo = new SkuItemVo();
        //1、sku基本信息的获取  pms_sku_info
        SkuInfoEntity info = getById(skuId);
        skuItemVo.setInfo(info);
        Long catalogId = info.getCatalogId();
        Long spuId = info.getSpuId();

        //2、sku的图片信息    pms_sku_images
        List<SkuImagesEntity> images = skuImagesService.getImagesBySkuId(skuId);
        skuItemVo.setImages(images);
        //3、获取spu的销售属性组合
        List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrBySpuId(spuId);
        skuItemVo.setSaleAttr(saleAttrVos);

        //4、获取spu的介绍    pms_spu_info_desc
        SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(spuId);
        skuItemVo.setDesc(spuInfoDescEntity);
        //5、获取spu的规格参数信息
        List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(spuId, catalogId);
        skuItemVo.setGroupAttrs(attrGroupVos);

        return skuItemVo;
    }

  在未使用异步编排之前所有的信息的查询都是串行获取的,执行后3000ms左右  

 

3.  CompletableFuture 异步编排

  在Java8中可以使用CompletableFuture 异步编排,sku的基本信息是先要获取的,当获取到基本信息后可以使用异步编排同时进行其他任务信息的获取,当所有任务都执行完成后,才返回实体;

  (1)在配置文件中声明线程池的配置信息

#application.properties
#配置线程池
gulimall.thread.coreSize=20
gulimall.thread.maxSize=200
gulimall.thread.keepAliveTime=10

  (2)创建线程池配置类,从配置文件中获取线程信息

@ConfigurationProperties(prefix = "gulimall.thread")
// @Component
@Data
public class ThreadPoolConfigProperties {

    private Integer coreSize;

    private Integer maxSize;

    private Integer keepAliveTime;


}

  (3)创建自定义线程池

@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {


    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
        return new ThreadPoolExecutor(
                pool.getCoreSize(),
                pool.getMaxSize(),
                pool.getKeepAliveTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }

}

  (4)对接口进行改造

    @GetMapping("/{skuId}.html")
    public String skuItem(@PathVariable("skuId") Long skuId, Model model) throws ExecutionException, InterruptedException {

        System.out.println("准备查询" + skuId + "详情");
        long start = System.currentTimeMillis();

        SkuItemVo vos = skuInfoService.item(skuId);

        model.addAttribute("item",vos);

        long end = System.currentTimeMillis();
        System.out.println("改造前耗时:" + (end - start));

        return "item";
    }

    @Override
    public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {
        SkuItemVo skuItemVo = new SkuItemVo();

        //1、sku基本信息的获取  pms_sku_info
        CompletableFuture<SkuInfoEntity> infoFutrue = CompletableFuture.supplyAsync(() -> {
            SkuInfoEntity info = getById(skuId);
            skuItemVo.setInfo(info);
            return info;
        }, executor);

        //3、获取spu的销售属性组合
        CompletableFuture<Void> saleAttrFuture = infoFutrue.thenAcceptAsync((res)->{
            List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrBySpuId(res.getSpuId());
            skuItemVo.setSaleAttr(saleAttrVos);
        },executor);

        //4、获取spu的介绍    pms_spu_info_desc
        CompletableFuture<Void> descFuture = infoFutrue.thenAcceptAsync((res)->{
            SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());
            skuItemVo.setDesc(spuInfoDescEntity);
        },executor);

        //5、获取spu的规格参数信息
        CompletableFuture<Void> baseAttrFuture = infoFutrue.thenAcceptAsync((res)->{
            List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
            skuItemVo.setGroupAttrs(attrGroupVos);
        },executor);

        //2、sku的图片信息    pms_sku_images
        CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
            List<SkuImagesEntity> images = skuImagesService.getImagesBySkuId(skuId);
            skuItemVo.setImages(images);
        }, executor);

        //等待所有任务都完成
        CompletableFuture.allOf(saleAttrFuture,descFuture,baseAttrFuture,imageFuture).get();
        return skuItemVo;
    }

  使用异步编排后,先获取到sku的基本信息,获取sku的图片信息可以与sku基本信息获取同时执行,剩下的任务等待任务1执行完成后都可以同时进行,等待所有的任务完成后才返回skuItemVo,消耗时间在900ms左右。

  总结:

  (1)CompletableFuture 中 runXxxx 都是没有返回结果的,supplyXxx 都是可以获取返回结果的,因为要获取到sku的基本信息的返回结果,所以使用supplyAsync,sku图片信息的获取不需要任务1的返回结果且可以与任务1同时执行;

  (2)可以使用自定义的线程池,否则就用默认的线程池;