Redis使用總結(四、處理延時任務)
引言
在開發中,往往會遇到一些關於延時任務的需求。例如
生成訂單30分鐘未支付,則自動取消
生成訂單60秒後,給使用者發簡訊
對上述的任務,我們給一個專業的名字來形容,那就是延時任務。那麼這裡就會產生一個問題,這個延時任務和定時任務的區別究竟在哪裡呢?一共有如下幾點區別
定時任務有明確的觸發時間,延時任務沒有
定時任務有執行週期,而延時任務在某事件觸發後一段時間內執行,沒有執行週期
定時任務一般執行的是批處理操作是多個任務,而延時任務一般是單個任務
下面,我們以判斷訂單是否超時為例,進行方案分析
方案分析
(1)資料庫輪詢
思路
該方案通常是在小型專案中使用,即通過一個執行緒定時的去掃描資料庫,通過訂單時間來判斷是否有超時的訂單,然後進行update或delete等操作
實現
博主當年早期是用quartz來實現的(實習那會的事),簡單介紹一下
maven專案引入一個依賴如下所示
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency>
呼叫Demo類MyJob如下所示
packagecom.rjzheng.delay1;
importorg.quartz
.JobBuilder;importorg.quartz.JobDetail;
importorg.quartz.Scheduler;
importorg.quartz.SchedulerException;
importorg.quartz.SchedulerFactory;
importorg.quartz.SimpleScheduleBuilder;
importorg.quartz.Trigger;
importorg.quartz.TriggerBuilder;
importorg.quartz.impl.StdSchedulerFactory;
importorg.quartz.Job;
import
org.quartz.JobExecutionContext;importorg.quartz.JobExecutionException;
publicclassMyJobimplementsJob{
publicvoidexecute(JobExecutionContextcontext)
throwsJobExecutionException{
System.out.println("要去資料庫掃描啦。。。");
}
publicstaticvoidmain(String[]args)throwsException{
// 建立任務
JobDetailjobDetail=JobBuilder.newJob(MyJob.class)
.withIdentity("job1","group1").build();
// 建立觸發器 每3秒鐘執行一次
Triggertrigger=TriggerBuilder
.newTrigger()
.withIdentity("trigger1","group3")
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3).repeatForever())
.build();
Schedulerscheduler=newStdSchedulerFactory().getScheduler();
// 將任務及其觸發器放入排程器
scheduler.scheduleJob(jobDetail,trigger);
// 排程器開始排程任務
scheduler.start();
}
}
執行程式碼,可發現每隔3秒,輸出如下
要去資料庫掃描啦。。。
優缺點
優點:簡單易行,支援叢集操作
缺點:(1)對伺服器記憶體消耗大
(2)存在延遲,比如你每隔3分鐘掃描一次,那最壞的延遲時間就是3分鐘
(3)假設你的訂單有幾千萬條,每隔幾分鐘這樣掃描一次,資料庫損耗極大
(2)JDK的延遲佇列
思路
該方案是利用JDK自帶的DelayQueue來實現,這是一個無界阻塞佇列,該佇列只有在延遲期滿的時候才能從中獲取元素,放入DelayQueue中的物件,是必須實現Delayed介面的。
DelayedQueue實現工作流程如下圖所示
其中Poll():獲取並移除佇列的超時元素,沒有則返回空
take():獲取並移除佇列的超時元素,如果沒有則wait當前執行緒,直到有元素滿足超時條件,返回結果。
實現
定義一個類OrderDelay實現Delayed,程式碼如下
packagecom.rjzheng.delay2;
importjava.util.concurrent.Delayed;
importjava.util.concurrent.TimeUnit;
publicclassOrderDelayimplementsDelayed{
privateStringorderId;
privatelongtimeout;
OrderDelay(StringorderId,longtimeout){
this.orderId=orderId;
this.timeout=timeout+System.nanoTime();
}
publicintcompareTo(Delayedother){
if(other==this)
return0;
OrderDelayt=(OrderDelay)other;
longd=(getDelay(TimeUnit.NANOSECONDS)-t
.getDelay(TimeUnit.NANOSECONDS));
return(d==0)?0:((d<0)?-1:1);
}
// 返回距離你自定義的超時時間還有多少
publiclonggetDelay(TimeUnitunit){
returnunit.convert(timeout-System.nanoTime(),TimeUnit.NANOSECONDS);
}
voidprint(){
System.out.println(orderId+"編號的訂單要刪除啦。。。。");
}
}
執行的測試Demo為,我們設定延遲時間為3秒
packagecom.rjzheng.delay2;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.concurrent.DelayQueue;
importjava.util.concurrent.TimeUnit;
publicclassDelayQueueDemo{
publicstaticvoidmain(String[]args){
// TODO Auto-generated method stub
List<String>list=newArrayList<String>();
list.add("00000001");
list.add("00000002");
list.add("00000003");
list.add("00000004");
list.add("00000005");
DelayQueue<OrderDelay>queue=newDelayQueue<OrderDelay>