如标题所述,本文是为了探讨在已有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;
}
}
优点:不再为已有的缓存数据浪费额外的线程资源,面对各种情况更灵活,更均衡
缺点:如果数据一直都有缓存,这样写就很鸡肋。
|