批量查询接口如何巧妙利用单查询接口中的@Cacheable

如标题所述,本文是为了探讨在已有jvm缓存的单查询接口的基础上增加批量查询接口功能,要如何实现,如何优化,如何抉择。

spring-cache用法请自行查询。

demo:

单查询接口如下:

@Service
public class BizCacheServiceImpl implements BizCacheService {

    /**
     * 单查询接口
     *
     * @param id
     * @return
     */
    @Cacheable(key = "'getBizCacheVO' + #id", value = "valueName")
    public BizCacheVO getStringById(Integer id) {
        //略去过程
        try {
            //模拟方法执行
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        BizCacheVO bizCacheVO = new BizCacheVO();
        bizCacheVO.setId(id);
        bizCacheVO.setValue("value");
        return bizCacheVO;
    }
}

一、单线程查询单个接口

  public Map<Integer, BizCacheVO> getListByIds(List<Integer> ids) {
        Map<Integer, BizCacheVO> result = new HashMap<>();
        for (Integer id : ids) {
            BizCacheVO bizCacheVO = bizCacheService.getStringById(id);
            if (bizCacheVO != null && bizCacheVO.getId() != null) {
                result.put(bizCacheVO.getId(), bizCacheVO);
            }
        }
        return result;
    }

缺点:如上图所述,单查询接口无缓存的情况下,执行时间大约为1s。如果同时请求20个,最起码也是20s。

优点:如果要查询的ids都被jvm缓存了,那完全没必要启用多线程,单线程完全可以适用,单线程还不浪费额外线程资源及上下文切换的损耗。

二、多线程查询单个接口

@Service
public class BizBatchCacheServiceImpl implements BizBatchCacheService {

    @Autowired
    private BizCacheService bizCacheService;
    //线程池
    private ExecutorService executor = Executors.newFixedThreadPool(50);

    /**
     * 批量接口
     *
     * @param ids
     * @return
     */
    public Map<Integer, BizCacheVO> getListByIds(List<Integer> ids) {
        Map<Integer, BizCacheVO> result = new HashMap<>();
        List<CompletableFuture<BizCacheVO>> completableFutureList = new ArrayList<>();
        for (Integer id : ids) {
            completableFutureList.add(CompletableFuture.supplyAsync(() -> bizCacheService.getStringById(id), executor));
        }
        try {
            CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[]{})).get();
            for (CompletableFuture<BizCacheVO> future : completableFutureList) {
                BizCacheVO bizCacheVO = future.get();
                if (bizCacheVO != null && bizCacheVO.getId() != null) {
                    result.put(bizCacheVO.getId(), bizCacheVO);
                }
            }
        } catch (Exception e) {
            //异常处理
        }
        return result;
    }
}

多线程如上述代码。

单查询接口无缓存的情况下,执行时间大约为1s。如果同时请求20个,总耗时也在1s多一些时间。看起来性能很不错,但有这么一个问题,批量接口的入参不固定,这一批数据里,可能有的已经存在缓存了,有的还没有缓存,那么有缓存的数据,还是额外的开启了线程去调用单查询接口,这里就造成了线程的消耗以及上下文切换时间的损耗。

缺点:已经缓存过的数据,还是会开启线程查询,消耗额外资源。

优点:如果数据大部分都是没有缓存的,这样调用的性能会远超单线程。

分析了单线程和多线程方式的调用,各有利弊,那怎么去平衡这两种方式呢?

那就是接下来的第三种方式。

三、cache+多线程接口

平衡点:让已经缓存过的直接去取缓存,未缓存的数据去开启多线程处理

@Service
public class BizBatchCacheServiceImpl implements BizBatchCacheService {

    @Autowired
    private BizCacheService bizCacheService;
    //线程池
    private ExecutorService executor = Executors.newFixedThreadPool(50);
    @Resource
    private CacheManager cacheManager;


    /**
     * cache+多线程方式批量接口
     *
     * @param ids
     * @return
     */
    public Map<Integer, BizCacheVO> getListByIds(List<Integer> ids) {
        Map<Integer, BizCacheVO> result = new HashMap<>();
        Cache cache = cacheManager.getCache("valueName");
        List<CompletableFuture<BizCacheVO>> completableFutureList = new ArrayList<>();
        Set<Integer> noHit = new HashSet<>();
        for (Integer id : ids) {
            BizCacheVO bizCacheVO = cache.get("getBizCacheVO" + id, BizCacheVO.class);
            if (bizCacheVO != null && bizCacheVO.getId() != null) {
                result.put(bizCacheVO.getId(), bizCacheVO);
            } else {
                noHit.add(id);
            }
        }
        //将未命中缓存的数据进行多线程请求
        for (Integer id : noHit) {
            completableFutureList.add(CompletableFuture.supplyAsync(() -> bizCacheService.getStringById(id), executor));
        }
        try {
            CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[]{})).get();
            for (CompletableFuture<BizCacheVO> future : completableFutureList) {
                BizCacheVO bizCacheVO = future.get();
                if (bizCacheVO != null && bizCacheVO.getId() != null) {
                    result.put(bizCacheVO.getId(), bizCacheVO);
                }
            }
        } catch (Exception e) {
            //异常处理
        }
        return result;
    }
}

优点:不再为已有的缓存数据浪费额外的线程资源,面对各种情况更灵活,更均衡

缺点:如果数据一直都有缓存,这样写就很鸡肋。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值