1. 程式人生 > >定時任務知多少(二)——持久化quartz到Mongodb中

定時任務知多少(二)——持久化quartz到Mongodb中

上文中,我們粗劣的介紹定時任務相關的知識。並且,我們初步瞭解了 Spring+quartz 的初步應用:將quartz放在記憶體中。

通過上文的分析,我們很容易看清:該種方式實現定時任務,較為簡單,實現的功能也較為粗劣。由於我們直接把quartz放入記憶體中,等待執行;我們無法在它執行之前對它進行操作,比如任務暫停、刪除等等。

今天,我們就來繼續介紹可序列化的quartz的deno。

二、整合Spring,序列化Quartz到Mongodb中

首先,看一下Spring的如下配置:

<?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"
	xmlns:aop="http://www.springframework.org/schema/aop"
	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/aop
	 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
	 http://www.springframework.org/schema/context
	 http://www.springframework.org/schema/context/spring-context-3.0.xsd
	 http://activemq.apache.org/schema/core
	 http://activemq.apache.org/schema/core/activemq-core.xsd"
	default-lazy-init="true">

	<!--配置註解  -->
	<context:annotation-config/>
	<context:component-scan base-package="com.lzq.quartz" />
	
	<!--quartz排程器  -->
	<bean id="scheduler4Stock" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="jobFactory">
			<bean class="org.springframework.scheduling.quartz.SpringBeanJobFactory" />
		</property>
<property name="configLocation" value="classpath:/spring/quartz_test.properties"></property>
	</bean>

</beans>


上面的新增方法中,定時工作管理員會將quartz序列化到Mongodb中。我們需要在下面job的配置quartz_test.properties中,配置Mongodb和Quartz的相關資訊:

# Use the MongoDB store
org.quartz.jobStore.class=com.novemberain.quartz.mongodb.MongoDBJobStore
# MongoDB URI (optional if 'org.quartz.jobStore.addresses' is set)
org.quartz.jobStore.mongoUri=mongodb://localhost:27017
# comma separated list of mongodb hosts/replica set seeds (optional if 'org.quartz.jobStore.mongoUri' is set)
# org.quartz.jobStore.addresses=localhost
# database name
org.quartz.jobStore.dbName=test-hello-db 
# Will be used to create collections like mycol_jobs, mycol_triggers, mycol_calendars, mycol_locks
org.quartz.jobStore.collectionPrefix=world
# thread count setting is ignored by the MongoDB store but Quartz requries it
org.quartz.threadPool.threadCount=3
org.quartz.jobStore.misfireThreshold = 1800000


上面配置中,我們指定了org.quartz.jobStore.dbName=test-hello-db,這句的意思是我們定義儲存quartz的庫叫做test-hello-db;指定org.quartz.jobStore.collectionPrefix=world,設定表明的字首為world(因為會生成多個表,所以要定義字首,使其相鄰且便於辨認)。


job,該類需要繼承org.quartz.Job介面,我們可以把它理解成一個任務。這是定時任務到時間時,將要執行的該job類中的execute方法:

import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerKey;

/**
 *  庫存相關的一個job
 * @author lzq
 *
 */
public class StockReturnJob implements Job {

	private Logger logger = Logger.getLogger(StockReturnJob.class);

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		
		JobDataMap jobDataMap = context.getTrigger().getJobDataMap();
		Scheduler scheduler = context.getScheduler();
		
		String userId = jobDataMap.getString("userId");
		String creditId = jobDataMap.getString("creditId");
		int num = jobDataMap.getInt("num");

		String orderId = context.getJobDetail().getKey().getName();
		String stockId = context.getJobDetail().getKey().getGroup();

		// 定時任務開始執行
		System.out.println("定時任務開始執行");
		System.out.println("jobDataMap中userId="+userId);
		System.out.println("jobDataMap中creditId="+creditId);
		System.out.println("jobDataMap中num="+num);
		
		// 移除觸發器
//		TriggerKey triggerKey = new TriggerKey(orderId, stockId);
//		try {
//			scheduler.unscheduleJob(triggerKey);
//		} catch (SchedulerException e) {
//			e.printStackTrace();
//		}

	}

}


下面這個類中,我們定義了定時任務的管理器,在這裡,我們新增定時任務、刪除定時任務……,同時,我們也可以多定時進行其他操作,如暫定、對觸發器進行移出等等操作,本例只提供最基本的介紹,幫助大家快速上手quartz,其他內容,請大家在網路上尋找其他資料。

import java.util.Calendar;

import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerKey;
import org.quartz.impl.JobDetailImpl;
import org.quartz.impl.triggers.SimpleTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.lzq.tool.quartz.schedule.StockReturnJob;

@Component
public class OrderQuartz{
	
	@Autowired
	private Scheduler scheduler4Stock;
	
	/**
	 * 新增一個定時任務
	 * @param orderId
	 * @param stockId
	 */
	public void addQuartz(String orderId,String stockId){
		
		//建立一個job
		JobDetailImpl jobDetail = new JobDetailImpl();
		jobDetail.setJobClass(StockReturnJob.class);
		jobDetail.setKey(new JobKey(orderId, stockId));
		
		//job的資料
		JobDataMap jobDataMap = new JobDataMap();
		jobDataMap.put("stockId", stockId);
		jobDataMap.put("orderId", orderId);
		jobDataMap.put("userId", "user_id_test");
		jobDataMap.put("num",1024);
		
		SimpleTriggerImpl strigger = new SimpleTriggerImpl();
		strigger.setKey(new TriggerKey(orderId, stockId));
		
		 //設定執行時間
		Calendar ca = Calendar.getInstance();
		ca.add(Calendar.SECOND,45*60);
		strigger.setStartTime(ca.getTime());  
		strigger.setJobDataMap(jobDataMap);
		try {
			//開始一個定時任務
			scheduler4Stock.scheduleJob(jobDetail, strigger);
		} catch (SchedulerException e1) {
			e1.printStackTrace();
		}
    	
    }
	
	/**
	 * 刪除一個定時任務
	 * @param orderId
	 * @param stockId
	 * @return true,刪除成功;false,刪除失敗
	 */
	public boolean deleteQuartz(String orderId,String stockId){
		JobKey jk =new JobKey(orderId,stockId);
		boolean deleteFlag = false;
		try {
			deleteFlag = scheduler4Stock.deleteJob(jk);
		} catch (SchedulerException e) {
			e.printStackTrace();
		}
		return deleteFlag;
	}
}


通過如下程式,執行測試。

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

import com.lzq.tool.quartz.OrderQuartz;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
		"classpath:spring/spring.xml"
})
@TransactionConfiguration(transactionManager="transactionManager",defaultRollback=false)
@Transactional
public class QuartzTest{
	@Autowired
	private OrderQuartz orderQuartz;
	
	@Test
	public void testAddOrder() throws Exception {
		String orderId ="test-order-lzq1";
		String stockId ="test-stock-lzq1";
		orderQuartz.addQuartz(orderId, stockId);
	}
	
	
}


執行結果,會在Mongodb中生成test-hello-db庫,且生成的表如下:

生成如上圖的四個表,我們會發現,world_locks表總是空的,但是為什麼會生成過它呢?從名稱上即可做出做好的猜測。加鎖,沒錯。比如多個執行緒同時新增同一個定時任務(比如,多個使用者同時對同一款產品的同一個庫存進行操作),如何才能正確條件定時任務呢?其實想到鎖,這個問題就很好結局了,大家完全可以把當做樂觀鎖來處理。

本篇文章介紹的可持久化的定時任務,使用起來比較靈活,用在訂單過期、庫存失效等業務上非常合適。