关闭

Spring4+Quartz2集群动态创建任务

3858人阅读 评论(9) 收藏 举报
分类:

     公司最近需要使用Quartz集群来实现任务的动态创建和删除,之前自己只是用过配置好的单机版的,而且是定时

执行的任务,正好借这个机会深入学习一下Quartz。

     在正式开始之前,我们先来了解下,spring3.1以下的版本必须使用quartz1.x系列,3.1以上的版本才支持quartz 2.x,不然会出错。至于原因,则是spring对于quartz的支持实现,org.springframework.scheduling.quartz.CronTriggerBean继承了org.quartz.CronTrigger,在quartz1.x系列中org.quartz.CronTrigger是个类,而在quartz2.x系列中org.quartz.CronTrigger变成了接口,从而造成无法用spring的方式配置quartz的触发器(trigger)。公司现运行项目用的spring版本是4.2.2.RELEASE,所有我选取的quartz版本是2.2.1。

     最终实现的功能:

     1)项目启动时,可动态添加、删除、修改和执行定时任务;

     2)因为是集群,一个实例运行挂了,保存在数据库的定时任务可以继续执行;而且定时任务只能被其中一个实例执行。


    一、引入Maven坐标


		<!-- quartz任务调度 -->
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>2.2.1</version>
		</dependency>
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz-jobs</artifactId>
			<version>2.2.1</version>
		</dependency>


     二、创建数据库表


     在Quartz包下docs/dbTables,选择对应的数据库脚本,创建相应的数据库表即可,我用的是mysql5.6,这里有一个需要注意的地方,mysql5.5之前用的表存储引擎是MyISAM,使用的是表级锁,锁发生冲突的概率比较高,并发度低;5.6之后默认的存储引擎为InnoDB,InnoDB采用的锁机制是行级锁,并发度也较高。而quartz集群使用数据库锁的

机制来来实现同一个任务在同一个时刻只被实例执行,所以为了防止冲突,我们建表的时候要选取InnoDB作为表的存

储引擎。如下:


      


     三、配置quartz.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="http://www.springframework.org/schema/beans   
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd  
    http://www.springframework.org/schema/context     
    http://www.springframework.org/schema/context/spring-context-4.1.xsd   
    http://www.springframework.org/schema/task    
    http://www.springframework.org/schema/task/spring-task-4.1.xsd">


	<!-- quartz持久化存储 -->
	<bean name="quartzScheduler"
		class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="dataSource">
			<ref bean="dataSource" />
		</property>
		<property name="applicationContextSchedulerContextKey" value="applicationContext" />
		<!--也可以在quartz.properties中配置-->
		<property name="quartzProperties">
			<props>
				<prop key="org.quartz.scheduler.instanceName">CRMscheduler</prop>
				<prop key="org.quartz.scheduler.instanceId">AUTO</prop>
				<!-- 线程池配置 -->
				<prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
				<prop key="org.quartz.threadPool.threadCount">20</prop>
				<prop key="org.quartz.threadPool.threadPriority">5</prop>
				<prop key="org.quartz.jobStore.misfireThreshold">120000</prop>
				<!-- JobStore 配置 -->
				<prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
				<!-- 集群配置 -->
				<prop key="org.quartz.jobStore.isClustered">true</prop>
				<prop key="org.quartz.jobStore.clusterCheckinInterval">15000</prop>
				<prop key="org.quartz.jobStore.maxMisfiresToHandleAtATime">1</prop>
				<!-- 数据表设置 -->
				<prop key="org.quartz.jobStore.tablePrefix">qrtz_</prop>
				<prop key="org.quartz.jobStore.dataSource">qzDS</prop>
			</props>
		</property>

		<!--可选,QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->
		<property name="overwriteExistingJobs" value="true" />
		
		<!--设置自动启动 -->
		<property name="autoStartup" value="true" />

	</bean>

</beans>

     引入spring配置文件:

<import resource="quartz.xml"/>


     配置文件中SchedulerFactoryBean中的dataSource用指定spring中之前配置好的就可以。


     四、实现Job接口


     我们在动态添加任务的时候需要用到这个类。


public class AllPushNotifyJob implements Job {
    private static final Log logger = LogFactory.getLog(AllPushNotifyJob.class);

    private AllPushMessageService allPushMessageService;

    public AllPushNotifyJob() {
        allPushMessageService = (AllPushMessageService) SpringContext.getBeanByName("allPushMessageService");
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap();

        AllPushMessage  allPushMessage = (AllPushMessage) dataMap.get("allPushMessage");

        Date expectTriggerTime = allPushMessage.getPush_time();

        Date realTriggerTime = new Date();
        logger.info("execute category notify job with expect trigger time:" + DateUtils.format(expectTriggerTime, "yyyy-MM-dd HH:mm:ss Z"));
        logger.info("real notify time:" + DateUtils.format(realTriggerTime, "yyyy-MM-dd HH:mm:ss Z"));
        allPushMessageService.enforceAllPush(allPushMessage);
    }
}


     五、JobScheduler管理任务


@Service
public class AllPushJobScheduler {
    private static final Log logger = LogFactory.getLog(AllPushJobScheduler.class);

    public void start() {
        logger.info("start category update notify scheduler");
        schedulerFactoryBean.start();

    }

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    /**
     * 添加任务
     * @param allPushMessage
     */
	public void scheduleNotifyJob(AllPushMessage allPushMessage) {
		if (allPushMessage.getPush_time().compareTo(new Date()) < 0) {
			allPushMessage.setPush_time(DateUtils.addSeconds(new Date(), 10));
		}

		Scheduler scheduler = schedulerFactoryBean.getScheduler();
		JobKey jobKey = getJobKey(allPushMessage);
		try {
			if (scheduler.checkExists(jobKey)) {
				logger.info("all push job existed!:" + jobKey.getName());
				return;
			}
		} catch (SchedulerException e) {
			logger.error("get exception:" + e.getMessage(), e);
		}

		logger.info("schedule all push job at:"+allPushMessage.getPush_time() +" with job name pushID" + jobKey.getName());
		JobDataMap jobData = new JobDataMap();
		jobData.put("allPushMessage", allPushMessage);

		JobDetail notifyJob = JobBuilder.newJob(AllPushNotifyJob.class)
				.setJobData(jobData).withIdentity(jobKey).build();
		SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
		trigger.setName("trigger-" + jobKey.getName());
		trigger.setJobDetail(notifyJob);
		trigger.setStartTime(allPushMessage.getPush_time());
		trigger.setRepeatCount(0);
		trigger.afterPropertiesSet();

		try {
			schedulerFactoryBean.getScheduler().scheduleJob(notifyJob,
					trigger.getObject());
		} catch (SchedulerException e) {
			logger.error("get exception when executing quartz job" + e);
		}

	}


    /**
     * 删除任务
     * @param allPushMessage
     * @throws Exception
     */
    public void deleteJob(AllPushMessage allPushMessage)throws Exception{

        try{
            //删除定时任务时   先暂停任务,然后再删除
            JobKey jobKey = getJobKey(allPushMessage);
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            scheduler.pauseJob(jobKey);
            scheduler.deleteJob(jobKey);
        }catch(Exception e){
            System.out.println("删除定时任务失败"+e);
            throw new Exception("删除定时任务失败");
        }
    }


    /**
     * 获取jobKey
     * @param allPushMessage
     * @return
     */
    public JobKey getJobKey(AllPushMessage allPushMessage) {

        return JobKey.jobKey(String.valueOf(allPushMessage.getPush_id()));
    }

    /**
     * 更新定时任务
     * @param
     * @param
     * @throws Exception
     */
    public void updateJob(AllPushMessage allPushMessage)throws Exception{
        try {
            TriggerKey triggerKey =getTriggerKey(String.valueOf(allPushMessage.getPush_id()));
            Scheduler scheduler = schedulerFactoryBean.getScheduler();

            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

            // 按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).build();

            // 按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (SchedulerException e) {
            System.out.println("更新定时任务失败"+e);
            throw new Exception("更新定时任务失败");
        }
    }

    /**
     * 获取触发器key
     *
     * @param
     * @param
     * @return
     */
    public static TriggerKey getTriggerKey(String jobkey) {

        return TriggerKey.triggerKey(jobkey);
    }



    /**
     * 暂停定时任务
     * @param allPushMessage
     * @throws Exception
     */
    public void pauseJob(AllPushMessage allPushMessage) throws Exception {

        JobKey jobKey = JobKey.jobKey(String.valueOf(allPushMessage.getPush_id()));
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            System.out.println("暂停定时任务失败"+e);
            throw new Exception("暂停定时任务失败");
        }
    }

    /**
     * 恢复任务
     * @param
     * @param
     * @param
     * @throws Exception
     */
    public void resumeJob(AllPushMessage allPushMessage) throws Exception {

        JobKey jobKey = JobKey.jobKey(String.valueOf(allPushMessage.getPush_id()));
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            System.out.println("恢复定时任务失败"+e);
            throw new Exception("恢复定时任务失败");
        }
    }

    /**
     * 运行一次任务
     * @param allPushMessage
     * @throws Exception
     */
    public void runOnce(AllPushMessage allPushMessage) throws Exception {
        JobKey jobKey = JobKey.jobKey(String.valueOf(allPushMessage.getPush_id()));
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            System.out.println("运行任务失败"+e);
            throw new Exception("运行一次定时任务失败");
        }
    }

}


     最后,在需要你的代码中调用添加、删除、修改任务的方法就可以了,喵




2
1

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:599085次
    • 积分:10278
    • 等级:
    • 排名:第1624名
    • 原创:170篇
    • 转载:2篇
    • 译文:0篇
    • 评论:2714条
    博客专栏
    文章分类
    最新评论