原文地址: https://devcenter.heroku.com/articles/scheduled-jobs-custom-clock-processes-java-quartz-rabbitmq

Table of Contents

There are numerous ways to schedule background jobs in Java applications. This article will teach you how to setup a Java application that uses the Quartz library along with RabbitMQ to create a scalable and reliable method of scheduling background jobs on Heroku.

Many of the common methods for background processing in Java advocate running background jobs within the same application as the web tier. This approach has scalability and reliability constraints.

A better approach is to move background jobs into separate processes so that the web tier is distinctly separate from the background processing tier. This allows the web tier to be exclusively for handling web requests. The scheduling of jobs should also be it’s own tier that puts jobs onto a queue. The worker processing tier can then be scaled independently from the rest of the application.

If you have questions about Java on Heroku, consider discussing them in the Java on Heroku forums.

The source for this article's reference application is available on GitHub.

Prerequisites

To clone the sample project to your local computer run:

$ git clone https://github.com/heroku/devcenter-java-quartz-rabbitmq.git
Cloning into devcenter-java-quartz-rabbitmq... $ cd devcenter-java-quartz-rabbitmq/

Scheduling jobs with Quartz

custom clock process will be used to create jobs and add them to a queue. To setup a custom clock process use the Quartz library. In Maven the dependency is declared with:

See the reference app's pom.xml for the full Maven build definition.

<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.1.5</version>
</dependency>

Now a Java application can be used to schedule jobs. Here is an example:

package com.heroku.devcenter;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.repeatSecondlyForever;
import static org.quartz.TriggerBuilder.newTrigger; public class SchedulerMain { final static Logger logger = LoggerFactory.getLogger(SchedulerMain.class); public static void main(String[] args) throws Exception {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); JobDetail jobDetail = newJob(HelloJob.class).build(); Trigger trigger = newTrigger()
.startNow()
.withSchedule(repeatSecondlyForever(2))
.build(); scheduler.scheduleJob(jobDetail, trigger);
} public static class HelloJob implements Job { @Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("HelloJob executed");
}
}
}

This simple example creates a HelloJob every two seconds that simply logs a message. Quartz has a very extensive API for creating Triggerschedules.

To test this application locally you can run the Maven build and then run the SchedulerMain Java class:

$ mvn package
INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building devcenter-java-quartz-rabbitmq 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------ $ java -cp target/classes:target/dependency/* com.heroku.devcenter.SchedulerMain
...
66 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
66 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.1.5
66 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
104 [DefaultQuartzScheduler_Worker-1] INFO com.heroku.devcenter.SchedulerMain - HelloJob executed
2084 [DefaultQuartzScheduler_Worker-2] INFO com.heroku.devcenter.SchedulerMain - HelloJob executed

Press Ctrl-C to exit the app.

If the HelloJob actually did work itself then we would have a runtime bottleneck because we could not scale the scheduler while avoiding duplicate jobs being scheduled. Quartz does have a JDBC module that can use a database to prevent jobs from being duplicated but a simpler approach is to only run one instance of the scheduler and have the scheduled jobs added to a message queue where they can be processes in parallel by job worker processes.

Queuing jobs with RabbitMQ

RabbitMQ can be used as a message queue so the scheduler process can be used just to add jobs to a queue and worker processes can be used to grab jobs from the queue and process them. To add the RabbitMQ client library as a dependency in Maven specify the following in dependencies block of the pom.xml file:

<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>2.8.2</version>
</dependency>

If you want to test this locally then install RabbitMQ and set an environment variable that will provide the application the connection information to your RabbitMQ server.

On Windows:

$ set CLOUDAMQP_URL="amqp://guest:guest@localhost:5672/%2f"

On Mac/Linux:

$ export CLOUDAMQP_URL="amqp://guest:guest@localhost:5672/%2f"

The CLOUDAMQP_URL environment variable will be used by the scheduler and worker processes to connect to the shared message queue. This example uses that environment variable because that is the way theCloudAMQP Heroku Add-on will provide it’s connection information to the application.

The SchedulerMain class needs to be updated to add a new message onto a queue every time the HelloJob is executed. Here are the newSchedulerMain and HelloJob classes from the SchedulerMain.java file in the sample project:

package com.heroku.devcenter;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.HashMap;
import java.util.Map; import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.repeatSecondlyForever;
import static org.quartz.TriggerBuilder.newTrigger; public class SchedulerMain { final static Logger logger = LoggerFactory.getLogger(SchedulerMain.class);
final static ConnectionFactory factory = new ConnectionFactory(); public static void main(String[] args) throws Exception {
factory.setUri(System.getenv("CLOUDAMQP_URL"));
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); JobDetail jobDetail = newJob(HelloJob.class).build(); Trigger trigger = newTrigger()
.startNow()
.withSchedule(repeatSecondlyForever(5))
.build(); scheduler.scheduleJob(jobDetail, trigger);
} public static class HelloJob implements Job { @Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { try {
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String queueName = "work-queue-1";
Map<String, Object> params = new HashMap<String, Object>();
params.put("x-ha-policy", "all");
channel.queueDeclare(queueName, true, false, false, params); String msg = "Sent at:" + System.currentTimeMillis();
byte[] body = msg.getBytes("UTF-8");
channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, body);
logger.info("Message Sent: " + msg);
connection.close();
}
catch (Exception e) {
logger.error(e.getMessage(), e);
} } } }

In this example every time the HelloJob is executed it adds a message onto a RabbitMQ message queue simply containing a String with the time the String was created. Running the updated SchedulerMainshould add a new message to the queue every 5 seconds.

Processing jobs

Next, create a Java application that will pull messages from the queue and handle them. This application will also use the RabbitFactoryUtilto get a connection to RabbitMQ from the CLOUDAMQP_URL environment variable. Here is the WorkerMain class from the WorkerMain.java file in the example project:

package com.heroku.devcenter;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.Collections;
import java.util.HashMap;
import java.util.Map; public class WorkerMain { final static Logger logger = LoggerFactory.getLogger(WorkerMain.class); public static void main(String[] args) throws Exception { ConnectionFactory factory = new ConnectionFactory();
factory.setUri(System.getenv("CLOUDAMQP_URL"));
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String queueName = "work-queue-1";
Map<String, Object> params = new HashMap<String, Object>();
params.put("x-ha-policy", "all");
channel.queueDeclare(queueName, true, false, false, params);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, false, consumer); while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
if (delivery != null) {
String msg = new String(delivery.getBody(), "UTF-8");
logger.info("Message Received: " + msg);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
} } }

This class simply waits for new messages on the message queue and logs that it received them. You can run this example locally by doing a build and then running the WorkerMain class:

$ mvn package
$ java -cp target/classes:target/dependency/* com.heroku.devcenter.WorkerMain

You can also run multiple instances of this example locally to see how the job processing can be horizontally distributed.

Running on Heroku

Now that you have everything working locally you can run this on Heroku. First declare the process model in a new file named Procfilecontaining:

scheduler: java $JAVA_OPTS -cp target/classes:target/dependency/* com.heroku.devcenter.SchedulerMain
worker: java $JAVA_OPTS -cp target/classes:target/dependency/* com.heroku.devcenter.WorkerMain

This defines two process types that can be executed on Heroku; one named scheduler for the SchedulerMain app and one named workerfor the WorkerMain app.

To run on Heroku you will need to push a Git repository to Heroku containing the Maven build descriptor, source code, and Procfile. If you cloned the example project then you already have a Git repository. If you need to create a new git repository containing these files, run:

$ git init
$ git add src pom.xml Procfile
$ git commit -m init

Create a new application on Heroku from within the project’s root directory:

$ heroku create
Creating furious-cloud-2945... done, stack is cedar-14
http://furious-cloud-2945.herokuapp.com/ | git@heroku.com:furious-cloud-2945.git
Git remote heroku added

Then add the CloudAMQP add-on to your application:

$ heroku addons:add cloudamqp
Adding cloudamqp to furious-cloud-2945... done, v2 (free)
cloudamqp documentation available at: https://devcenter.heroku.com/articles/cloudamqp

Now push your Git repository to Heroku:

$ git push heroku master
Counting objects: 165, done.
Delta compression using up to 2 threads.
...
-----> Heroku receiving push
-----> Java app detected
...
-----> Discovering process types
Procfile declares types -> scheduler, worker
-----> Compiled slug size is 1.4MB
-----> Launching... done, v5
http://furious-cloud-2945.herokuapp.com deployed to Heroku

This will run the Maven build for your project on Heroku and create a slug file containing the executable assets for your application. To run the application you will need to allocate dynos to run each process type. You should only allocate one dyno to run the scheduler process type to avoid duplicate job scheduling. You can allocate as many dynos as needed to the worker process type since it is event driven and parallelizable through the RabbitMQ message queue.

To allocate one dyno to the scheduler process type run:

$ heroku ps:scale scheduler=1
Scaling scheduler processes... done, now running 1

This should begin adding messages to the queue every five seconds. To allocate two dynos to the worker process type run:

$ heroku ps:scale worker=2
Scaling worker processes... done, now running 2

This will provision two dynos, each which will run the WorkerMain app and pull messages from the queue for processing. You can verify that this is happening by watching the Heroku logs for your application. To open a feed of your logs run:

$ heroku logs -t
2012-06-26T22:26:47+00:00 app[scheduler.1]: 100223 [DefaultQuartzScheduler_Worker-1] INFO com.heroku.devcenter.SchedulerMain - Message Sent: Sent at:1340749607126
2012-06-26T22:26:47+00:00 app[worker.2]: 104798 [main] INFO com.heroku.devcenter.WorkerMain - Message Received: Sent at:1340749607126
2012-06-26T22:26:52+00:00 app[scheduler.1]: 105252 [DefaultQuartzScheduler_Worker-2] INFO com.heroku.devcenter.SchedulerMain - Message Sent: Sent at:1340749612155
2012-06-26T22:26:52+00:00 app[worker.1]: 109738 [main] INFO com.heroku.devcenter.WorkerMain - Message Received: Sent at:1340749612155

In this example execution the scheduler creates 2 messages which are handled by the two different worker dynos (worker.1 and worker.2). This shows that the work is being scheduled and distributed correctly.

Further learning

This example application just shows the basics for architecting a scalable and reliable system for scheduling and processing background jobs. To learn more see:

Scheduled Jobs with Custom Clock Processes in Java with Quartz and RabbitMQ的更多相关文章

  1. java 多线程——quartz 定时调度的例子

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  2. 【RabbitMQ】 Java简单的实现RabbitMQ

    准备工作 1.安装RabbitMQ,参考[RabbitMQ] RabbitMQ安装 2.新建Java项目,引入RabbitMQ的Maven依赖 <dependency> <group ...

  3. java框架---->quartz的使用(一)

    Quartz 是个开源的作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制.今天我们就来学习一下它的使用,这里会分篇章对它进行介绍.只是希望能有个人,在我说没事的时候,知道我不 ...

  4. [Spring] Java spring quartz 定时任务

    首先,需要导入quartz 的jar包 ① applicationContext.xml <!-- 轮询任务 --> <import resource="classpath ...

  5. Java学习---Quartz定时任务快速入门

    Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简单或为运行十个,百个, ...

  6. java任务调度quartz框架的小例子

    quartz是一个开源的作业调度框架,当然,java可以使用Timer来实现简单任务调度的功能,但Timer是单线程的设计方案,使得一个任务延迟会影响到其他的任务.java也可以使用Scheduled ...

  7. rabbitMQ第二篇:java简单的实现RabbitMQ

    前言:在这里我将用java来简单的实现rabbitMQ.下面我们带着下面问题来一步步的了解和学习rabbitMQ. 1:如果消费者连接中断,这期间我们应该怎么办 2:如何做到负载均衡 3:如何有效的将 ...

  8. java maven quartz exampe 实用指南

    pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w ...

  9. java实现Quartz定时功能

    本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/49975443 最近在学习定时相关的技术.当前,几乎所有的互 ...

随机推荐

  1. SQL存储过程相关信息查看

    --1.查看所有存储过程与函数      exec sp_stored_procedures     或者      select * from dbo.sysobjects where OBJECT ...

  2. Spring技术内幕:设计理念和整体架构概述(转)

    程序员都很崇拜技术大神,很大一部分是因为他们发现和解决问题的能力,特别是线上出现紧急问题时,总是能够快速定位和解决. 一方面,他们有深厚的技术基础,对应用的技术知其所以然,另一方面,在采坑的过程中不断 ...

  3. 今日头条高级后端开发实习生三轮技术面+HR面 面经

    二面结束后已经意识模糊,好多问过的东西都忘了,而且有一些基础知识就不在这写了,大部分公司都问的差不多... 一面(2018/03/27,11:00~11:50) 1:自我介绍 2:简单说说你这个项目吧 ...

  4. 【WIN10】程序內文件讀取與保存

    DEMO下載:http://yunpan.cn/cFHIZNmAy4ZtH  访问密码 cf79 1.讀取與保存文件 Assets一般被認為是保存用戶文件數據的地方.同時,微軟還支持用戶自己創建文件夾 ...

  5. 【BZOJ 4558】 4558: [JLoi2016]方 (计数、容斥原理)

    未经博主同意不能转载 4558: [JLoi2016]方 Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 362  Solved: 162 Descri ...

  6. BZOJ.5329.[SDOI2018]战略游戏(圆方树 虚树)

    题目链接 显然先建圆方树,方点权值为0圆点权值为1,两点间的答案就是路径权值和减去起点终点. 对于询问,显然可以建虚树.但是只需要计算两关键点间路径权值,所以不需要建出虚树.统计DFS序相邻的两关键点 ...

  7. 【递推】Codeforces Round #483 (Div. 2) [Thanks, Botan Investments and Victor Shaburov!] D. XOR-pyramid

    题意:定义,对于a数组的一个子区间[l,r],f[l,r]定义为对该子区间执行f操作的值.显然,有f[l,r]=f[l,r-1] xor f[l+1,r].又定义ans[l,r]为满足l<=i& ...

  8. hdu 5246 乱搞

    题意:题目太长直接看链接 链接:点我 乱搞题 显然,一个人要想成功,必须大于等于最强的人的战斗力,所以我们从后往前看 这里直接拿例1解释,首先递减排个序 15,13,10,9,8 作差得2,3,1,1 ...

  9. SpringBoot静态资源目录

    在web开发中,静态资源的访问是必不可少的,如:图片.js.css 等资源的访问. SpringBoot对静态资源访问提供了很好的支持,基本使用默认配置就能满足开发需求. 在传统的web项目中静态资源 ...

  10. 【洛谷】3953:逛公园【反向最短路】【记忆化搜索(DP)统计方案】

    P3953 逛公园 题目描述 策策同学特别喜欢逛公园.公园可以看成一张N个点M条边构成的有向图,且没有 自环和重边.其中1号点是公园的入口,N号点是公园的出口,每条边有一个非负权值, 代表策策经过这条 ...