1. 程式人生 > >Redis使用總結(四、處理延時任務)

Redis使用總結(四、處理延時任務)

引言

在開發中,往往會遇到一些關於延時任務的需求。例如

  • 生成訂單30分鐘未支付,則自動取消

  • 生成訂單60秒後,給使用者發簡訊

對上述的任務,我們給一個專業的名字來形容,那就是延時任務。那麼這裡就會產生一個問題,這個延時任務和定時任務的區別究竟在哪裡呢?一共有如下幾點區別

  1. 定時任務有明確的觸發時間,延時任務沒有

  2. 定時任務有執行週期,而延時任務在某事件觸發後一段時間內執行,沒有執行週期

  3. 定時任務一般執行的是批處理操作是多個任務,而延時任務一般是單個任務

下面,我們以判斷訂單是否超時為例,進行方案分析

方案分析

(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>