HOW-TO:具有MySQL的JEE应用程序中具有集群功能的Quartz Scheduler
Quartz Scheduler是Java世界中最流行的調度庫之一。 過去,我主要在Spring應用程序中使用Quartz。 最近,我一直在研究要在云中部署的JBoss 7.1.1上運行的JEE 6應用程序中的調度。 我考慮的一種選擇是Quartz Scheduler,因為它提供了與數據庫的集群。 在本文中,我將展示在JEE應用程序中配置Quartz并在JBoss 7.1.1或WildFly 8.0.0上運行它,使用MySQL作為作業存儲以及利用CDI在作業中使用依賴注入是多么容易。 所有這些都將在IntelliJ中完成。 讓我們開始吧。
創建Maven項目
我使用org.codehaus.mojo.archetypes:webapp-javaee6原型來引導應用程序,然后我稍微修改了pom.xml 。 我還添加了slf4J依賴項,因此生成的pom.xml如下所示:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>pl.codeleak</groupId><artifactId>quartz-jee-demo</artifactId><version>1.0</version><packaging>war</packaging><name>quartz-jee-demo</name><properties><endorsed.dir>${project.build.directory}/endorsed</endorsed.dir><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>javax</groupId><artifactId>javaee-api</artifactId><version>6.0</version><scope>provided</scope></dependency><!-- Logging --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.7</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.7.7</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.3.2</version><configuration><source>1.7</source><target>1.7</target><compilerArguments><endorseddirs>${endorsed.dir}</endorseddirs></compilerArguments></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-war-plugin</artifactId><version>2.1.1</version><configuration><failOnMissingWebXml>false</failOnMissingWebXml></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><version>2.1</version><executions><execution><phase>validate</phase><goals><goal>copy</goal></goals><configuration><outputDirectory>${endorsed.dir}</outputDirectory><silent>true</silent><artifactItems><artifactItem><groupId>javax</groupId><artifactId>javaee-endorsed-api</artifactId><version>6.0</version><type>jar</type></artifactItem></artifactItems></configuration></execution></executions></plugin></plugins></build></project>接下來是將項目導入到IDE。 在我的情況下,這是IntelliJ,并使用JBoss 7.1.1創建運行配置。
值得注意的是,在運行配置中的VM Options中,我添加了兩個變量:
-Djboss.server.default.config=standalone-custom.xml -Djboss.socket.binding.port-offset=100
standalone-custom.xml是標準standalone.xml的副本,因為需要修改配置(請參見下文)。
配置JBoss服務器
在我的演示應用程序中,我想將MySQL數據庫與Quartz一起使用,因此需要將MySQL數據源添加到我的配置中。 這可以通過兩個步驟快速完成。
添加驅動程序模塊
我創建了一個文件夾JBOSS_HOME/modules/com/mysql/main 。 在這個文件夾中,我添加了兩個文件: module.xml和mysql-connector-java-5.1.23.jar 。 模塊文件如下所示:
<?xml version="1.0" encoding="UTF-8"?> <module xmlns="urn:jboss:module:1.0" name="com.mysql"> <resources> <resource-root path="mysql-connector-java-5.1.23.jar"/> </resources> <dependencies> <module name="javax.api"/> </dependencies> </module>配置數據源
在datasources子系統的standalone-custom.xml文件中,我添加了一個新的數據源:
<datasource jta="false" jndi-name="java:jboss/datasources/MySqlDS" pool-name="MySqlDS" enabled="true" use-java-context="true"><connection-url>jdbc:mysql://localhost:3306/javaee</connection-url><driver>com.mysql</driver><security><user-name>jeeuser</user-name><password>pass</password></security> </datasource>和驅動程序:
<drivers><driver name="com.mysql" module="com.mysql"/> </drivers>注意:就本演示而言,數據源不是由JTA管理的,以簡化配置。
使用集群配置Quartz
我使用官方教程通過集群配置Quarts: http : //quartz-scheduler.org/documentation/quartz-2.2.x/configuration/ConfigJDBCJobStoreClustering
將Quartz依賴項添加到pom.xml
<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.properties添加到src/main/resources
#============================================================================ # Configure Main Scheduler Properties #============================================================================org.quartz.scheduler.instanceName = MyScheduler org.quartz.scheduler.instanceId = AUTO#============================================================================ # Configure ThreadPool #============================================================================org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 1#============================================================================ # Configure JobStore #============================================================================org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.useProperties = false org.quartz.jobStore.dataSource=MySqlDSorg.quartz.jobStore.isClustered = true org.quartz.jobStore.clusterCheckinInterval = 5000org.quartz.dataSource.MySqlDS.jndiURL=java:jboss/datasources/MySqlDS創建供Quartz使用MySQL表
可以在Quartz發行版中找到該模式文件: quartz-2.2.1\docs\dbTables 。
演示代碼
完成配置后,我想檢查Quartz是否正常工作,因此我創建了一個沒有作業和觸發器的調度程序。
package pl.codeleak.quartzdemo;import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.TriggerKey; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.GroupMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory;import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.ejb.Singleton; import javax.ejb.Startup;@Startup @Singleton public class SchedulerBean {private Logger LOG = LoggerFactory.getLogger(SchedulerBean.class);private Scheduler scheduler;@PostConstructpublic void scheduleJobs() {try {scheduler = new StdSchedulerFactory().getScheduler(); scheduler.start();printJobsAndTriggers(scheduler);} catch (SchedulerException e) {LOG.error("Error while creating scheduler", e);}}private void printJobsAndTriggers(Scheduler scheduler) throws SchedulerException {LOG.info("Quartz Scheduler: {}", scheduler.getSchedulerName());for(String group: scheduler.getJobGroupNames()) {for(JobKey jobKey : scheduler.getJobKeys(GroupMatcher.<JobKey>groupEquals(group))) {LOG.info("Found job identified by {}", jobKey);}}for(String group: scheduler.getTriggerGroupNames()) {for(TriggerKey triggerKey : scheduler.getTriggerKeys(GroupMatcher.<TriggerKey>groupEquals(group))) {LOG.info("Found trigger identified by {}", triggerKey);}}}@PreDestroypublic void stopJobs() {if (scheduler != null) {try {scheduler.shutdown(false);} catch (SchedulerException e) {LOG.error("Error while closing scheduler", e);}}} }運行應用程序時,您應該能夠從Quartz中看到一些調試信息:
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.NOT STARTED.Currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 1 threads.Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is clustered.讓Quartz利用CDI
在Quartz中,作業必須實現org.quartz.Job接口。
package pl.codeleak.quartzdemo;import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException;public class SimpleJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {// do something} }然后使用JobBuilder創建一個Job:
JobKey job1Key = JobKey.jobKey("job1", "my-jobs"); JobDetail job1 = JobBuilder.newJob(SimpleJob.class).withIdentity(job1Key).build();在我的示例中,我需要將EJB注入到我的作業中,以便重新使用現有的應用程序邏輯。 因此,實際上,我需要注入EJB參考。 Quartz如何做到這一點? 簡單。 Quartz Scheduler有一種提供JobFactory的方法,該方法將負責創建Job實例。
package pl.codeleak.quartzdemo;import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.spi.JobFactory; import org.quartz.spi.TriggerFiredBundle;import javax.enterprise.inject.Any; import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.inject.Named;public class CdiJobFactory implements JobFactory {@Inject@Anyprivate Instance<Job> jobs;@Overridepublic Job newJob(TriggerFiredBundle triggerFiredBundle, Scheduler scheduler) throws SchedulerException {final JobDetail jobDetail = triggerFiredBundle.getJobDetail();final Class<? extends Job> jobClass = jobDetail.getJobClass();for (Job job : jobs) {if (job.getClass().isAssignableFrom(jobClass)) {return job;}}throw new RuntimeException("Cannot create a Job of type " + jobClass);} }到目前為止,所有作業都可以使用依賴項注入和注入其他依賴項,包括EJB。
package pl.codeleak.quartzdemo.ejb;import org.slf4j.Logger; import org.slf4j.LoggerFactory;import javax.ejb.Stateless;@Stateless public class SimpleEjb {private static final Logger LOG = LoggerFactory.getLogger(SimpleEjb.class);public void doSomething() {LOG.info("Inside an EJB");} }package pl.codeleak.quartzdemo;import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import pl.codeleak.quartzdemo.ejb.SimpleEjb;import javax.ejb.EJB; import javax.inject.Named;public class SimpleJob implements Job {@EJB // @Inject will work tooprivate SimpleEjb simpleEjb;@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {simpleEjb.doSomething();} }最后一步是修改SchedulerBean:
package pl.codeleak.quartzdemo;import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.spi.JobFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory;import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.ejb.Singleton; import javax.ejb.Startup; import javax.inject.Inject;@Startup @Singleton public class SchedulerBean {private Logger LOG = LoggerFactory.getLogger(SchedulerBean.class);private Scheduler scheduler;@Injectprivate JobFactory cdiJobFactory;@PostConstructpublic void scheduleJobs() {try {scheduler = new StdSchedulerFactory().getScheduler();scheduler.setJobFactory(cdiJobFactory);JobKey job1Key = JobKey.jobKey("job1", "my-jobs");JobDetail job1 = JobBuilder.newJob(SimpleJob.class).withIdentity(job1Key).build();TriggerKey tk1 = TriggerKey.triggerKey("trigger1", "my-jobs");Trigger trigger1 = TriggerBuilder.newTrigger().withIdentity(tk1).startNow().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10)).build();scheduler.scheduleJob(job1, trigger1);scheduler.start();printJobsAndTriggers(scheduler);} catch (SchedulerException e) {LOG.error("Error while creating scheduler", e);}}private void printJobsAndTriggers(Scheduler scheduler) throws SchedulerException {// not changed}@PreDestroypublic void stopJobs() {// not changed} }注意:在運行應用程序之前,將bean.xml文件添加到WEB-INF目錄。
<?xml version="1.0" encoding="UTF-8"?> <beansxmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"bean-discovery-mode="all"></beans>現在,您可以啟動服務器并觀察結果。 首先,創建作業和觸發器:
12:08:19,592 INFO (MSC service thread 1-3) Quartz Scheduler: MyScheduler 12:08:19,612 INFO (MSC service thread 1-3) Found job identified by my-jobs.job1 12:08:19,616 INFO (MSC service thread 1-3) Found trigger identified by m我們的工作正在運行(大約每10秒運行一次):
12:08:29,148 INFO (MyScheduler_Worker-1) Inside an EJB 12:08:39,165 INFO (MyScheduler_Worker-1) Inside an EJB還要查看Quartz表內部,您將看到其中已填充數據。
測試應用
我要檢查的最后一件事是在多個實例中如何觸發作業。 為了進行測試,我只是在IntelliJ中克隆了兩次服務器配置,并為每個新副本分配了不同的端口偏移。
我需要做的其他更改是修改作業和觸發器的創建。 由于所有Quartz對象都存儲在數據庫中,因此創建相同的作業和觸發器(使用相同的鍵)將引發異常:
我需要更改代碼,以確保如果作業/觸發器存在,請對其進行更新。 此測試的scheduleJobs方法的最終代碼為同一作業注冊了三個觸發器。
@PostConstruct public void scheduleJobs() {try {scheduler = new StdSchedulerFactory().getScheduler();scheduler.setJobFactory(cdiJobFactory);JobKey job1Key = JobKey.jobKey("job1", "my-jobs");JobDetail job1 = JobBuilder.newJob(SimpleJob.class).withIdentity(job1Key).build();TriggerKey tk1 = TriggerKey.triggerKey("trigger1", "my-jobs");Trigger trigger1 = TriggerBuilder.newTrigger().withIdentity(tk1).startNow().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10)).build();TriggerKey tk2 = TriggerKey.triggerKey("trigger2", "my-jobs");Trigger trigger2 = TriggerBuilder.newTrigger().withIdentity(tk2).startNow().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10)).build();TriggerKey tk3 = TriggerKey.triggerKey("trigger3", "my-jobs");Trigger trigger3 = TriggerBuilder.newTrigger().withIdentity(tk3).startNow().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(10)).build();scheduler.scheduleJob(job1, newHashSet(trigger1, trigger2, trigger3), true);scheduler.start();printJobsAndTriggers(scheduler);} catch (SchedulerException e) {LOG.error("Error while creating scheduler", e);} }除了上述內容之外,我還添加了在SimpleJob中記錄JobExecutionContext的信息,因此可以更好地分析結果。
@Override public void execute(JobExecutionContext context) throws JobExecutionException {try {LOG.info("Instance: {}, Trigger: {}, Fired at: {}",context.getScheduler().getSchedulerInstanceId(),context.getTrigger().getKey(),sdf.format(context.getFireTime()));} catch (SchedulerException e) {}simpleEjb.doSomething(); }運行所有三個服務器實例后,我觀察了結果。
工作執行
我觀察到在所有三個節點上都執行trigger2,并且在三個節點上執行了trigger2,如下所示:
Instance: kolorobot1399805959393 (instance1), Trigger: my-jobs.trigger2, Fired at: 13:00:09 Instance: kolorobot1399805989333 (instance3), Trigger: my-jobs.trigger2, Fired at: 13:00:19 Instance: kolorobot1399805963359 (instance2), Trigger: my-jobs.trigger2, Fired at: 13:00:29 Instance: kolorobot1399805959393 (instance1), Trigger: my-jobs.trigger2, Fired at: 13:00:39 Instance: kolorobot1399805959393 (instance1), Trigger: my-jobs.trigger2, Fired at: 13:00:59對于其他觸發器類似。
復蘇
斷開kolorobot1399805989333(instance3)的連接后,一段時間后,我在日志中看到以下內容:
ClusterManager: detected 1 failed or restarted instances. ClusterManager: Scanning for instance "kolorobot1399805989333"'s failed in-progress jobs.然后我斷開了kolorobot1399805963359(instance2)的連接,這也是我在日志中看到的內容:
ClusterManager: detected 1 failed or restarted instances. ClusterManager: Scanning for instance "kolorobot1399805963359"'s failed in-progress jobs. ClusterManager: ......Freed 1 acquired trigger(s).到目前為止,由kolorobot1399805959393(instance1)執行的所有觸發器
在Wildfly 8上運行
無需任何更改,我就可以在WildFly 8.0.0上部署相同的應用程序。 與JBoss 7.1.1相似,我添加了MySQL模塊(WildFly 8上modules文件夾的位置不同– modules/system/layers/base/com/mysql/main 。數據源和驅動程序的定義與上圖完全相同。我為WildFly 8創建了運行配置:
然后我運行該應用程序,結果與JBoss 7相同。
我發現WildFly似乎為持久EJB計時器提供了基于數據庫的存儲 ,但是我尚未對其進行調查。 也許是另一篇博客文章的內容。
源代碼
- 請在GitHub上找到此博客文章的源代碼: https : //github.com/kolorobot/quartz-jee-demo
翻譯自: https://www.javacodegeeks.com/2014/05/how-to-quartz-scheduler-with-clustering-in-jee-application-with-mysql.html
總結
以上是生活随笔為你收集整理的HOW-TO:具有MySQL的JEE应用程序中具有集群功能的Quartz Scheduler的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux对齐命令(linux对齐)
- 下一篇: 老游戏安卓版(老游戏安卓)