定时任务-异常日志(可针对一个集合数据中的单条数据进行记录异常日志,不影响其它数据)

本文介绍了一种改进的定时任务异常处理机制,通过AbstractQuartzJob和QuartzJobExecution工具类集中管理定时任务,及时捕获并记录异常日志。同时,为避免因单条数据异常导致整个集合事务回滚,提出了使用isRun标志和异常次数限制的方法,确保其他任务正常执行,并减少了重复的日志记录。此外,还展示了如何在业务代码中优雅地处理异常,确保系统的稳定性和可维护性。
摘要由CSDN通过智能技术生成

描述

我们在部署系统开启定时任务之后,当定时任务的业务代码模块出现异常,我们往往不能及时查看,还必须在服务器的log日志文件里查找,这对于运维来说极其不方便。故需要一个能及时捕捉定时任务异常的功能,并能在后台界面显示。参考若依系统定时任务模块代码

也会存在对一个集合的数据遍历处理,却因为该集合的一条数据异常导致整个集合数据事务回滚的情况,以下内容也针对这种情况进行改善。

工具类 AbstractQuartzJob

在之前的定时任务中,我们都是通过对每一个定时任务指定的类实现定时任务的接口去执行业务代码,这就显得定时任务很分散,无法集中管理,AbstractQuartzJob配置类就是对各个定时任务集中管理的工具类,从而实现监控各个定时任务。

主要方法

 @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        /** 获取定时任务相关信息**/
        QuartzJob job = new QuartzJob();
        BeanUtils.copyProperties(context.getMergedJobDataMap().get(JobConstants.TASK_PROPERTIES),job);
      try {
           before(context, job);//定时任务执行前
           doExecute(context, job);//定时任务执行
//          after(context, job, null);//定时任务执行后,记录成功日志,因主要记录异常日志故注释掉了,若有需要解开注释
        } catch (InvocationTargetException e) {
            String errorMessage = e.getTargetException().getMessage();//获取异常信息
            log.error("定时任务执行异常:{}",errorMessage);
            after(context, job, errorMessage);//定时任务执行后,记录异常日志
        }catch (Exception ex) {
            String errorMessage = ex.getMessage();//获取异常信息
            log.error("定时任务执行异常:{}", errorMessage);
            after(context, job, errorMessage);//定时任务执行后,记录异常日志
        }
    }

定时任务实现工具类 QuartzJobExecution

public class QuartzJobExecution extends AbstractQuartzJob{
    @Override
    protected void doExecute(JobExecutionContext context, QuartzJob job) throws Exception {
        JobInvokeUtils.invokeMethod(context,job);
    }
}

描述:根据定时任务的相关信息,获取定时任务下的业务实现类,从而调用该类下的execute方法

实现方法 execute

例:

@Slf4j
@Component
public class AgvTaskAutoUpdateJob {
​
    /**
     * 任务是否正在执行标记 :false--未执行; true--正在执行; 默认未执行
     */
    private static volatile boolean isRun = false;
​
    public void execute(){
        // 任务正在执行,跳过本次执行
        if (isRun) {
            log.info("定时任务——agv任务表:前一次未执行完,跳过本次任务!");
            return;
        }
        isRun = true;
        try{
            log.info(String.format("定时任务——agv任务表!  时间:" + DateUtils.getTimestamp()));
            SpringUtils.getBean(StudentService.class).method1();
        }finally {
            isRun = false;
        }
    }
}

为什么在类中定义 isRun常量呢?

该常量的作用主要是类似于锁的作用。在实际情况中,会由于网络的原因导致业务代码的处理时间大于定时任务每次处理时间的间隔,这就会出现重复执行定时任务的情况,故而在这里定义一个可见性的常量,当上一条定时任务还未执行完成时会跳过本次定时任务。

为什么不用Lock和synchronized?

使用Lock最终解锁需要使用unLock,但是需要利用try,这样会捕捉到异常,无法将异常返回给AbstractQuartzJob工具类,从而会导致无法记录异常日志。使用synchronized会出现死锁的情况,会阻塞下一个定时任务。

异常抛出

1、普通使用(一条记录异常,整个任务异常抛出)

在业务代码异常处直接抛出异常即可

throw new PracticBootException("错误信息");//也可其它异常信息

2、针对使用(一条记录异常,只针对该条记录进行异常抛出,对其它记录不影响,包括事务回滚)

需要借用外层方法嵌套内层方法(会抛出异常的业务代码)

例:

@Override
public void method1() {
    List<Student> studentList = studentService.list();
    /**循环内需要处理每条记录的异常,以保证其它循环记录能正常执行*/
    for(Student student : studentList){
        String errorMsg = this.method2();
        if(StringUtils.isNotEmpty(errorMsg)){
            throw new PracticBootException(errorMsg);//若内层方法捕捉到的异常信息不为空,抛出异常,给QuartzJobExecution工具类捕捉,记录异常日志
        }
    }
}
​
@DS("数据源")//用于开启一个新的事务,每条记录拥有不同的事务,互不影响
public Sting merhod2(){
     String errorMsg = "";
    try{
        ..............
    }catch(Exception e){
         if(StringUtils.isNotEmpty(e.getMessage())){
             errorMsg = e.getMessage();
         }else {
               errorMsg = "系统异常";//为防止出现异常信息为空的情况,故手动将这些信息为空的异常归类为系统异常
            }//捕捉到异常,记录异常
    }finally{
        return errorMsg;//将异常信息返回给外层方法
    }
}

3、特殊使用(当单条记录异常次数大于N次后跳过,不再记录异常日志)

上面方法可以看出,每执行一次定时任务,若异常记录一直没有被修正,异常日志就会一直记录,从而使日志的数据记录过于重复显示,且浪费了数据库空间。故优化如下:

1、首先需要在实体类中加入一个字段,用来记录单条数据出现的异常次数

  /** 异常次数 */
    @ApiModelProperty(value = "异常次数")
    private Integer errorNumber;

2、以大于3次不记录异常日志为例,示例代码:

@Override
public void method1() {
    List<Student> studentList = studentService.list();
    /**循环内需要处理每条记录的异常,以保证其它循环记录能正常执行*/
    for(Student student : studentList){
        Student student = this.method2(student);
        //内层方法返回的对象里的异常次数大于3次直接跳出循环
        if(student.getErrorNumber() >3){
           continue;
        }
     if (StringUtils.isNotEmpty(student.getErrorMsg())){
                            throw new PracticBootException(student.getErrorMsg());//若内层方法捕捉到的异常信息不为空,抛出异常,给QuartzJobExecution工具类捕捉,记录异常日志
                        }
    }
}
​
@DS("数据源")//用于开启一个新的事务,每条记录拥有不同的事务,互不影响
public Student merhod2(Student student){
     String errorMsg = "";
    try{
        ..............
    }catch(Exception e){
      student.setErrorNumber(wmsAgvTask.getErrorNumber()+1);
      if(student.getErrorNumber() <= 3){
         if(StringUtils.isNotEmpty(e.getMessage())){
             errorMsg = e.getMessage();
         }else {
               errorMsg = "系统异常";//为防止出现异常信息为空的情况,故手动将这些信息为空的异常归类为系统异常
            }//捕捉到异常,记录异常
      }
    }finally{
         if(student.getErrorNumber() <= 3){
                studentService.updateById(student);//更新异常次数
            }
            student.setErrorMsg(errorMsg);
            return student;//对象包含异常信息一起返回给外层方法
    }
}

说明:PracticBootException为作者自定义的异常,他人可使用自己的异常

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值