定時任務知多少(二)——持久化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表總是空的,但是為什麼會生成過它呢?從名稱上即可做出做好的猜測。加鎖,沒錯。比如多個執行緒同時新增同一個定時任務(比如,多個使用者同時對同一款產品的同一個庫存進行操作),如何才能正確條件定時任務呢?其實想到鎖,這個問題就很好結局了,大家完全可以把當做樂觀鎖來處理。
本篇文章介紹的可持久化的定時任務,使用起來比較靈活,用在訂單過期、庫存失效等業務上非常合適。