背景描述
功能:向用户提供区间可查询,用户输入时间区间,或者其他特定参数,后端调用底层平台获取数据后再进行业务处理、分页等封装以便展示。
问题:1. 当用户输入时间区间过大可能会导致跨平台查询底层数据量过大,查询速度慢、超时、撑爆内存,进而造成服务卡死或宕机。2. 由于数据量的多少和时间区间的长短并无之间联系(数据量多少与业务有关,有时候一秒内几千笔,而有时候数小时内都可能无记录),因此输入区间限制不宜在前端控制,而后端接收参数后也需跨平台调用接口,后端业务层入口也无法做范围限制。
愿景:我们需要对查询接口做超时处理,即查询有结果立即返回,若查询耗时超过30秒之后无论是否有结果也立即返回。
解决方案
该功能实现的业务层中开启线程池,启动两个线程来分别执行查询任务和监控超时打断任务。
技术思路
- 设定一个查询任务,将有可能查询超时及复杂业务处理的逻辑写进任务中,该查询任务丢给子线程执行;
- 再设定上述查询任务的守护任务,该任务先sleep 30秒,然后询问查询任务是否已完成,若未完成或未取消,则将查询任务终止。毫无疑问,这里的守护任务需持有查询任务的future对象;
- 业务层中将这两个任务丢进线程池,若查询结果很快有结果则立即返回并关闭守护线程;若查询超过30秒,则查询线程被守护线程打断,主线程继续处理超时异常并关闭守护任务及线程池;
代码实现
查询任务Task类
public class RangeQueryTask implements Callable<List> {
//复杂耗时查询的句柄
private ComplexRangeQuery complexRangeQuery;
public RangeQueryTask(ComplexRangeQuery complexRangeQuery){
this.complexRangeQuery = complexRangeQuery;
}
@Override
public List call() throws Exception {
try{
//执行查询逻辑 略
//返回数据处理及封装 略
}catch (Exception e){
//不同的异常的对应处理 略
}
return new ArrayList();
}
}
守护任务Task类
public class RangeQueryDemonTask implements Callable<Integer> {
private Future<List> complexRangeQryTaskFuture;
public RangeQueryDemonTask(Future<List> complexRangeQryTaskFuture){
this.complexRangeQryTaskFuture = complexRangeQryTaskFuture;
}
@Override
public Integer call() throws Exception {
try{
sleep(30000);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if (!complexRangeQryTaskFuture.isDone()||!complexRangeQryTaskFuture.isCancelled()){
complexRangeQryTaskFuture.cancel(true);
}
//此处将超时异常抛给前端处理
throw new BussinessException(EnumErrorInfo.QRY_TIMEOUT.getCode(),EnumErrorInfo.QRY_TIMEOUT.getMsg());
}
return null;
}
}
业务层处理逻辑
ThreadPoolExecutor threadPoolExecutor = null;
List resultList;
try{
//拟用ComplexRangeQuery指代耗时查询,输入参数为起始时间及终止时间
ComplexRangeQuery complexRangeQuery = new ComplexRangeQuery("startTime","endTime");
threadPoolExecutor = new ThreadPoolExecutor(2,2,10, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>());
// 定义查询任务
RangeQueryTask rangeQueryTask = new RangeQueryTask(complexRangeQuery);
Future<List> qryTaskFuture = threadPoolExecutor.submit(rangeQueryTask);
// 定义守护任务 并将查询任务结果句柄传入
RangeQueryDemonTask rangeQueryDemonTask = new RangeQueryDemonTask(qryTaskFuture);
Future<Integer> demonTaskFuture = threadPoolExecutor.submit(rangeQueryDemonTask);
// 业务主线程一直阻塞于此,若30秒之后查询任务未结束,则查询任务线程被打断后向下继续执行
resultList = qryTaskFuture.get();
if (null != resultList){
//对于查询之后立即返回结果的 也需将守护线程终止
if (!demonTaskFuture.isDone()||!demonTaskFuture.isCancelled()){
demonTaskFuture.cancel(true);
}
}
}catch (Exception e){
// 各类异常的相应处理 略
}finally {
//关闭线程池
if (null!=threadPoolExecutor&&!threadPoolExecutor.isShutdown()){
threadPoolExecutor.shutdownNow();
}
}
return resultList;
本代码亲测有效,现已上线,运行稳定。