技術(shù)匯總:第九章:任務(wù)調(diào)度SpringTask

什么是任務(wù)調(diào)度

在企業(yè)級(jí)應(yīng)用中,經(jīng)常會(huì)制定一些“計(jì)劃任務(wù)”,即在某個(gè)時(shí)間點(diǎn)做某件事情,核心是以時(shí)間為關(guān)注點(diǎn),即在一個(gè)特定的時(shí)間點(diǎn),系統(tǒng)執(zhí)行指定的一個(gè)操作。常見的任務(wù)調(diào)度框架有Quartz和SpringTask等。
 SpringTask入門小Demo

創(chuàng)建模塊pinyougou-task-service,引入spring相關(guān)依賴 dao 和common工程,tomcat7端口為9108  

添加web.xml

添加配置文件applicationContext-task.xml  ,內(nèi)容如下

<?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:p="http://www.springframework.org/schema/p"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:task="http://www.springframework.org/schema/task"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd

        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.2.xsd">

<context:component-scan base-package="com.pinyougou.task"/>

    <task:annotation-driven/>

</beans>

創(chuàng)建包c(diǎn)om.pinyougou.task

編寫類

@Component

public class SeckillTask {

/**

 * 刷新秒殺商品

 */

@Scheduled(cron="* * * * * ?")

public void refreshSeckillGoods(){

System.out.println("執(zhí)行了任務(wù)調(diào)度"+new Date());

}

}

執(zhí)行后會(huì)看到控制臺(tái)每秒都輸出了當(dāng)前時(shí)間,其中cron設(shè)置的為表達(dá)式,是執(zhí)行的時(shí)間規(guī)則。
Cron表達(dá)式
Cron表達(dá)式格式

 

Cron表達(dá)式是一個(gè)字符串,字符串以5或6個(gè)空格隔開,分為6或7個(gè)域,每一個(gè)域代表一個(gè)含義,Cron有如下兩種語法格式:

(1)Seconds Minutes Hours DayofMonth Month DayofWeek Year

(2)Seconds Minutes Hours DayofMonth Month DayofWeek

每一個(gè)域可出現(xiàn)的字符如下:

Seconds:可出現(xiàn)", - * /"四個(gè)字符,有效范圍為0-59的整數(shù)

Minutes:可出現(xiàn)", - * /"四個(gè)字符,有效范圍為0-59的整數(shù)

Hours:可出現(xiàn)", - * /"四個(gè)字符,有效范圍為0-23的整數(shù)

DayofMonth:可出現(xiàn)", - * / ? L W C"八個(gè)字符,有效范圍為1-31的整數(shù)

Month:可出現(xiàn)", - * /"四個(gè)字符,有效范圍為1-12的整數(shù)或JAN-DEc

DayofWeek:可出現(xiàn)", - * / ? L C #"四個(gè)字符,有效范圍為1-7的整數(shù)或SUN-SAT兩個(gè)范圍。1表示星期天,2表示星期一, 依次類推

Year:可出現(xiàn)", - * /"四個(gè)字符,有效范圍為1970-2099年

每一個(gè)域都使用數(shù)字,但還可以出現(xiàn)如下特殊字符,它們的含義是:

(1)*:表示匹配該域的任意值,假如在Minutes域使用*, 即表示每分鐘都會(huì)觸發(fā)事件。

(2)?:只能用在DayofMonth和DayofWeek兩個(gè)域。它也匹配域的任意值,但實(shí)際不會(huì)。因?yàn)镈ayofMonth和 DayofWeek會(huì)相互影響。例如想在每月的20日觸發(fā)調(diào)度,不管20日到底是星期幾,則只能使用如下寫法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期幾都會(huì)觸發(fā),實(shí)際上并不是這樣。

(3)-:表示范圍,例如在Minutes域使用5-20,表示從5分到20分鐘每分鐘觸發(fā)一次

(4)/:表示起始時(shí)間開始觸發(fā),然后每隔固定時(shí)間觸發(fā)一次,例如在Minutes域使用5/20,則意味著5分鐘觸發(fā)一次,而25,45等分別觸發(fā)一次.

(5),:表示列出枚舉值值。例如:在Minutes域使用5,20,則意味著在5和20分每分鐘觸發(fā)一次。

(6)L:表示最后,只能出現(xiàn)在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味著在最后的一個(gè)星期四觸發(fā)。

(7)W: 表示有效工作日(周一到周五),只能出現(xiàn)在DayofMonth域,系統(tǒng)將在離指定日期的最近的有效工作日觸發(fā)事件。例如:在 DayofMonth使用5W,如果5日是星期六,則將在最近的工作日:星期五,即4日觸發(fā)。如果5日是星期天,則在6日(周一)觸發(fā);如果5日在星期一 到星期五中的一天,則就在5日觸發(fā)。另外一點(diǎn),W的最近尋找不會(huì)跨過月份

(8)LW:這兩個(gè)字符可以連用,表示在某個(gè)月最后一個(gè)工作日,即最后一個(gè)星期五。

(9)#:用于確定每個(gè)月第幾個(gè)星期幾,只能出現(xiàn)在DayofMonth域。例如在4#2,表示某月的第二個(gè)星期三。
 Cron表達(dá)式例子

0 0 10,14,16 * * ? 每天上午10點(diǎn),下午2點(diǎn),4點(diǎn)

0 0/30 9-17 * * ? 朝九晚五工作時(shí)間內(nèi)每半小時(shí)

0 0 12 ? * WED 表示每個(gè)星期三中午12點(diǎn)

"0 0 12 * * ?" 每天中午12點(diǎn)觸發(fā)

"0 15 10 ? * *" 每天上午10:15觸發(fā)

"0 15 10 * * ?" 每天上午10:15觸發(fā)

"0 15 10 * * ? *" 每天上午10:15觸發(fā)

"0 15 10 * * ? 2005" 2005年的每天上午10:15觸發(fā)

"0 * 14 * * ?" 在每天下午2點(diǎn)到下午2:59期間的每1分鐘觸發(fā)

"0 0/5 14 * * ?" 在每天下午2點(diǎn)到下午2:55期間的每5分鐘觸發(fā)

"0 0/5 14,18 * * ?" 在每天下午2點(diǎn)到2:55期間和下午6點(diǎn)到6:55期間的每5分鐘觸發(fā)

"0 0-5 14 * * ?" 在每天下午2點(diǎn)到下午2:05期間的每1分鐘觸發(fā)

"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44觸發(fā)

"0 15 10 ? * MON-FRI" 周一至周五的上午10:15觸發(fā)

"0 15 10 15 * ?" 每月15日上午10:15觸發(fā)

"0 15 10 L * ?" 每月最后一日的上午10:15觸發(fā)

"0 15 10 ? * 6L" 每月的最后一個(gè)星期五上午10:15觸發(fā)

"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一個(gè)星期五上午10:15觸發(fā)

"0 15 10 ? * 6#3" 每月的第三個(gè)星期五上午10:15觸發(fā)

 
秒殺商品列表的增量更新

每分鐘執(zhí)行查詢秒殺商品表,將符合條件的記錄并且緩存中不存在的秒殺商品存入緩存

/**

 * 刷新秒殺商品

 */

@Scheduled(cron="0 * * * * ?")

public void refreshSeckillGoods(){

System.out.println("執(zhí)行了任務(wù)調(diào)度"+new Date());

//查詢所有的秒殺商品鍵集合

List ids = new ArrayList( redisTemplate.boundHashOps("seckillGoods").keys());

//查詢正在秒殺的商品列表

TbSeckillGoodsExample example=new TbSeckillGoodsExample();

Criteria criteria = example.createCriteria();

criteria.andStatusEqualTo("1");//審核通過

criteria.andStockCountGreaterThan(0);//剩余庫存大于0

criteria.andStartTimeLessThanOrEqualTo(new Date());//開始時(shí)間小于等于當(dāng)前時(shí)間

criteria.andEndTimeGreaterThan(new Date());//結(jié)束時(shí)間大于當(dāng)前時(shí)間

criteria.andIdNotIn(ids);//排除緩存中已經(jīng)有的商品

List<TbSeckillGoods> seckillGoodsList= seckillGoodsMapper.selectByExample(example);

//裝入緩存

for( TbSeckillGoods seckill:seckillGoodsList ){

redisTemplate.boundHashOps("seckillGoods").put(seckill.getId(), seckill);

}

System.out.println("將"+seckillGoodsList.size()+"條商品裝入緩存");

}
過期秒殺商品的移除

每秒中在緩存的秒殺上皮列表中查詢過期的商品,發(fā)現(xiàn)過期同步到數(shù)據(jù)庫,并在緩存中移除該秒殺商品

/**

 * 移除秒殺商品

 */

@Scheduled(cron="* * * * * ?")

public void removeSeckillGoods(){

System.out.println("移除秒殺商品任務(wù)在執(zhí)行");

//掃描緩存中秒殺商品列表,發(fā)現(xiàn)過期的移除

List<TbSeckillGoods> seckillGoodsList = redisTemplate.boundHashOps("seckillGoods").values();

for( TbSeckillGoods seckill:seckillGoodsList ){

if(seckill.getEndTime().getTime()<new Date().getTime()  ){//如果結(jié)束日期小于當(dāng)前日期,則表示過期

seckillGoodsMapper.updateByPrimaryKey(seckill);//向數(shù)據(jù)庫保存記錄

redisTemplate.boundHashOps("seckillGoods").delete(seckill.getId());//移除緩存數(shù)據(jù)

System.out.println("移除秒殺商品"+seckill.getId());

}

}

System.out.println("移除秒殺商品任務(wù)結(jié)束");

}