如何在定时任务执行的过程中动态的对其进行修改

场景

在某一个时间段,系统有定时任务会对某一张表的数据进行处理,在处理的这时候可能会有其他的服务会来对这个表进行操作。如果他们操作了同一个行记录,这个时间点上就会出现数据不一致的可能性。所以需要对这种情况进行处理。

解决方案

背景介绍:任务当前是对表数据进行遍历取出来,然后逐条进行执行。

方案一

这种方案是最简单,改造成本最低的。我们可以加一个变量来标识任务是否有在执行的过程中,执行的过程中拒绝其他服务的调用即可。这里就不展开赘述了。

方案二

这个方案的设计初衷就是标题所述,想在定时任务的执行过程中,其他服务也可以进行调用。但是其他服务要先来定时任务处进行查询是否已被处理,未被执行到的行记录就更新下状态,让定时任务跳过当条记录,已处理的数据则拒绝更新,来保证数据的一致性。

定时任务的流程图大概是这样的
1.加载资源处理成map
2.遍历每一条记录
3.判断该记录是否被其他服务更新状态,是则跳过,否则继续处理
st=>start: 开始
op1=>operation: 处理资源成map
op2=>operation: 遍历执行每一条记录(判断状态)
cond1=>condition: 该条记录是否被更新状态
op3=>operation: 继续处理该条记录


e=>end: 结束

st->op1->op2->cond1->op3->e
cond1(no)->op3
cond1(yes)->op2

改造思路

定时任务部分

  1. 将原有的list存储换成ConcurrentHashMap以支持多线程的操作,结构以主键作为key,value是处理的表对象 + 该条记录的状态(0-未处理, 1-处理中, 2-已处理)。
  2. 将封装的ConcurrentHashMap对外暴露以给其他接口服务进行调用。
  3. 遍历的时候,对该条记录的状态进行判断,对状态被更新为2的记录进行不处理。
    ps:这里状态为2的记录就是被其他服务修改的,然后通知给定时任务。

其他服务接口

  1. 查询这个ConcurrentHashMap是否当前记录在定时任务的处理中,不在则可以直接使用,在则对状态进行更新为2,通知定时任务不可以继续处理了。

存在的问题
这种方案设计完成后,我深思了一下。发现无法保证同时间同一个key在同时get和put的时候执行的顺序,所以会存在这种边界问题(我自己这么称之)。虽然ConcurrentHashMap是线程安全的,但是它任然无法满足我的要求,如下伪代码所示。

//其他服务接口
line = map.get(key); //这里先取到
if(line.state == 0){ //满足未执行的时候,进行更新
    //更新state=2;
}

get和put执行的区间,定时任务同时也进行了该条行记录的数据,这就会产生数据不一致的问题。
方案二其实无法保证数据的一致性,在稍微极端的情况下。于是就有了方案三,基于方案二的迭代思路。

方案三

在方案二中我们知道了极端情况下,数据会存在不一致的问题。是因为数据在第一次被其他服务接口读取后,又在定时任务的循环中被读取了,重新进行处理。那么我们对这里进行一定的改造是不是就可以了。顺着这个思路我就继续往下想。

然后还真被我想到了处理方法,也是我最近在学习spring源码的过程中得到到想法,我可以对被读取的数据进行封装,不直接是行记录的对象,正如spring-beans中的beanbeanWrapper的关系**

封装类伪代码如下。

public class LineWrapper {
    private T line; //封装的行对象
    private volatile int state = 0; //计数器
    private ReentrantLock lock = new ReentrantLock(); //锁

    public LineWrapper(T line){
        this.line = line;
    }

    public T getLine(){
        //state != 0则表表明不是第一次就不用进行锁的判断可以直接返回,如果第一次则进行尝试加锁
        if(state == 0 && lock.tryLock()){
            lock.lock(); //加锁
            state++; //增加计数器
            return line; //返回存在的对象
        }
        return null; //否则返回null,这样不管是定时任务还是其他服务接口都不进行处理
        //TODO 在finally进行锁的释放
    }
}

这样设计之后就可以保证在极端的情况下也不会出现该行记录被操作两次,同时也可以抛弃掉ConcurrentHashMap,使用HashMap,来提高一些性能。

Ps

方案三默认是单机版存储在内存中的
如果在微服务的架构下,可以使用 redis + 分布式锁 来进行替换

最终

finally,选取了第一种方案(好失落啊),虽然第三种方案更加的完善,支持同时的处理。但是它的改造成本要高于第一种方案不少,同时复杂性提升也会带来更多可能出现的问题。这里虽然只选对的不选择最好的道理,但是我们同样也不要放弃思考更优的解决方案


如果有不对的,也请多多指正。毕竟一家之见,多有偏颇。
谢谢

你可能感兴趣的:(如何在定时任务执行的过程中动态的对其进行修改)