利用Oracle行锁,处理集群中定时任务,在多进程情况下的重复执行

    在集群上,每台主机部署的代码一样,会出现定时任务重复的情况,考虑过三种实现。

    第一种方式采用过固定一台IP的方式,牺牲了其他几台主机的作用;

    第二种是在分配任务时加了随机时间,先休眠结束的任务,抢到任务并将job的状态改成RUNNING,确实每台主机都用到了,但这样做有两个问题,首先,不能完全避免多个地方同时结束Thread.sleep(),另外增加了处理的时间间隔。

    下面这个方式是通过让集群每个主机节点争夺数据库行锁来实现的。

第一步:将所有定时任务配置在job表


利用Oracle行锁,处理集群中定时任务,在多进程情况下的重复执行_第1张图片

关键字段:JOB_NAME任务名称、JOB_RUN_STATUS任务状态、IP_ADDRESS执行的IP

第二步:执行任务前,加入判断 JobService.canExecute(job)

执行定时任务前,需判断本任务是否可执行

@Scheduled(fixedDelayString = "${report.task.time}")

public void runBorrowDataUpdateJob() {

    Map job =new HashMap();

    job.put("jobName", "generateJob");

    try {

        //获取主机IP

        InetAddress address;

        address = InetAddress.getLocalHost();

        String currentIP = address.getHostAddress();

        job.put("ipAddress", currentIP);

        //等待状态则执行,把本机IP记录在sys_batch_job

        if(JobService.canExecute(job)){    //判断是否可执行

                        //目标job

                        generateService.generateReport();    

                        job.put("jobStatus", "ONWAIT");

                        job.put("beginTime", null);

                        job.put("runCount", "调用次数+1");

                        job.put("endTime", "记录结束时间");

                        JobService.updateJob(job);

        }

} catch (Exception e) {

        job.put("jobStatus", "ONWAIT");

        job.put("beginTime", null);

        job.put("runCount", "调用次数+1");

        job.put("endTime", "记录结束时间");

        JobService.updateJob(job); //如果出现异常,当前job置于等待状态

        LoggerUtil.info("GenerateJob失败",e);

}

第三步:判断是否可执行

①Mapping

    JobService.canExecute(job)中需要从库表中查询当前job是否可执行,当集群中一台主机占据这个行锁,其他主机只能等待,加了nowait则抛出(Caused by: java.sql.SQLException: ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired)直接返回,getRunningJob对应的mapping如下

②SERVICE

    由于链接默认是自动commit的,需要关闭自动提交事务,最后在finally中记得sqlSession.commit和close();

    canExecute()对应的代码如下

@Transactional

@Override

public Boolean canExecute(Map job){ 

        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

        SqlSession sqlSession=null;

        try {

        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/report/jobMapper.xml"));

        factoryBean.setDataSource(institutionDataSource);

        sqlSession = factoryBean.getObject().openSession(false);    //手动事务(重点)

        //不在RUNNING状态,启动服务,更改IP、状态成RUNNING

        if (null != getRunningJob(job.get("jobName")+"")){      //调用①部分,加锁

                job.put("jobStatus", "RUNNING");

                job.put("beginTime", "记录起始时间");

                //commit后,当前JOB更新成RUNNING状态,其他进程无法获取当前数据行

                jobDao.updateJob(job); 

                return true;

        }

//在当前job在RUNNING状态

        LoggerUtil.info(job.get("jobName")+"已被其他服务器执行");

        return false;

        } catch (IOException e) {

                LoggerUtil.error("读取xml文件出错", e);;

        }  catch (Exception e) {

                LoggerUtil.error("表锁住了", e);

        } finally{

                sqlSession.commit(true);

                sqlSession.close();

        }

        return false;

}

第四步: 验证效果


某个时间

利用Oracle行锁,处理集群中定时任务,在多进程情况下的重复执行_第2张图片

一段时间后

执行次数和IP发生了变化,且定时任务数据在同一时间没有出现重复的情况

你可能感兴趣的:(利用Oracle行锁,处理集群中定时任务,在多进程情况下的重复执行)