定时器引发的问题
前言
Github:https://github.com/HealerJean
1、关于定时器引起的问题:
1.1、定时器执行期间数据覆盖
如果当我们在定时器执行的时候,如果查出来大量实体数据,由于事物的隔离性的存在,而且针对整个数据采用了事物,只有全部提交之后才会进行整体修改以及覆盖数据,这样就会造成我们在定时器执行期间人工修改过的数据在定时器执行期间看似被修改,但是定时器执行之后会被定时器执行所覆盖,导致我们并没有成功修改
解决方案
-
定时器执行的时候,不要使用save(Jpa的save方法会进行缓存,不会锁行)更新数据,而是使用update语句悲观锁,将某一行锁住 ,这样在定时器执行期间就不会发生,被人工修改的情况了,人工修改不会发生,因为已经被锁住了,还怎么修改 。
-
如果查询出是根据查询出大量的主键,然后分别对该主键执行的定时器任务,可以将这个主键锁对应的任务放在一个事务中,这样一个主键,一个主键的事务执行,不会发生大量数据在定时器执行完毕之后才会执行事务。
@Scheduled(cron = "0 */5 * * * ?")
public void queryContract () {
log.info("查询未签署的合同的状态");
boolean lockResource = cacheService.lock("queryContract",1,TimeUnit.MINUTES);
if (!lockResource) {
log.info("【查询未签署的合同的状态】任务抢锁失败");
return;
}
ContractDTO contract = new ContractDTO();
contract.setStatusList(
Arrays.asList(BusinessEnum.ContractSignStatus.READY.getCode(),
BusinessEnum.ContractSignStatus.PART.getCode()));
PageDTO<ContractDTO> page = contractService.contractRecordPage(contract);
List<ContractDTO> datas = page.getDatas();
if(datas != null && !datas.isEmpty()){
datas.forEach(item -> {
try {
contractService.updateSignResult(item);
}catch (ScfException e){
String msg = e.getMsg();
log.info("定时任务-查询未签署的合同,ID:{},错误原因:{}", item.getContractId(), msg);
}
log.info("定时任务-查询未签署的合同,ID:{}", item.getContractId());
});
}
log.info("定时任务-查询未签署的合同,完成记录数:{}",datas == null ? 0 : datas.size());
}
1.2、定时器任务重复执行
解决方案
方案一: 单节点
将定时器专门作为一个定时器任务取出来,放在一个单节点的机器上去,这样只有一个定时器,肯定任务不会重复执行,但是一旦定时器挂掉就完蛋了 ,所以推荐使用方案二
方案二: 分布式锁
boolean lockResource = cacheService.lock("queryContract",1,TimeUnit.MINUTES);
if (!lockResource) {
log.info("【查询未签署的合同的状态】任务抢锁失败");
return;
}
2、定时器乐观锁的使用
3.1、时间作为乐观锁
场景:隔天执行定时任务
举例;每天凌晨1点会执行一条定时任务。
CREATE TABLE `t_lock` (
`key` varchar(15) NOT NULL COMMENT '定时任务Key',
`utime` bigint(20) NOT NULL COMMENT '用于乐观锁的时间',
UNIQUE KEY `lockUniq` (`key`,`utime`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
每次执行任务之前,先执行下面的sql(传入的是当天的日期,这样就保证了多个几点中只有一个节点能够返回记录行 1 ),其他返回0的数据不执行
update `t_lock` set `utime` = #{utime} where `key`=#{key} and `utime`<#{utime}
3.2、版本号
其实和上面的也是一样的,只不是是需要我们先把版本号version
取出来
update test set
version = #{version,jdbcType=VARCHAR},
where id = #{id,jdbcType=BIGINT} and version = #{version,jdbcType=VARCHAR}