您的位置:首页 > 编程语言 > Java开发

4-SSM框架下定时任务调度的实现

2017-12-28 14:05 405 查看
这是SSM框架整合的第4篇文章了,这两天基本都是每天整理2篇的节奏,真的是闲得蛋疼了我。不过这几篇文章都是自己动手实践过程中记录的,期间也遇到一些问题,参考了一些前辈的实现方式。在这里感谢前辈们的无私奉献,才换来现在我们的学习成本才能这么低,谢谢大家~

回归话题,今天想跟大家分享的是在ssm框架下实现任务调度的实现方式。因为在我们实际的项目中,很多时候会使用到一些定时器,比如邮件汇总、报表汇总这样的业务场景。那在ssm框架下有什么方式实现呢?本人常用的有2种,这里当做记录,和大家一起分享下。

spring task

spring task是spring 3.0版本开始,框架自带的任务调度开发工具。可以通过注解或配置的方式简单快速地将一个普通类转为定时任务,只要在spring-context.xml中启用spring task,简单的配置即可,如:

<!-- 启用spring task -->
<task:annotation-driven />


普通类:
public class DemoTask {
public void doSomething() {
//想要做的事情
}
}


@Component
public class DemoTask {
@Scheduled(cron="0 0/1 * * * ?") //cron表达式
public void doSomething() {
//想要做的事情
}
}


Quartz

Quartz是一个开源的框架,实现的功能也是任务调度。它采用工厂模式进行任务调度,主要有3个概念:JobDetail、Tigger、SchedulerFactory,个人简单的理解就是:什么事情(JobDetail)在什么时候(Tigger)有哪位大爷(SchedulerFactory)调配着做。当然,Qaurtz的功能十分强大,还能够配合mysql数据库实现集群化。这里我们就基于一个需求来实现SSM跟Quartz的整合。

需求

在我们的实际项目中经常会遇到这样的一种情况,我在业务代码里面设定了一个定时任务,单机跑起来没问题吧,都可以执行。但是蛋疼的是,我们很多时候项目是采用集群方式部署的,假设部署了A、B、C三台服务器,那业务代码肯定是一样的,这样定时任务就会各自独立跑一遍吧,假如这个定时任务操作了数据库,写数据进去,是不是有可能因为同样的流程并发着跑了导致有脏数据了。很简单的想法就是我就留一台机子跑定时任务嘛,其他两台去掉相同的业务代码不就可以了?当然可以,可是部署是不是就很麻烦了,还有一个就是哪天机子宕掉了,定时任务不就没有了。所以,基于上述的需要,我们需要的是,集群内服务器的代码肯定要一样的,然后我们的定时任务每次只能有一台机子跑,谁跑我们不关心嘛。这时我们就基于Quartz实现下,把它整合到SSM框架去。

ssm整合Quartz

首先,还是在pom.xml添加相关的依赖。

<!-- 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>


上面我们提到过实现集群需要mysql的配合,我们要讲下面的11张表结构导入我们的数据库中。

SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
--  Table structure for `QRTZ_BLOB_TRIGGERS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_BLOB_TRIGGERS`;
CREATE TABLE `QRTZ_BLOB_TRIGGERS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL,
`BLOB_DATA` blob,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
CONSTRAINT `QRTZ_BLOB_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_CALENDARS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_CALENDARS`;
CREATE TABLE `QRTZ_CALENDARS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`CALENDAR_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`CALENDAR` blob NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`CALENDAR_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_CRON_TRIGGERS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_CRON_TRIGGERS`;
CREATE TABLE `QRTZ_CRON_TRIGGERS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL,
`CRON_EXPRESSION` varchar(200) COLLATE utf8_bin NOT NULL,
`TIME_ZONE_ID` varchar(80) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
CONSTRAINT `QRTZ_CRON_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_FIRED_TRIGGERS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_FIRED_TRIGGERS`;
CREATE TABLE `QRTZ_FIRED_TRIGGERS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`ENTRY_ID` varchar(95) COLLATE utf8_bin NOT NULL,
`TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL,
`INSTANCE_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`FIRED_TIME` bigint(13) NOT NULL,
`SCHED_TIME` bigint(13) NOT NULL,
`PRIORITY` int(11) NOT NULL,
`STATE` varchar(16) COLLATE utf8_bin NOT NULL,
`JOB_NAME` varchar(200) COLLATE utf8_bin DEFAULT NULL,
`JOB_GROUP` varchar(200) COLLATE utf8_bin DEFAULT NULL,
`IS_NONCONCURRENT` varchar(1) COLLATE utf8_bin DEFAULT NULL,
`REQUESTS_RECOVERY` varchar(1) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`SCHED_NAME`,`ENTRY_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_JOB_DETAILS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_JOB_DETAILS`;
CREATE TABLE `QRTZ_JOB_DETAILS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`JOB_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`JOB_GROUP` varchar(200) COLLATE utf8_bin NOT
1358c
NULL,
`DESCRIPTION` varchar(250) COLLATE utf8_bin DEFAULT NULL,
`JOB_CLASS_NAME` varchar(250) COLLATE utf8_bin NOT NULL,
`IS_DURABLE` varchar(1) COLLATE utf8_bin NOT NULL,
`IS_NONCONCURRENT` varchar(1) COLLATE utf8_bin NOT NULL,
`IS_UPDATE_DATA` varchar(1) COLLATE utf8_bin NOT NULL,
`REQUESTS_RECOVERY` varchar(1) COLLATE utf8_bin NOT NULL,
`JOB_DATA` blob,
PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_LOCKS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_LOCKS`;
CREATE TABLE `QRTZ_LOCKS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`LOCK_NAME` varchar(40) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_PAUSED_TRIGGER_GRPS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_PAUSED_TRIGGER_GRPS`;
CREATE TABLE `QRTZ_PAUSED_TRIGGER_GRPS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_SCHEDULER_STATE`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_SCHEDULER_STATE`;
CREATE TABLE `QRTZ_SCHEDULER_STATE` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`INSTANCE_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`LAST_CHECKIN_TIME` bigint(13) NOT NULL,
`CHECKIN_INTERVAL` bigint(13) NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`INSTANCE_NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_SIMPLE_TRIGGERS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_SIMPLE_TRIGGERS`;
CREATE TABLE `QRTZ_SIMPLE_TRIGGERS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL,
`REPEAT_COUNT` bigint(7) NOT NULL,
`REPEAT_INTERVAL` bigint(12) NOT NULL,
`TIMES_TRIGGERED` bigint(10) NOT NULL,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
CONSTRAINT `QRTZ_SIMPLE_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_SIMPROP_TRIGGERS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_SIMPROP_TRIGGERS`;
CREATE TABLE `QRTZ_SIMPROP_TRIGGERS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL,
`STR_PROP_1` varchar(512) COLLATE utf8_bin DEFAULT NULL,
`STR_PROP_2` varchar(512) COLLATE utf8_bin DEFAULT NULL,
`STR_PROP_3` varchar(512) COLLATE utf8_bin DEFAULT NULL,
`INT_PROP_1` int(11) DEFAULT NULL,
`INT_PROP_2` int(11) DEFAULT NULL,
`LONG_PROP_1` bigint(20) DEFAULT NULL,
`LONG_PROP_2` bigint(20) DEFAULT NULL,
`DEC_PROP_1` decimal(13,4) DEFAULT NULL,
`DEC_PROP_2` decimal(13,4) DEFAULT NULL,
`BOOL_PROP_1` varchar(1) COLLATE utf8_bin DEFAULT NULL,
`BOOL_PROP_2` varchar(1) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
CONSTRAINT `QRTZ_SIMPROP_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

-- ----------------------------
--  Table structure for `QRTZ_TRIGGERS`
-- ----------------------------
DROP TABLE IF EXISTS `QRTZ_TRIGGERS`;
CREATE TABLE `QRTZ_TRIGGERS` (
`SCHED_NAME` varchar(120) COLLATE utf8_bin NOT NULL,
`TRIGGER_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`TRIGGER_GROUP` varchar(200) COLLATE utf8_bin NOT NULL,
`JOB_NAME` varchar(200) COLLATE utf8_bin NOT NULL,
`JOB_GROUP` varchar(200) COLLATE utf8_bin NOT NULL,
`DESCRIPTION` varchar(250) COLLATE utf8_bin DEFAULT NULL,
`NEXT_FIRE_TIME` bigint(13) DEFAULT NULL,
`PREV_FIRE_TIME` bigint(13) DEFAULT NULL,
`PRIORITY` int(11) DEFAULT NULL,
`TRIGGER_STATE` varchar(16) COLLATE utf8_bin NOT NULL,
`TRIGGER_TYPE` varchar(8) COLLATE utf8_bin NOT NULL,
`START_TIME` bigint(13) NOT NULL,
`END_TIME` bigint(13) DEFAULT NULL,
`CALENDAR_NAME` varchar(200) COLLATE utf8_bin DEFAULT NULL,
`MISFIRE_INSTR` smallint(2) DEFAULT NULL,
`JOB_DATA` blob,
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
KEY `SCHED_NAME` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
CONSTRAINT `QRTZ_TRIGGERS_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT;

SET FOREIGN_KEY_CHECKS = 1;


然后配置下数据库的信息,写入到quartz.properties。

org.quartz.scheduler.instanceName = quartzScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.txIsolationLevelSerializable = false
org.quartz.jobStore.useProperties = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread =true


接着我们要基于QuartzJobBean这个抽象接口实现我们的具体要调度的任务,但是我们又不想我们的任务继承任何类或接口实现,Quartz提供了MethodInvokingJobDetailFactoryBean帮我们实现了这一点。

DemoTask.java
public class DemoTask {
public void execute() {
System.out.println(Util.dateFormat("集群化定时任务...");
}
}


spring-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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="demoTask" class="com.maven4web.quartz.DemoTask"></bean>
<bean id="demo" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" value="demoTask"></property>
<!-- 任务类中需要执行的方法 -->
<property name="targetMethod" value="execute"></property>
<!-- 上一次未执行完成的,要等待有再执行 -->
<property name="concurrent" value="false"></property>
</bean>
<bean id="demoTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="demo" />
<property name="cronExpression" value="0 0/1 * * * ?" />
</bean>
<!-- 以下是Quartz定时调度配制 -->
<bean id="schedulerFactory"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:quartz.properties" />
<property name="dataSource" ref="dataSource" />
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
<!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->
<property name="overwriteExistingJobs" value="true" />
<!-- 设置自动启动 -->
<property name="autoStartup" value="true" />
<property name="startupDelay" value="5" />
<property name="triggers">
<list>
<ref bean="demoTrigger" />
</list>
</property>
</bean>
</beans>


貌似大功告成了吧,启动项目看看,结果发现报错了。刚开始接触的时候也觉得一头雾水,不知道怎么办,后面看了几位前辈的文章介绍才知道Spring从2.0.2开始便不再支持Quartz集群。具体表现 在Quartz 的 Task 实例化被写入进入数据库后,会产生 Serializable 的错误。



那如何处理这个问题呢?参考了网络上大家的一些做法,就是直接实现JobDetail,自己实现MethodInvokingJobDetailFactoryBean的逻辑,MethodInvokingJobDetailFactoryBean 的思想:是在executeInternal时将通过反射获取bean的方法,调用invoke执行该方法,配合jobClass和jobDataAsMap实现注入。那我们修改下代码按这思路处理下:

JobDetailBean.java
public class JobDetailBean extends QuartzJobBean {
private String targetObject;
private String targetMethod;
private ApplicationContext ctx;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {

try {
Object bean = ctx.getBean(targetObject);
Method m = bean.getClass().getDeclaredMethod(targetMethod);
m.invoke(bean);
} catch (Exception e) {
e.printStackTrace();
}
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.ctx = applicationContext;
}
public void setTargetObject(String targetObject) {
this.targetObject = targetObject;
}
public void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
}


spring-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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 
<!-- 实例 -->
<bean id="demo"
class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.maven4web.quartz.JobDetailBean" />
<property name="durability" value="true" />
<property name="description" value="demo实例..." />
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="demoTask" />
<entry key="targetMethod" value="execute" />
</map>
</property>
</bean>

<bean id="demoTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="demo" />
<property name="cronExpression" value="0 0/1 * * * ?" />
</bean>

<!-- 以下是Quartz定时调度配制 -->
<bean id="schedulerFactory"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:quartz.properties" />
<property name="dataSource" ref="dataSource" />
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
<!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 -->
<property name="overwriteExistingJobs" value="true" />
<!-- 设置自动启动 -->
<property name="autoStartup" value="true" />
<property name="startupDelay" value="5" />
<property name="triggers">
<list>
<ref bean="demoTrigger" />
</list>
</property>
</bean>
</beans>


ok,我们启动服务器再看看效果。服务器正常启动了,1分钟之后控制台也打印了我们写在定时器里面的内容。



那至于在集群环境下是否能够达到我们想要的效果,大家可以把项目部署到2、3台服务器上试试。本人之前在实际的项目中已经使用了这种方式实现了,就不多此一举了。Over~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息