Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现。作为一个优秀的开源调度框架,Quartz 具有功能强大,应用灵活,易于集成的特点
目前项目处于运维阶段中期,事情不多,所以有时间将自己用过的技术以及想要学习的技术整理一下,所以萌生一个自己做项目的想法,通过自己做项目将这些年使用的技术以及想要学习和深入了解的技术做一个总结,做项目第一件事就是给项目起名字,发现起名字这件事说简单也简单,说难也难,想取一个响亮点名字吧才发现自己语言有多么的匮乏,不过还好,由于最近再玩魔方,所以就用魔方的单词来作为项目的名字,反正最终目的是为了对自己这几年技术的总结,无所谓了.....
上图就是自己想到的项目模块,当然后续还会在增加,想到一些就加一些,目前只是实现了系统管理也就是权限部分还有系统监控部分的作业监控
项目简介:
项目名称:CUBE
项目使用的开发语言:Java
项目框架:Struts2+Spring+Hibernate+Maven
服务器:Tomcat
数据库:Mysql
开发工具:Eclipse
前端:easyui
目前CUBE项目的效果如图:
登陆界面:
登陆后的主界面
美工这活儿真心不好做啊,现在主界面的效果就是这样了,top和bottom还需要在美化一下,后期在做吧
下面是作业监控的最终效果:
触发器添加界面,触发器的时间规则使用CronTrigger
上边就是CUBE系统中的作业监控部分的最终实现
Quartz是Java领域最著名的开源任务调度工具。Quartz提供了极为广泛的特性如持久化任务,集群和分布式任务等,其特点如下:
- 完全由Java写成,方便集成(Spring)
- 伸缩性
- 负载均衡
- 高可用性
Quartz数据库核心表如下:
QRTZ_CALENDARS | 存储Quartz的Calendar信息 |
QRTZ_CRON_TRIGGERS | 存储CronTrigger,包括Cron表达式和时区信息 |
QRTZ_FIRED_TRIGGERS | 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息 |
QRTZ_PAUSED_TRIGGER_GRPS | 存储已暂停的Trigger组的信息 |
QRTZ_SCHEDULER_STATE | 存储少量的有关Scheduler的状态信息,和别的Scheduler实例 |
QRTZ_LOCKS | 存储程序的悲观锁的信息 |
QRTZ_JOB_DETAILS | 存储每一个已配置的Job的详细信息 |
QRTZ_SIMPLE_TRIGGERS | 存储简单的Trigger,包括重复次数、间隔、以及已触的次数 |
QRTZ_BLOG_TRIGGERS | Trigger作为Blob类型存储 |
QRTZ_TRIGGERS | 存储已配置的Trigger的信息 |
目前CUBE系统作业监控只是用到了上述加粗部分的表,其余表没有用到
Quartz的执行逻辑如下:
Quartz 任务调度的核心元素是 scheduler, trigger 和 job,其中 trigger 和 job 是任务调度的元数据, scheduler 是实际执行调度的控制器。
Quartz 中,trigger 是用于定义调度时间的元素,即按照什么时间规则去执行任务。Quartz 中主要提供了四种类型的 trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,和 NthIncludedDayTrigger。这四种 trigger 可以满足企业应用中的绝大部分需求。我们将在企业应用一节中进一步讨论四种 trigger 的功能。
Quartz 中,job 用于表示被调度的任务。主要有两种类型的 job:无状态的(stateless)和有状态的
(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。Job 主要有两种属性:volatility 和 durability,其中 volatility 表示任务是否被持久化到数据库存储,而 durability 表示在没有 trigger 关联的时候任务是否被保留。两者都是在值为 true 的时候任务被持久化或保留。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。
Quartz 中, scheduler 由 scheduler 工厂创建:DirectSchedulerFactory 或者 StdSchedulerFactory。 第二种工厂 StdSchedulerFactory 使用较多,因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。 Scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。CUBE以最常用的 StdScheduler 实现。
说了很多Quartz的理论,就不在多说了,自己目前也就理解到这里了,下面是Quartz监控的代码
applicationContext-config.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:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config /> <context:component-scan base-package="com.cube.*" /> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitName" value="cube"></property> <!-- value 对应persistence.xml中的 persistence-unit name --> </bean> <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <!-- login action --> <tx:annotation-driven transaction-manager="txManager" /> </beans>
applicationContext-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:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="applicationContextSchedulerContextKey" value="applicationContextKey" /> <property name="configLocation" value="classpath:quartz.properties" /> </bean> <bean name="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass"> <value>com.cube.service.quartz.QuartzJobService</value> </property> <property name="durability" value="true" /> </bean> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/cube?useUnicode=true&characterEncoding=UTF8" /> <property name="user" value="root" /> <property name="password" value="" /> <property name="initialPoolSize" value="10" /> <property name="minPoolSize" value="10" /> <property name="maxPoolSize" value="25" /> <property name="acquireIncrement" value="5" /> <property name="maxIdleTime" value="7200" /> </bean> </beans>
quartz.properties
org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false org.quartz.scheduler.wrapJobExecutionInUserTransaction = false org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true org.quartz.jobStore.misfireThreshold = 60000 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 = false org.quartz.jobStore.maxMisfiresToHandleAtATime=1
Schedule调度器Service
public interface SchedulerService { public List<Map<String, Object>> getQrtzTriggers(); /** * 根据 Quartz Cron Expression 调试任务 * * @param name Quartz CronTrigger名称 * @param group Quartz CronTrigger组 * @param cronExpression Quartz Cron 表达式,如 "0/10 * * ? * * *"等 */ void schedule(String name, String group, String cronExpression); /** * 根据 Quartz Cron Expression 调试任务 * * @param name Quartz CronTrigger名称 * @param group Quartz CronTrigger组 * @param cronExpression Quartz CronExpression */ void schedule(String name, String group, CronExpression cronExpression); /** * * @param name Quartz CronTrigger名称 * @param group Quartz CronTrigger组 * @param cronExpression Quartz CronExpression */ void schedule(String name, String group); void schedule(Map<String, Object> map); /** * 暂停触发器 * * @param triggerName 触发器名称 */ void pauseTrigger(String triggerName); /** * 暂停触发器 * * @param triggerName 触发器名称 * @param group 触发器组 */ void pauseTrigger(String triggerName, String group); /** * 恢复触发器 * * @param triggerName 触发器名称 */ void resumeTrigger(String triggerName); /** * 恢复触发器 * * @param triggerName 触发器名称 * @param group 触发器组 */ void resumeTrigger(String triggerName, String group); /** * 删除触发器 * * @param triggerName 触发器名称 * @return */ boolean removeTrigdger(String triggerName); /** * 删除触发器 * * @param triggerName 触发器名称 * @param group 触发器组 * @return */ boolean removeTrigdger(String triggerName, String group); }
@Transactional @Service("schedulerService") public class SchedulerServiceImpl implements SchedulerService { @Resource(name = "quartzDao") private QuartzDao quartzDao; @Autowired private Scheduler scheduler; @Autowired private JobDetail jobDetail; private static final String NULLSTRING = null; private static final Date NULLDATE = null; /** * * @param name * @param group * @param cronExpression * @see com.cube.service.SchedulerService#schedule(java.lang.String, java.lang.String, * java.lang.String) */ public void schedule(String name, String group, String cronExpression) { try { schedule(name, group, new CronExpression(cronExpression)); } catch (ParseException e) { e.printStackTrace(); } } /** * @param name * @param group * @param cronExpression * @see com.cube.service.SchedulerService#schedule(java.lang.String, java.lang.String, * org.quartz.CronExpression) */ public void schedule(String name, String group, CronExpression cronExpression) { if (name == null || name.trim().equals("")) { name = UUID.randomUUID().toString(); } CronTriggerImpl trigger = new CronTriggerImpl(); trigger.setCronExpression(cronExpression); TriggerKey triggerKey = new TriggerKey(name, group); trigger.setJobName(jobDetail.getKey().getName()); trigger.setKey(triggerKey); try { scheduler.addJob(jobDetail, true); if (scheduler.checkExists(triggerKey)) { scheduler.rescheduleJob(triggerKey, trigger); } else { scheduler.scheduleJob(trigger); } } catch (SchedulerException e) { throw new IllegalArgumentException(e); } } /** * @param map * @see com.cube.service.SchedulerService#schedule(java.util.Map) */ public void schedule(Map<String, Object> map) { // TODO Auto-generated method stub } /** * @param triggerName * @see com.cube.service.SchedulerService#pauseTrigger(java.lang.String) */ public void pauseTrigger(String triggerName) { // TODO Auto-generated method stub } /** * @param triggerName * @param group * @see com.cube.service.SchedulerService#pauseTrigger(java.lang.String, java.lang.String) */ public void pauseTrigger(String triggerName, String group) { try { scheduler.pauseTrigger(new TriggerKey(triggerName, group)); } catch (SchedulerException e) { e.printStackTrace(); } } /** * @param triggerName * @see com.cube.service.SchedulerService#resumeTrigger(java.lang.String) */ public void resumeTrigger(String triggerName) { // TODO Auto-generated method stub } /** * @param triggerName * @param group * @see com.cube.service.SchedulerService#resumeTrigger(java.lang.String, java.lang.String) */ public void resumeTrigger(String triggerName, String group) { TriggerKey triggerKey = new TriggerKey(triggerName, group); try { scheduler.resumeTrigger(triggerKey); } catch (SchedulerException e) { e.printStackTrace(); } } /** * @param triggerName * @return * @see com.cube.service.SchedulerService#removeTrigdger(java.lang.String) */ public boolean removeTrigdger(String triggerName) { return removeTrigdger(triggerName, NULLSTRING); } /** * @param triggerName * @param group * @return * @see com.cube.service.SchedulerService#removeTrigdger(java.lang.String, java.lang.String) */ public boolean removeTrigdger(String triggerName, String group) { TriggerKey triggerKey = new TriggerKey(triggerName, group); try { scheduler.pauseTrigger(triggerKey);// 停止触发器 return scheduler.unscheduleJob(triggerKey);// 移除触发器 } catch (SchedulerException e) { throw new RuntimeException(e); } } /** * @return * @see com.cube.service.SchedulerService#getQrtzTriggers() */ public List<Map<String, Object>> getQrtzTriggers() { return quartzDao.getQrtzTriggers(); } /** * @param name * @param group * @see com.cube.service.SchedulerService#schedule(java.lang.String, java.lang.String) */ public void schedule(String name, String group) { schedule(name, group, NULLSTRING); } }
QuartzDao,主要负责查询触发器的状态信息
@Repository("quartzDao") public class QuartzDao { private DataSource dataSource; @Autowired public void setDataSource(@Qualifier("dataSource") DataSource dataSource) { this.dataSource = dataSource; } // 查询Trigger public List<Map<String, Object>> getQrtzTriggers() { List<Map<String, Object>> results = getJdbcTemplate().queryForList( "select * from QRTZ_TRIGGERS order by start_time"); long val = 0; String temp = null; for (Map<String, Object> map : results) { temp = MapUtils.getString(map, "trigger_name"); if (StringUtils.indexOf(temp, "&") != -1) { map.put("display_name", StringUtils.substringBefore(temp, "&")); } else { map.put("display_name", temp); } val = MapUtils.getLongValue(map, "next_fire_time"); if (val > 0) { map.put("next_fire_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss")); } val = MapUtils.getLongValue(map, "prev_fire_time"); if (val > 0) { map.put("prev_fire_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss")); } val = MapUtils.getLongValue(map, "start_time"); if (val > 0) { map.put("start_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss")); } val = MapUtils.getLongValue(map, "end_time"); if (val > 0) { map.put("end_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss")); } map.put("trigger_state", Constant.status.get(MapUtils.getString(map, "trigger_state"))); } return results; } private JdbcTemplate getJdbcTemplate() { return new JdbcTemplate(this.dataSource); } }
JobClass
public class QuartzJobService extends QuartzJobBean { // 负责所有任务的调度 private TaskService taskService; /** * @param context * @throws JobExecutionException * @see org.springframework.scheduling.quartz.QuartzJobBean#executeInternal(org.quartz.JobExecutionContext) */ @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { String triggerName = context.getTrigger().getKey().getName(); taskService = (TaskService) getApplicationContext(context).getBean("taskService"); taskService.execute(triggerName); } private ApplicationContext getApplicationContext(final JobExecutionContext jobexecutioncontext) { try { return (ApplicationContext) jobexecutioncontext.getScheduler().getContext() .get("applicationContextKey"); } catch (SchedulerException e) { throw new RuntimeException(e); } } }
TaskService,负责所有任务的调用
public interface TaskService { /** * @param triggerName */ public void execute(String triggerName); }
@Transactional @Service("taskService") public class TaskServiceImpl implements TaskService { //此处注入自己业务的Service @Resource(name = "reportService") private ReportService reportService; /** * 根据TriggerName 调用不同的业务逻辑service * * @param triggerName * @see com.cube.service.TaskService#execute(java.lang.String) */ public void execute(String triggerName) { //此处根据触发器名称调用相应的业务逻辑Service if ("reportTigger".equalsIgnoreCase(triggerName)) { reportService.createReport(); } else { System.out.println(triggerName + ":企业业务逻辑"); } } }
下面是Action类的实现,负责添加、删除、暂停、恢复Trigger
@Controller @Scope("prototype") public class TriggerAction extends BaseAction<TriggerEntity> { /** */ private static final long serialVersionUID = -3326354633384499660L; private TriggerEntity triggerEntity = getModel(); private Map jsonMap = new HashMap(); @Resource(name = "schedulerService") private SchedulerService schedulerService; /** * 跳转到tigger 管理界面 * * @return */ public String trigger() { return "trigger"; } /** * 分页查询Trigger * * @return */ public String list() { List<Map<String, Object>> list = schedulerService.getQrtzTriggers(); jsonMap.put("rows", list); jsonMap.put("total", list.size()); return "list"; } /** * 添加触发器 * * @return */ public String save() { // 获取界面以参数 String triggerName = triggerEntity.getTrigger_name(); String cronExpression = triggerEntity.getCron(); String group = triggerEntity.getTrigger_group(); schedulerService.schedule(triggerName, group, cronExpression); jsonMap.put("flag", true); return "save"; } /** * 暂停 * * @return */ public String pause() { schedulerService.pauseTrigger(triggerEntity.getTrigger_name(), triggerEntity.getTrigger_group()); jsonMap.put("flag", true); return "pause"; } /** * Trigger恢复 * * @return */ public String play() { schedulerService.resumeTrigger(triggerEntity.getTrigger_name(), triggerEntity.getTrigger_group()); jsonMap.put("flag", true); return "play"; } /** * 删除 * * @return */ public String deleteTrigger() { schedulerService.removeTrigdger(triggerEntity.getTrigger_name(), triggerEntity.getTrigger_group()); jsonMap.put("flag", true); return "deleteTrigger"; } public Map getJsonMap() { return jsonMap; } public void setJsonMap(Map jsonMap) { this.jsonMap = jsonMap; } }
到此,作业监控功能就实现了,当然实现方式不止这一种,希望有别的实现大家可以共享一下吧,互相学习!!