定时任务编码总结-严谨、严谨、再严谨

最近做一个线上新闻的app系统后台,涉及到一个推送的功能。开始设计时没想太多,以为非常的容易,无非定时的把未处理的数据进行处理不就可以了吗?
于是写了个这样的代码,

 @Scheduled
          void work() {
                for (Message message : queryNotHander()) {
                     push(message);
                }
          }
          List<Message> queryNotHander() {return new ArrayList<Message>();};

          void push(Message message) {
                System.out.println(message);
          }

发现没,简单定时批量任务处理逻辑骨架。可是总觉得,有问题,心里不踏实,不敢保证代码不出异常啊?要是出异常了,那线程不就终止了吗,后续不就不能继续处理了吗?肯定不行。既然每条记录的处理需要单独的隔离,那么必然单条记录的异常必须不能影响到后续其他的处理逻辑,改代码,如下:

@Scheduled
     void work() {
           for (Message message : queryNotHander()) {
                push(message);
           }
     }
     List<Message> queryNotHander() {return new ArrayList<Message>();};

     void push(Message message) {
           try {
                doPush(message);
                changeStatusToSuccess(message);
           } catch (Exception e) {
                log.info("", e);
           }
     }

     void doPush(Message message) {}
     /**
      * 把状态改变为成功状态,不然下次还会处理相同的数据
      * @param message
      */
     void changeStatusToSuccess(Message message){}

多了一个具体处理方法,在push中进行了异常的捕获,并对于异常进行了日志记录,方便后面的异常追查,并且还做了状态的控制,成功处理的改变状态为success。
但是,仔细分析,还有很大的问题,如果定时任务查询出来的一批数据,并且没有到下一次定时启动时处理完成,也就是没有打上成功的标记,会如何?那么下一次就会同样查出相同的数据,也就是重复执行,尤其对于高频率的定时来说更是如此,怎么解决?看下面代码:

@Scheduled
     void work() {
           for (Message message : queryNotHander()) {
                process(message);
           }
     }
     List<Message> queryNotHander() {return new ArrayList<Message>();};


     void process(Message message) {
           try {
                if(!changeStatusByCASToHandering(message))
                     return;
                doProcess(message);
                changeStatusFromHandingToSuccess(message);
           } catch (Exception e) {
                log.info("", e);
           }
     }

     void doProcess(Message message) {}
     /**
      * 把处理中的状态变为处理成功
      * @param message
      */
     void changeStatusFromHandingToSuccess(Message message){}
     /**
      * 通过cas比较将状态从未处理到处理中
      * @param message
      * @return true 状态改变成功 fasle 失败(此时说明有其他线程已经占用了资源,也就是说其他线程正在处理)
      */
     boolean changeStatusByCASToHandering(Message message) {return true;}

上面流程,好似没问题了,循环资源,处理资源前进行cas操作(改变新值时比较原值)进行资源的占用(保证不会有其他线程会查出),然后处理,最后改变资源成功处理的标记。流程上是没问题了,不过代码去有些问题,异常处理不够完善,进行简单的修改,最后代码为:

@Scheduled
     void work() {
           for (Message message : queryNotHander()) {
                process(message);
           }
     }
     List<Message> queryNotHander() {return new ArrayList<Message>();};


     void process(Message message) {
           boolean successFlag = false;
           try {
                if(!changeStatusByCASToHandering(message))
                     return;
                doProcess(message);
                successFlag = true;
           } catch (Exception e) {
                log.info("", e);//此处为进行失败处理的地方,进行日志记录或者数据记录,或者邮件通知等等操作。
           } finally {
                if(successFlag)
                     changeStatusFromHandingToSuccess(message);
                else
                     changeStatusFromHandingToFailure(message);
           }
     }

     void doProcess(Message message) {}

     void changeStatusFromHandingToFailure(Message message) {};

     /**
      * 把处理中的状态变为处理成功
      * @param message
      */
     void changeStatusFromHandingToSuccess(Message message){}
     /**
      * 通过cas比较将状态从未处理到处理中
      * @param message
      * @return true 状态改变成功 fasle 失败(此时说明有其他线程已经占用了资源)
      */
     boolean changeStatusByCASToHandering(Message message) {return true;}

此流程用于频率很高的定时,应该没问题了,但是具体的场景,doProcess方法需要不同的处理。因为doProcess失败可能会在核心的业务处理完成后,也许是掉用了远程服务成功的后续处理异常,也许是数据更新完成后续处理异常(当然事务回滚除外)等等。处理失败也有很简单的一种情况,业务数据没有改变或者远程服务没有调用前的异常。
所以对于失败的处理,需要根据业务数据是否改变进行具体的处理,如何定义,需要看具体的业务,或者此方法中,就应该只处理核心的业务,保证失败的原子性(失败后业务数据并不会改变)。
总之,定时任务尤其是频率很高的定时,需要有非常严谨的思路以及细心的设计。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值