@hello,很久没更了,最近上班太忙了,还有楼主太懒。。。。今天给大家分享一篇多线程批量处理数据库数据的解耦解决方案。。文章有点糙。。望谅解。
多线程批量处理任务(解耦,提高效率,保证事务一致性)
知识点
1.aop
2.线程池
3.线程通信
4.事务
案例:批量导入更新数据库数据,当用户导入上千条时,由于业务复杂,最终的执行sql相当于数据条数的十倍以上,更新时长超过80s最终超时异常。
我的优化方案(有更好的方案分享给博主哟,欢迎指正):
1-异步处理,提示用户稍候查看,后台处理;
2-同步处理,并行处理,更新脚本拼接,只做一次数据库链接更新,减少创建链接销毁链接的开销。因为特殊业务场景,不允许第一种方案,我选择了第二种,最终实现的效果由80s降低到10s以内。
整体思路:
1.拦截批量处理方法
2.在环绕通知中,调用任务处理方法mainThreadRun();
3.通过BatchDealWithAnt注解的参数index,找到批量处理集合
4.通过BatchDealWithAnt注解的参数minMath,判断是否需要对方法中批量处理的list集合进行切割,不需要则直接调用proceed方法执行放行。
5.如果满足批量处理条件,根据cpu核心数确定任务执行线程数nThreads。
6.创建线程池,事务回滚对象BatchWithRollBack(这里最好用对象),声明主线程计数器(1)、子线程计数器(nThreads)
7.拆分任务,组装参数
8.自定义线程对象,实现Callable接口,重写call()方法,在call()方法中调用proceed方法,并传入切割过后的参数,返回值类型可以根据业务自定
9.将线程任务提交给线程池
10.任务完全提交后,主线程调用子线程计数器的await方法等待所有子线程完成任务
11.子线程完成后,调用子线程计数器的countDown方法,计数-1,然后调用主线程计数器的await方法,等待主线程放行
12.子线程全部执行完成,主线程根据await()方法的返回值判断是否回滚,如果回滚,BatchWithRollBack对象设置为ture,并执行主线程计数器的countDown方法放行
13.此时子线程放行,判断回滚对象BatchWithRollBack是否回滚,如果是,则抛出异常
最终结果:任何一个子线程处理失败,全部回滚,当且仅当所有线程执行成功,事务才全部提交
注意:子任务必须加入事务,主线程等待所有子任务完成时间不能太长,否则导致线程占用资源时间过长,数据库锁住时间过长!
第一步,自定义注解、拦截器
//自定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BatchDealWithAnt {
int timeout() default 5;
int minMath() default 200;
int index() default 0;
}
//自定义拦截器
@Aspect
@Component
@Slf4j
public class BatchDealWithAspectj {
@Resource
private BatchWithService batchWithService;
@Pointcut("@annotation(com....BatchDealWithAnt)")
public void batchDealWithPointCut(){
}
@Around(value = "batchDealWithPointCut()")
public Object batchDealWithAround(ProceedingJoinPoint joinPoint){
Object res = null;
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = null;
try {
method = joinPoint.getTarget().getClass().getMethod(joinPoint.getSignature().getName(), methodSignature.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new BaseException("=======批量处理异常!");
}
String methodName = method.getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
BatchDealWithAnt batchDealWithAnt = method.getAnnotation(BatchDealWithAnt.class);
try {
res = mainThreadRun(joinPoint,batchDealWithAnt,className,methodName);
} catch (Throwable throwable) {
throwable.printStackTrace();
log.error("=======批量处理:{}--{}异常!",className,methodName);
throw new BaseException("=======批量处理:"+className + "--"+methodName+"异常!");
}
return res;
}
public Object mainThreadRun(ProceedingJoinPoint joinPoint,BatchDealWithAnt batchDealWithAnt,String className,String methodName) throws Throwable {
Object res = null;
Object[] args = joinPoint.getArgs();
Object tasks = args[batchDealWithAnt.index()];
if (tasks instanceof List){
List tasksList = (List) tasks;
if (tasksList.size() < batchDealWithAnt.minMath()){
log.info("=======批量处理:{}--{}低于处理阈值,正常执行......",className,methodName);
res = joinPoint.proceed(args);
return res;
}
if (batchDealWithAnt.timeout() > 30){
log.info("=======批量处理:{}--{}超过最大等待时间,正常执行......",className,methodName);
res = joinPoint.proceed(args);
return res;
}
log.info("=======批量处理:为{}--{}高于处理阈值,预分配任务中......",className,methodName);
int perThreadHandleCount = 1;
int cupNum = Runtime.getRuntime().availableProcessors();
int nThreads = (int)Math.floor(cupNum*0.7*2) ;
int taskSize = tasksList.size();
perThreadHandleCount = taskSize % nThreads == 0 ? taskSize / nThreads : taskSize / nThreads + 1;
nThreads = taskSize % perThreadHandleCount == 0 ? taskSize / perThreadHandleCount : taskSize / perThreadHandleCount + 1;
log.info("=======批量处理,共创建 {}个线程",nThreads);
CountDownLatch mainLatch = new CountDownLatch(1);
CountDownLatch threadLatch = new CountDownLatch(nThreads);
BatchWithRollBack rollBack = new BatchWithRollBack(false);
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("批量处理:" + className + " - " + methodName + "-pool-").build();
ThreadPoolExecutor fixedThreadPool = new ThreadPoolExecutor(
nThreads,
nThreads,
batchDealWithAnt.timeout(),
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(nThreads),
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
List<Future<Boolean>> futures = Lists.newArrayList();
BlockingDeque<Boolean> resultList = new LinkedBlockingDeque<>(nThreads);
for (int i = 0; i < nThreads; i++) {
int lastIndex = (i + 1) * perThreadHandleCount;
List subList = tasksList.subList(i * perThreadHandleCount, lastIndex >= taskSize ? taskSize : lastIndex);
Object[] params = new Object[args.length];
for (int j = 0; j < args.length; j++) {
if (j == batchDealWithAnt.index()){
params[batchDealWithAnt.index()] = subList;
continue;
}
params[j] = args[j];
}
BatchWithCallable threadHandleTaskCallable = new BatchWithCallable(mainLatch, threadLatch, rollBack,joinPoint,params,resultList);
Future<Boolean> future = fixedThreadPool.submit(threadHandleTaskCallable);
futures.add(future);
}
try {
boolean await = threadLatch.await(batchDealWithAnt.timeout(), TimeUnit.SECONDS);
if (!await) {
log.debug("==========批量处理线程执行超时");
rollBack.setRollBack(true);
} else {
log.info("批量处理,线程执行完毕,共 {}个线程",nThreads);
for (int i = 0; i < nThreads; i++) {
Boolean result = resultList.take();
log.debug("==========子线程返回结果result: "+result);
if (!result) {
rollBack.setRollBack(true);
}
}
}
} catch (InterruptedException e) {
log.error("等待所有子线程执行完毕时,出现异常");
rollBack.setRollBack(true);
e.printStackTrace();
throw new MsgException("等待所有子线程执行完毕时,出现异常,整体回滚");
} finally {
mainLatch.countDown();
log.info("关闭线程池,释放资源");
fixedThreadPool.shutdown();
}
if (rollBack.getRollBack()){
throw new BaseException("有线程执行异常,主线程回滚");
}
log.info("======>批量处理成功");
} else {
log.error("批量处理:{}--{}参数异常,正常执行......",className,methodName);
res = joinPoint.proceed(args);
}
return res;
}
class BatchWithCallable implements Callable<Boolean> {
private CountDownLatch mainLatch;
private CountDownLatch threadLatch;
private BatchWithRollBack rollBack;
private ProceedingJoinPoint joinPoint;
private Object[] args;
private BlockingDeque<Boolean> resultList;
public BatchWithCallable(CountDownLatch mainLatch, CountDownLatch threadLatch, BatchWithRollBack rollBack,ProceedingJoinPoint joinPoint,Object[] args,BlockingDeque<Boolean> resultList) {
this.mainLatch = mainLatch;
this.threadLatch = threadLatch;
this.rollBack = rollBack;
this.joinPoint = joinPoint;
this.args = args;
this.resultList = resultList;
}
@Override
public Boolean call() throws Exception {
return batchWithService.childThreadRun(joinPoint,args,threadLatch,mainLatch,rollBack,resultList);
}
}
}
第二步:子任务方法实现(这里事务做了全局配置,所以没加事务注解!)
public interface BatchWithService {
Boolean childThreadRun(ProceedingJoinPoint joinPoint, Object[] args, CountDownLatch threadLatch, CountDownLatch mainLatch, BatchWithRollBack rollBack, BlockingDeque<Boolean> resultList);
}
@Service("batchWithService")
@Slf4j
public class BatchWithServiceImpl implements BatchWithService{
@Override
public Boolean childThreadRun(ProceedingJoinPoint joinPoint, Object[] args, CountDownLatch threadLatch, CountDownLatch mainLatch, BatchWithRollBack rollBack, BlockingDeque<Boolean> resultList) {
log.info("=====线程:"+Thread.currentThread().getName()+",开始执行====");
long startTime = System.currentTimeMillis();
Boolean result = false;
try {
joinPoint.proceed(args);
result = true;
} catch (Throwable throwable) {
log.error("=====线程{}执行任务出现异常,等待主线程通知是否回滚,异常信息:{}",Thread.currentThread().getName(),throwable.getMessage());
throwable.printStackTrace();
}
resultList.add(result);
threadLatch.countDown();
log.info("=====线程{},计算过程已经结束,等待主线程通知是否需要回滚====",Thread.currentThread().getName());
long endTime = System.currentTimeMillis();
long sec = (endTime - startTime)/1000;
log.info("=====线程{},计算时间秒:{} ====",Thread.currentThread().getName(),sec);
try {
mainLatch.await();
log.info("=====子线程{}再次启动",Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new MsgException("批量处理,线程InterruptedException异常");
}
if (rollBack.getRollBack()) {
log.info("=====线程{},线程回滚====",Thread.currentThread().getName());
throw new MsgException("批量处理,线程回滚");
}
log.info("=====线程{},线程退出====",Thread.currentThread().getName());
return result;
}
}
第三步:业务层批量处理方法使用BatchDealWithAnt注解即可,根据实际情况传入参数,楼主经验有限,如有不对,欢迎指正!
|